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;
}

浙公网安备 33010602011771号