P2700逐个击破(并查集/树形dp)
P2700 逐个击破
题目背景
三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起子一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,老毛同志制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。
题目描述
现在有N个城市,其中K个被敌方军团占领了,N个城市间有N-1条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你K个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这K个地方军团互相隔离开,以便第二步逐个击破敌人。
输入输出格式
输入格式:
第一行包含两个正整数n和k。
第二行包含k个整数,表示哪个城市别敌军占领。
接下来n-1行,每行包含三个正整数a,b,c,表示从a城市到b城市有一条公路,以及破坏的代价c。城市的编号从0开始。
输出格式:
输出一行一个整数,表示最少花费的代价。
输入输出样例
说明
【数据范围】
10%的数据:2≤n≤10;
100%的数据:2≤n≤100000,2≤k≤n,1≤c≤1000000。
/* 逆向并查集 把有敌人的联通块标记一下 别忘开long long */ #include<bits/stdc++.h> #define N 100001 #define ll long long using namespace std; int n,m,cnt; ll ans; int head[N<<1],a[N],fa[N],opt[N]; struct edge{ int u,v,w,nxt; bool operator < (const edge a) const{ return w>a.w; } }e[N<<1]; inline 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; } int find (int x) { return x==fa[x]?fa[x]:fa[x]=find(fa[x]); } int main() { //freopen("ly.in","r",stdin); int x,y,z; n=read();m=read(); for(int i=1;i<=m;i++) a[i]=read(),opt[a[i]]=1; for(int i=1;i<n;i++) { e[i].u=read();e[i].v=read();e[i].w=read(); ans+=e[i].w; } sort(e+1,e+n); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<n;i++) { x=e[i].u,y=e[i].v; int r1=find(x),r2=find(y); if(r1==r2) continue; if(!opt[r1] && !opt[r2]) fa[y]=x,ans-=e[i].w; if(opt[r1] && !opt[r2]) opt[r2]=1,fa[r2]=r1,ans-=e[i].w; if(opt[r2] && !opt[r1]) opt[r1]=1,fa[r1]=r2,ans-=e[i].w; } printf("%lld\n",ans); return 0; }
/* 注意:此代码wa了一个点...... I don't know why... f[u][0]表示到了u这个点,子树已经合法,当前点所在集合没有敌人的最小代价 f[u][1]表示到了u这个点,子树已经合法,当前点所在集合有敌人的最小代价 u有无敌人分情况转移 u号点没有敌人,那么f[u][1]就意味着要从子树中选一个能使花费最小的f[v][1], 其他子树都切断或者从f[v][0]转移 这时候f[u][0]+=min(f[v][0], f[v][1]+e[i].w)(v是儿子节点) u有敌人时初值f[u][0]=inf f[u][1]+=min(f[v][0],f[v][1]+w[i]) 只能和有敌人的儿子节点断绝联系 */ #include<bits/stdc++.h> #define N 100001 #define ll long long #define inf 0x3f3f3f3f using namespace std; ll n,m,cnt; ll ans,f[N][2]; ll head[N<<1],opt[N],have[N]; struct edge{ ll u,v,w,nxt; }e[N<<1]; inline ll read() { ll 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; } inline void add(int u,int v,ll w) { e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt; } ll min_(ll a,ll b){return a<b?a:b;} void dp(int u,int fa) { f[u][0]=f[u][1]=0; have[u]=opt[u];//have判断子树内有无敌人 if(opt[u]) f[u][0]=inf; ll val=0; for(int i=head[u];i;i=e[i].nxt) { int v=e[i].v; if(v==fa) continue; dp(v,u);have[u]|=have[v]; val+=min_(f[v][0],f[v][1]+(ll)e[i].w);//dp前的预处理,求以当前点为根子树的答案。 } if(!opt[u]) { f[u][1]=val;//因为f[u][1]要求u必须在有敌人的联通块,而val不一定,所以要枚举选那棵子树。 for(int i=head[u];i;i=e[i].nxt) { int v=e[i].v; if(v==fa) continue; if(!have[v]) continue;//子树中无敌人,答案一定是0,不需要管。 f[u][1]=min(f[u][1],val-min_(f[v][0],f[v][1]+(ll)e[i].w)+f[v][1]); //选择和哪一棵子树相连 if(opt[v]) f[u][0]+=(ll)e[i].w+f[v][1]; else f[u][0]+=min(f[v][0],f[v][1]+(ll)e[i].w); } } else { for(int i=head[u];i;i=e[i].nxt) { int v=e[i].v; if(v==fa) continue; if(!have[v]) continue; if(opt[v]) f[u][1]+=f[v][1]+(ll)e[i].w; else f[u][1]+=min_(f[v][0],f[v][1]+(ll)e[i].w); } } } int main() { freopen("ly.in","r",stdin); int x,y;ll z; n=read();m=read(); for(int i=1;i<=m;i++) x=read()+1,opt[x]=1; for(int i=1;i<n;i++) { x=read()+1;y=read()+1;z=read(); add(x,y,z);add(y,x,z); } dp(1,-1); printf("%lld\n",min_(f[1][0],f[1][1])); return 0; }
折花枝,恨花枝,准拟花开人共卮,开时人去时。
怕相思,已相思,轮到相思没处辞,眉间露一丝。