简要题意:
给定一个n个点,m条边的无向图,其中你在第i个点建立旅游站点的费用为Ci。在这张图中,任意两点间不存在节点数超过10的简单路径。请找到一种费用最小的建立旅游站点的方案,使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。
题解:
根据dfs搜索树进行树DP。
对一个点的影响不仅有它的儿子,还有它的祖先。
对每一个独立的联通块DP,令f[i][s]表示在dfs搜索树中深度为i的点,其祖先的状态为s。其中s是一个三进制数。对于s中每一位上,0表示该点选了,1表示该点未选且这个点不满足要求,2表示该点未选但这个点满足要求(即它的邻接点选了)。
第一种转移:节点u不选。
访问节点u,其深度为dep时,先找出它的邻接点且为它的祖先。
第一种转移:节点u不选。
若其中有一个点选了,则
f[dep][s+2∗pow[dep]]=min(f[dep][s+2∗pow[dep]],f[dep−1][s]);
否则f[dep][s+pow[dep]]=min(f[dep][s+pow[dep]],f[dep−1][s]+val[u]);
第二种转移:节点u选了。
那么将s中为1的点改成2即可。
对于它的儿子,直接用min(f[dep+1][s],f[dep+1][s+2∗pow[dep+1]])来更新f[dep][s]即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=20005,M=50010,K=59050,INF=1000000000;
int read(){
int x=0,f=1; char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1; ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
struct Edg{
int nxt,poi;
}e[M];
int c[N],f[11][K],deep[N],pw[N],first[N],l=0,st[N];
bool vis[N];
void addedge(int u,int v){
l++;
e[l].nxt=first[u];
e[l].poi=v;
first[u]=l;
}
void dfs(int u){
vis[u]=1;
int mx=pw[deep[u]]-1,d=deep[u],top=0;
for (int i=0;i<pw[deep[u]+1];i++) f[d][i]=INF;
if (!d) f[0][0]=0,f[0][1]=INF,f[0][2]=c[u];
for (int p=first[u];p;p=e[p].nxt){
int v=e[p].poi; if (vis[v]) st[++top]=v;
}
for (int s=0;s<=mx;s++){
int e=s+2*pw[d],t=0;
for (int i=1;i<=top;i++){
int dv=deep[st[i]];
int sv=s/pw[dv]%3;
if (sv==2) t=1;
if (!sv) e+=pw[dv];
}
f[d][s+t*pw[d]]=min(f[d][s+t*pw[d]],f[d-1][s]);
f[d][e]=min(f[d][e],f[d-1][s]+c[u]);
}
for (int p=first[u];p;p=e[p].nxt){
int v=e[p].poi;
if (vis[v]) continue;
deep[v]=deep[u]+1; dfs(v);
for (int s=0;s<pw[d+1];s++){
f[d][s]=min(f[d+1][s+pw[d+1]],f[d+1][s+2*pw[d+1]]);
}
}
}
int main(){
int n=read(),m=read();
for (int i=1;i<=n;i++) c[i]=read();
for (int i=1;i<=m;i++){
int u=read(),v=read();
addedge(v,u); addedge(u,v);
}
pw[0]=1;
for (int i=1;i<=13;i++) pw[i]=pw[i-1]*3;
int ans=0;
for (int i=1;i<=n;i++){
if (vis[i]) continue;
dfs(i);
ans+=min(f[0][1],f[0][2]);
}
printf("%d\n",ans);
return 0;
}