[洛谷P3569] POI2014 KAR
问题描述
给定一个n个点,m条边的无向图,其中你在第i个点建立旅游站点的费用为Ci。在这张图中,任意两点间不存在节点数超过10的简单路径。请找到一种费用最小的建立旅游站点的方案,使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。
解析
对于这类问题,不妨对原图的每个连通块建出 DFS 树,那么原问题就转化为了树上点覆盖问题。由题目的性质,这棵树的深度不会超过10。
我们用一个3进制数表示一个点的状态:0表示这个点被选择,1表示这个点没有被选也没有被覆盖,2表示这个点没有被选但是被覆盖了。设 \(f_{u,d,s}\) 表示当前在点 \(u\) ,深度为 \(d\),\(u\) 的祖先们状态为 \(s\) 时的最小代价。转移时先考虑 \(u\) 的返祖边,然后继续DP,再用子树的状态更新 \(f_{u,d}\) 。在实际实现中 \(u\) 这一维可以省略。
设连通块的 DFS 树根节点为 \(root\),那么答案为 \(\sum \min(f_{root,0,0},f_{root,0,2})\)。
代码
#include <iostream>
#include <cstdio>
#define N 20002
#define M 25002
#define S 60000
using namespace std;
const int inf=1<<30;
int head[N],ver[M*2],nxt[M*2],l;
int n,m,i,w[N],c[N],f[12][N],pw[12],a[N],dep[N],ans;
bool vis[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
void insert(int x,int y)
{
l++;
ver[l]=y;
nxt[l]=head[x];
head[x]=l;
}
void dfs(int x,int pre)
{
vis[x]=1;
int cnt=0,d=dep[x];
if(d==0) f[0][0]=w[x],f[0][1]=0,f[0][2]=inf;
else{
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(vis[y]&&dep[y]<d) a[++cnt]=dep[y];
}
for(int s=0;s<pw[d+1];s++) f[d][s]=inf;
for(int s=0;s<pw[d];s++){
int s1=s,op=1;
for(int i=1;i<=cnt;i++){
if(s/pw[a[i]]%3==0) op=2;
else if(s/pw[a[i]]%3==1) s1+=pw[a[i]];
}
f[d][s+op*pw[d]]=min(f[d][s+op*pw[d]],f[d-1][s]);
f[d][s1]=min(f[d][s1],f[d-1][s]+w[x]);
}
}
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!vis[y]){
dep[y]=dep[x]+1;
dfs(y,x);
for(int s=0;s<pw[d+1];s++) f[d][s]=min(f[d+1][s],f[d+1][s+2*pw[d+1]]);
}
}
}
int main()
{
n=read();m=read();
for(i=1;i<=n;i++) w[i]=read();
for(i=1;i<=m;i++){
int u=read(),v=read();
insert(u,v);insert(v,u);
}
for(i=pw[0]=1;i<=10;i++) pw[i]=pw[i-1]*3;
for(i=1;i<=n;i++){
if(!vis[i]){
dfs(i,0);
ans+=min(f[0][0],f[0][2]);
}
}
printf("%d\n",ans);
return 0;
}