也许看到“最大权闭合子图”这个词许多人就懵逼了。
一开始我也是这样,那年我在GDKOI2016上遇见了宝藏。
一个子图(点集), 如果它的所有的出边都在这个子图当中,那么它就是闭合子图。
点权和最大的闭合子图就是最大闭合子图。
简单说就是有一些点,每个点有一些点权(正负都有),要选一个点,就必须要选它所连向的点。
有可能连成一条链,像这样:x->y->z->…
求合法的点集的最大的点权和。
设s为源点,t为汇点。
使s连向所有的正权点(非负权点),边权为点权。
使所有非负权点(负权点)连向t,边权为点权的绝对值。
若需要选y才能选x,连一条由x到y的边,边权是∞。
最大点权和=正权点和-最小割
我们知道一个割会把图分成两个部分,一部分是S能走到的,另一部分是能走到T的。
我们设这两个集合为A,B。
A0,B0分别表示A,B中原来点权是负数的点集, A1,B1分别表示A,B中点权原来是正数的点集。
由于中间的边是max的,割的一定是和S,T相连的边,这个叫简单割。
所以割的大小 = |A0| +B1
绝对值表示把负的变成了正的。
A,B一定是最大权闭合子图
A的点权和 = A1 - |A0|
A的点权和 + 割的大小 = A1 + B1 = 全图中的正权点和
这是一个定值, 要使A的点权和最大, 就要使割最小,就是最小割,
至此得证。
胡伯涛《最小割模型在信息学竞赛中的应用》
裸题:GDKOI2006 破解密文,读者可以去查一下。
Code:
#include
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
const int maxn=505;
struct node{
int to,next,flow;
}e[maxn*maxn*2];
int tot=1,ans,n,m,s,t,p[maxn],cur[maxn],co[maxn],d[maxn],final[maxn];
void link(int x,int y,int z){
e[++tot].to=y,e[tot].next=final[x],e[tot].flow=z,final[x]=tot;
e[++tot].to=x,e[tot].next=final[y],e[tot].flow=0,final[y]=tot;
}
int dg(int x,int flow){
if(x==t) return flow;
int use=0;
for(int i=cur[x];i;i=e[i].next){
cur[x]=i;
if(e[i].flow>0 && d[e[i].to]+1==d[x]){
int c=dg(e[i].to,min(flow-use,e[i].flow));
use+=c,e[i].flow-=c,e[i^1].flow+=c;
if(flow==use) return use;
}
}
cur[x]=final[x];
if(!(--co[d[x]])) d[0]=t;
++co[++d[x]];
return use;
}
int main(){
scanf("%d",&n);
s=n+1,t=n+2;
fo(i,1,n){
scanf("%d %d",&p[i],&m);
if(p[i]>0)
ans+=p[i],link(s,i,p[i]); else link(i,t,-p[i]);
fo(j,1,m){
int y; scanf("%d",&y);
link(i,y,1<<30);
}
}
co[0]=t;
for(;d[0]s,1<<30);
printf("%d",ans);
}