2025/2/22课堂记录

目录

  1. 愚蠢的矿工
  2. 国王(猛兽军团)

愚蠢的矿工

这网页稍微有点乱,不过凑合凑合还能看,就是没法提交了而已

首先看到的第一眼,感觉是树上依赖背包

毕竟要挖到某一个节点的宝藏要之前知道根节点所有节点都要有狗狗停留

但是呢......

没错,他就是树上依赖背包(别打我)

但是呢,这道题还有第二种写法,就是多叉树转二叉树

没错,左牵儿子,右擎兄弟的孩子兄弟表示法

记住一个口诀:之前你的儿子现在是我的兄弟,现在你的儿子是我

然后就可以根据普通的树形dp来做啦
这是代码

#include<iostream>
#include<cstring>
using namespace std;
int a[1010],leftt[1010],rightt[1010],vis[1010][110];
int dp(int x,int m)//dp(a,b):求以a为根节点的子树,给b位矿工,求最大值,也就是vis[a][b] 
{
	if(m==0)return 0;//不给人没法干活
	if(vis[x][m]!=-1)return vis[x][m];//之前算过这个,剪枝
	if(x==-1)return 0;//访问的节点不存在,sorry,the number you...
	int ans=0;//终于剪完枝了 
	ans=dp(rightt[x],m);//自己没留人,孩子不能走。把所有人手都给自己的兄弟
	for(int i=0;i<=m-1;i++)//至少给自己留一人,然后就可以走孩子了,孩子兄弟分家产
		ans=max(ans,dp(leftt[x],i)+a[x]+dp(rightt[x],m-i-1));//别忘了数你自己! 
	vis[x][m]=ans;
	return ans; 
}
int main()
{
	memset(vis,-1,sizeof(vis));
	int n,m;
	cin>>n>>m;//宝藏个数;旷工人数
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=0;i<=n;i++)leftt[i]=rightt[i]=-1;
	for(int i=1;i<=n;i++)
	{
		int x,y;
		cin>>x>>y;
		rightt[y]=leftt[x];//之前你的儿子现在是我的兄弟
		leftt[x]=y;//现在你的儿子是我 
	}
	cout<<dp(leftt[0],m); //0的孩子就是树根 
	cout<<"\n";
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			cout<<vis[i][j]<<" ";
		cout<<"\n";
	}
	return 0;
}

国王(猛兽军团)

这是一个全新的dp种类,名叫"状压"

状压就是列数比较少的,行数不限的dp,每行可以用二进制等进行描述

前面预处理部分是筛选出所有合法状态,然后把二进制压缩(所以行数必须要少,不然数据的大小是呈指数型增长的!!)

比如10110这个

010110

& 101100


000100

所以就有重叠

后来通过4for:枚举行,个数,方案,前一行方案来进行dp

// 猛兽军团1; 互不侵犯
// <<算法竞赛宝典-第二部>>
// N*N  放m个猛兽  上下左右,四条对角线; 共8个方向冲突;
#include<iostream>
using namespace std;
int n,m,ans; 
int s[1050] ;//保存一行之内不冲突的可行状态
int state;   //统计行内可行状态的个数
int num[1050];//对应可行状态的猛兽个数;
int f[11][105][1050];// f[i][j][s]: 前i行放j个猛兽,并且第i行放置第s个可行状态; 
int main()
{
	cin>>n>>m;
	
	//预处理部分 : 找出行内不冲突的可行状态; 
	for(int i=0;i<(1<<n);i++)
	{
		if(i&i<<1)    // 状态s里面有相邻的1 ,冲突 
		    continue;
		state++;   
		s[state]=i;
		int t=i;
		while(t)
		{
			num[state]+=t&1;
			t=t>>1;   // t=t/2;
		}
	}
	
	//dp部分;
	//第一行 
	for(int i=1;i<=state;i++)
	{
		int j=num[i];
		if(j>m)
		    continue;
		f[1][j][i]=1;  //前1行放置j个猛兽,并且第1行放第i个合法状态的  方案数 
	}
	
	//2-n行  4for: 
	for(int i=2;i<=n;i++)  // 枚举到第i行 
	{
	    for(int j=0;j<=m;j++) // 1~i行放置j个猛兽 
		{
			for(int a=1;a<=state;a++)   // 当前行可行状态
			{
				int x=num[a];//第1行放置的猛兽个数 
				if(x>j)
				    continue;
				int xx=j-x;               //1~i-1行剩余可用的猛兽数 
				for(int b=1;b<=state;b++) //第i-1行可行状态
				{
					int y=num[b];
					if(y>xx)
				        continue;
				        
				    if(s[a]&s[b])     //同列 
				        continue;
				    if(s[a]<<1&s[b])  //左上角 
				        continue;
				    if(s[a]&s[b]<<1)  //右上角 
				        continue;
				        
				    f[i][j][a]+=f[i-1][j-num[a]][b];
				} 
				if(i==n&&j==m)
				    ans+=f[n][m][a]; 
			}
		}	
	} 
	cout<<ans<<endl;
	return 0;
}