树形DP和状压DP

P1352 没有上司的舞会

作为一道经典例题,几乎学树形 DP 就得先做它。

f[i][j] ,当 j0 时表示第 i 个人不来,当 j1 时表示第 i 个人来,所以状态转移方程为:

f[x][1]+=f[y][0](yson(x))

f[x][0]+=max(f[y][0],f[y][1])(yson(x))

#include<bits/stdc++.h>
using namespace std;
int dp[200020][2];
int n;
int h[200003];
struct node{
	int to,nxt;
}z[200003];
int cnt;
void add(int a,int b){
	z[++cnt].to=b;
	z[cnt].nxt=h[a];
	h[a]=cnt;
}
int a[200003];
void dfs(int now,int fa){
	dp[now][0]=0;
	dp[now][1]=a[now];
	for(int i=h[now];i;i=z[i].nxt){
		int y=z[i].to;
		if(y==fa) continue;
		else{
			dfs(y,now);
			dp[now][0]+=max(dp[y][0],dp[y][1]);
			dp[now][1]+=dp[y][0]; 
		}
	}
}
int in[200003];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++) {
		int k,l;
		scanf("%d%d",&l,&k);
		add(k,l);
		in[l]++;
	}
	int boss;
	for(int i=1;i<=n;i++) if(!in[i]){ boss=i;break;	}
	dfs(boss,0);
	cout<<max(dp[boss][1],dp[boss][0]);
}

P2014 [CTSC1997] 选课

其实就是一个 01 背包。
f[i][j] 表示以 i 为根的子树中选了 j 个点的最大价值。
对于点 x 遍历自己的儿子们,然后不断与自己合并。

#include<bits/stdc++.h>
using namespace std;
struct node{
	int nxt,to;
}z[305];
int n,m,cnt;
int h[305];
int dp[305][304];
void add(int u,int v){
	cnt++;
	z[cnt].nxt=h[u];
	z[cnt].to=v;
	h[u]=cnt;
}
void dfs(int x){
	for(int i=h[x];i;i=z[i].nxt){
		int y=z[i].to;
		dfs(y);
		for(int j=m;j>=1;j--){
			for(int k=0;k<j;k++){
				dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]);
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		int a,b;
		scanf("%d%d",&a,&dp[i][1]);
		add(a,i);
	}
	m++;
	dfs(0);
	printf("%d",dp[0][m]);
}

P3478 [POI2008] STA-Station

我们可以先从第 1 个节点搜,预处理出当以 1 为根的深度和还有每个点的子树大小。
然后再来一遍 DFS 我们发现,当以 x 为根时,对于以 x 的父亲为根时,自己的子树的深度都会减 1,而其他的点的深度都会加 1,所以就得到了状态转移方程。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
struct node{
	int to,nxt;
}z[2000004];
int h[1000004];
int cnt;
void add(int x,int y){
	z[++cnt].to=y;
	z[cnt].nxt=h[x];
	h[x]=cnt;
}
int dep[1000004];
int siz[1000004];
int sum_dep[1000005];
void dfs(int x,int fa){
	siz[x]=1;
	dep[x]=dep[fa]+1;
	for(int i=h[x];i;i=z[i].nxt ){
		int y=z[i].to;
		if(y==fa) continue;
		else{
			dfs(y,x);
			siz[x]+=siz[y];
		}
	}
}
int f[1000004];
void DFS(int x,int fa){
	for(int i=h[x];i;i=z[i].nxt){
		int y=z[i].to;
		if(y==fa) continue;
		else{
			f[y]=f[x]-1ll*2*siz[y]+n;
			DFS(y,x);
		}
	} 
}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%lld%lld",&u,&v);
		add(u,v);
		add(v,u); 
	} 
	dfs(1,0);
	int t;
	int maxx=0;
	for(int i=1;i<=n;i++) f[1]+=dep[i];
	DFS(1,0);
	for(int i=1;i<=n;i++){
		//maxx=max(maxx,f[i]);
		if(maxx<f[i]){
			maxx=f[i];
			t=i;
		}
	}
	cout<<t<<endl;
}

P2607 [ZJOI2008] 骑士

这是一颗基环树,所以我们肯定不能上来就做。
我们可以先找到环,然后跑两次 DFS 就好了

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int f[N][2];
int val[N];
int n;
struct node{
	int to,nxt;
}z[2*N];
int cnt;
int h[N];
int F[N];
int vis[N];
void add(int x,int y){
	z[++cnt].to=y;
	z[cnt].nxt=h[x];
	h[x]=cnt;
}
int root;
void dp(int x){
	f[x][0]=0;
	f[x][1]=val[x];
	vis[x]=1;
	for(int i=h[x];i;i=z[i].nxt){
		int y=z[i].to;
		if(y!=root){
			dp(y); 
			f[x][0]+=max(f[y][0],f[y][1]);
			f[x][1]+=f[y][0];
		}
		else{
			f[y][1]=-1e9;
		}
	}
}
int ans;

void Find(int x){
	root=x;
	vis[x]=1;
	while(!vis[F[root]]){
		root=F[root];
		vis[root]=1;
	}
	dp(root);
	int t=max(f[root][1],f[root][0]);
	vis[root]=1;
	root=F[root];
	dp(root);
	ans+=max(t,max(f[root][1],f[root][0]));
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int fa;
		cin>>val[i]>>fa;
		F[i]=fa;
		add(fa,i);	
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			Find(i);
		}
	}
	cout<<ans;
}

P1896 [SCOI2005] 互不侵犯

一道很普通的状压 DP。
首先预处理出所有状态(即代码中的 dfs )。
然后定义 f[i][j][k] 表示d第 i 行的状态为 j,一共选了 k 个。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,K;
int sum_s[20090];
int f[10][120][100];
int cnt;
int s[20004];
void dfs(int x,int sum,int y){
	if(y>=n){
		s[++cnt]=x;
		sum_s[cnt]=sum;
		return ;
	}
	dfs(x,sum,y+1);
	dfs(x+(1<<y),sum+1,y+2); //因为两个King不能相邻 
}
signed main(){
	cin>>n>>K;
	dfs(0,0,0);
	for(int i=1;i<=cnt;i++) f[1][i][sum_s[i]]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=cnt;j++){
			for(int k=1;k<=cnt;k++){
				if(s[j]&s[k]) continue;
				if((s[j]<<1)&s[k]) continue;
				if(s[j]&(s[k]<<1)) continue;
				for(int p=K;p>=sum_s[j];p--){
					f[i][j][p]+=f[i-1][k][p-sum_s[j]];
				}
			}
		}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++) ans+=f[n][i][K];
	cout<<ans;
} 
posted @   yueyan_WZF  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示