题目
有一个n个点,m条边的无向图,第i个点建立一个旅游站点的费用是c_i。特别地,这张图中的任意两点间不存在节点数超过10的简单路径。
我想要建造一些旅游站点使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。我还希望这个建造方案总花费尽量少。
请求出这个花费。
数据范围
对于前30%的测试点,满足1<=n<=20,0<=m<=50。
对于另外15%的测试点,满足每个连通块都是一棵树。
对于100%的测试点,满足
1<=n<=2∗104,0<=m<=2.5∗104,0<=ci<=104
。
题解
30分肯定枚举选哪个不选哪个。
另15分直接树形DP。
AC做法
题目条件
选了一个点,与之相连的点可以不选。
以任何一点为根的dfs树深度不超过10。
困惑点
有返祖边干扰了转移。
只确定当前点的状态满足不了题目的需求。
准确做法
考虑树形DP。
考虑怎么样避免返祖边的干扰。一个新知识:欧拉序。
欧拉序,就是从根遍历到每个点,再回到根的经过的点的序列。
如果第二次到点x,说明x的子树已经处理完了,可以更新到x。
那么可以考虑基于欧拉序的DP。
设
f[x][s]
表示做到x,x到根节点的点状态为s(s为三进制数,0表示没有覆盖到也没选,1表示被覆盖到,2表示被选)
那么枚举状态s,讨论x是否被选两种情况就可以DP。(设x的父亲是y)
①没有被选:由于返祖边的干扰,我们先确定与之有边连着的祖先是否被选,如果是的话x一定会被覆盖到
f[x][s+3dep[x]−1]←f[y][s]
,否则不会
f[x][s]←f[y][s]
。
②被选:那么会影响到与之有边连着的祖先。
设这个新状态为s1,则有
f[x][s1]←f[y][s]+c[x]
。
接下来递归子树。由于第一次遍历到x点时为讨论到x的子树更新x的情况,所以
f[x][s]
要重新被算一次。由于x的祖先转移到x的情况已经传给了x的子树,所以递归回来的时候(设x的儿子为y)直接
f[x][s]=min(f[y][s+3dep[y]−1],f[y][s+2∗3dep[y]−1])
。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 20010
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
int to,next;
};note edge[N*5/2];
int i,j,k,l,n,m;
int dep[N],ans,fa[N];
int c[N],u,v;
int head[N],tot;
int f[2][59050];
int _3[13];
bool bz[N],bb[13];
int read(){
int fh=1,res=0;char ch;
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')fh=-1,ch=getchar();
while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
return res*fh;
}
void lb(int x,int y){
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
}
void mix(int &x,int y){x=x<y?x:y;}
void dg(int x){
bz[x]=1;
int o=dep[x]&1;
bool pp;
memset(f[o],63,sizeof(f[o]));
memset(bb,0,sizeof(bb));
int i,s,s1;
for(i=head[x];i;i=edge[i].next)
if(dep[edge[i].to]<dep[x])bb[dep[edge[i].to]]=1;
fo(s,0,_3[dep[x]]-1)
if(f[1^o][s]<1061109567){
s1=s+_3[dep[x]]*2;
pp=0;
fo(i,1,dep[x]-1)
if(bb[i]){
if(((s1/_3[i])%3)==0)s1+=_3[i];
if(((s/_3[i])%3)==2)pp=1;
}
mix(f[o][s1],f[1^o][s]+c[x]);
if(pp)mix(f[o][s+_3[dep[x]]],f[1^o][s]);
else mix(f[o][s],f[1^o][s]);
}
for(i=head[x];i;i=edge[i].next)
if(!bz[edge[i].to]){
fa[edge[i].to]=x;
dep[edge[i].to]=dep[x]+1;
dg(edge[i].to);
fo(s,0,_3[dep[edge[i].to]]-1)
f[o][s]=min(f[1^o][s+_3[dep[edge[i].to]]],f[1^o][s+_3[dep[edge[i].to]]*2]);
}
}
int main(){
_3[1]=1;fo(i,2,12)_3[i]=_3[i-1]*3;
n=read(),m=read();
fo(i,1,n)c[i]=read();
fo(i,1,m){
u=read(),v=read();
lb(u,v),lb(v,u);
}
fo(i,1,n)
if(!bz[i]){
memset(f,63,sizeof(f));
f[0][0]=0;
dep[i]=1;
dg(i);
ans+=min(f[1][1],f[1][2]);
}
printf("%d",ans);
return 0;
}