这题是最大权闭合子图的模板题。
闭合子图:所有点的后继都在图内。
模型:
有一些正权点,负权点。
每个正权点有一些后继。
你需要求一个最大权闭合子图。
做法: a n s = ans= ans=正权点值和 − - −最小割
证明:
给二分图加入源点,汇点。
正权点与S的连边的容量为其权值。
负权点与T的连边的容量为其权值的相反数。
正权点和其后继的连边的容量为
i
n
f
inf
inf.
跑完网络流后,那么残余网络上与S相连的部分的点权和即为答案。
解释:由于中间的边为 i n f inf inf,所以一定不会被割掉,跟S相连的部分的 ∣ 负 权 点 的 权 值 和 ∣ ≤ 正 权 点 的 权 值 和 |负权点的权值和|\le 正权点的权值和 ∣负权点的权值和∣≤正权点的权值和,也就是获得收益。(割边都对应负权点)
而和T相连的部分就是不会得到收益。(割边都对应正权点)。
有一个更方便的做法是: a n s = ans= ans=正权点值和 − - −最小割。就是后一部分不计入答案。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,inf=2e9;
int n,m,d[N],q[N],ans,st,ed;
struct edge{int y,next,c;}a[N*N]; int len=1,last[N],cur[N],b[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}
bool bfs() {
memset(d,0,(ed+1)<<2);
int l,r; q[l=r=1]=st; d[st]=1;
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next) {
y=a[k].y;
if(!d[y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
if(y==ed) return 1;
}
}
}
return 0;
}
int dfs(int x,int f) {
if(x==ed)return f;
int s=0,t;
for(int k=cur[x],y,z;k&&s<f;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(d[y]==d[x]+1&&z>0) {
s+=(t=dfs(y,min(f-s,z)));
a[k].c-=t; a[k^1].c+=t; cur[x]=k;
}
}
if(!s) d[x]=0;
return s;
}
int main() {
scanf("%d %d",&n,&m); m+=n; st=0; ed=m+1;
for(int i=1,x;i<=n;i++) {
scanf("%d",&x); add(st,i,x); ans+=x;
while(getchar()==' ') scanf("%d",&x),add(i,x+n,inf);
}
for(int i=n+1,x;i<=m;i++) scanf("%d",&x),add(i,ed,x);
while(bfs()) memcpy(cur,last,(ed+1)<<2),ans-=dfs(st,inf);
for(int i=1;i<=n;i++) if(d[i]) b[++b[0]]=i;
for(int i=1;i<=b[0];i++) printf("%d%c",b[i]," \n"[i==b[0]]);
b[0]=0;
for(int i=n+1;i<=m;i++) if(d[i]) b[++b[0]]=i-n;
for(int i=1;i<=b[0];i++) printf("%d%c",b[i]," \n"[i==b[0]]);
printf("%d\n",ans); return 0;
}