Tourism 题解

树形 dp 的一道好题。

在一个图上做 dp 是非常难的,一个常见的思路就是图转化成树,树转化成链。那么我们对这个图做一个生成树。明显地,非树边都是返祖边,这个性质对树形 dp 非常好。

而我们又发现了树的直径至多为 \(10\)。这启示我们需要用状压的方式来写,而题目又是一个类树上最大独立集的东西,若我们直接在树上做的话可以写成 \(f_{i,0/1/2}\) 来转移,表示 \(i\) 这个点有选,没选有被覆盖,有选没被覆盖。而这里是一个生成树,有返祖边,肯定不能这么做,这里的返祖边和它的所有祖先的关系有关,所以需要把所有祖先的状态都记下来,写成 \(dp_{i,st}\) 表示 \(i\) 这个点,它的祖先状态为 \(st\) 的最小代价,而这里的 \(st\) 就是一个三进制状态。但是这个状态太抽象了,空间早就爆了,所以我们需要将 \(i\) 设为它的深度,因为这个树形 dp 是线性转移的,不会多个兄弟同时转移,所以可以这么做。

这里转移很简单,对于 \(dp_{u,st}\) 和它的一个儿子 \(v\) ,当 \(v\) 被选择时,它的所有返祖边的点上如果有 \(1\) 这个状态,就加 \(1\) ,状态变成 \(2\)。当 \(v\) 不被选择时,如果它的所有返祖边的点上如果有 \(0\) 这个状态,\(v\) 这个点状态为 \(2\) ,否则状态为 \(1\)

刷新儿子的时候 dp 也要改,更新为这个儿子选 \(0\)\(2\) 的最大值。

代码:

#include<bits/stdc++.h>
#define get(x,y) (i/pw[y]%3)
#define tomin(x,y) (x>y?x=y:0)
using namespace std;
const int N=2E4+5,inf=1e9;
int n,m,c[N],ans,pw[15],dp[15][60006],dep[N],p[N],top,vis[N];
vector<int>e[N];
void dfs(int x,int fa){
	
	vis[x]=1;int top=0;
	for(int v:e[x])
		if(dep[v] < dep[x] && vis[v])p[++top]=v;
	if(!fa){
		dp[dep[x]][0]=c[x];
		dp[dep[x]][1]=0;
		dp[dep[x]][2]=inf;
	} 
	else {
		for(int st=0;st<(pw[dep[x]+1]);st++)dp[dep[x]][st]=inf; 
		for(int st=0;st<(pw[dep[x]]);st++){
			int tmp1=st,tmp2=1;
			for(int i=1;i<=top;i++){
				if(get(st,dep[p[i]])==0)
					tmp2=2;
				if(get(st,dep[p[i]])==1)
					tmp1+=pw[dep[p[i]]];
			}
			tomin(dp[dep[x]][tmp1],dp[dep[fa]][st]+c[x]);
			tomin(dp[dep[x]][st+tmp2*pw[dep[x]]],dp[dep[fa]][st]);
		}
	}
	for(int v:e[x]){
		if(vis[v] )continue;
		dep[v]=dep[x]+1;
		dfs(v,x);
		for(int st=0;st<pw[dep[v]];st++)
			dp[dep[x]][st]=min(dp[dep[v]][st],dp[dep[v]][st+2*pw[dep[v]]]);
	}
}
signed main(){
	scanf("%d%d",&n,&m);
	pw[0]=1;
	for(int i=1;i<=10;i++)
		pw[i]=pw[i-1]*3;
	for(int i=1;i<=n;i++)
		scanf("%d",c+i);
	for(int i=1,a,b;i<=m;i++){
		scanf("%d%d",&a,&b);
		e[a].push_back(b);
		e[b].push_back(a);
	} 
	for(int i=1;i<=n;i++)
		if(!vis[i])
			dfs(i,0),ans+=min(dp[0][0],dp[0][2]);
	cout<<ans<<endl;
	return 0;
}

posted @ 2025-07-17 15:49  hnczy  阅读(6)  评论(0)    收藏  举报