传送门
太空飞行计划
题意:m个实验分别依赖若干仪器,实验有收益,仪器需支出。被不同实验所依赖的相同仪器可在被买下后重复使用。求选择若干实验的最大获利(收益-支出)。
I think
先说做法。实验和仪器分设为x,y集合的点。增设源汇点,源点向所有x集合点连边权为实验收益的边,y集合点向汇点连边权为仪器费用(>=0)的边,x集合点向y集合中实验对应仪器点连边,权值设为Inf。最终答案即是总收益(所有实验纯收益)-最小割。
把整张图分为S集与T集,S集合中是选择的实验及仪器,T集合中是不选择的实验与仪器。最小割不可能割容量为Inf的边,因此相应的实验与其需要的仪器最终必然在同一个集合,割边一定直接与源/汇点相连,被割掉的边所连接的非源汇点是不选择的实验或选择的仪器。
最后我们引入最大权闭合子图的概念:图中所有节点所连边指向的点仍在图中,且是所有满足该条件子图中点权和最大的图。我们发现答案实验与其所需仪器构成的图必然是最大权闭合子图。
于是得到求解最大权闭合子图–>求解最小割–>求解最大流问题,最大权值
ans=∑val−cut(最小割)
Code
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int sm = 105,sn = 2505;
const int Inf = 0x3f3f3f3f;
int N,M,tot=1,sum,S,T,Flow;
int to[sn],hd[sm],nxt[sn],c[sn];
int lev[sm],cur[sm];
int a[sm],b[sm][sm],ct[sm];
bool ex[sm];
int Min(int x,int y) { return x<y?x:y; }
char ch;
void read(int &x) {
x=0;ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Add(int u,int v,int w) {
to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot,c[tot]=w;
}
bool Bfs(int S,int T) {
memset(lev,0,sizeof(lev));
queue<int>q;
q.push(S),lev[S]=1;
while(!q.empty()) {
int t=q.front();q.pop();
for(int i=hd[t];i;i=nxt[i])
if(!lev[to[i]]&&c[i]) {
lev[to[i]]=lev[t]+1;
if(to[i]==T) return 1;
q.push(to[i]);
}
}
return lev[T];
}
int Dfs(int x,int mx) {
if(!mx||x==T) return mx;
int f=0;
for(int i=cur[x]?cur[x]:hd[x];i;i=nxt[i]) {
cur[x]=i;
if(c[i]&&lev[to[i]]==lev[x]+1) {
if(f=Dfs(to[i],Min(mx,c[i])))
return c[i]-=f,c[i^1]+=f,f;
}
}
return 0;
}
void Dinic() {
int f=0;
while(Bfs(S,T)) {
memset(cur,0,sizeof(cur));
while(f=Dfs(S,Inf))Flow+=f;
}
}
void DFS(int x,int b) {
ex[x]=b;
for(int i=hd[x];i;i=nxt[i])
if(c[i]&&!ex[to[i]])
DFS(to[i],b+1);
}
int main() {
read(M),read(N);
for(int i=1;i<=M;++i) {
read(a[i]);sum+=a[i];
while(ch!='\n') {
read(b[i][++ct[i]]); b[i][ct[i]]+=M;
Add(i,b[i][ct[i]],Inf);
Add(b[i][ct[i]],i,0);
}
}
for(int i=1;i<=N;++i) read(a[i+M]);
S=N+M+1; T=S+1;
for(int i=1;i<=M;++i)
Add(S,i,a[i]),Add(i,S,0);
for(int i=M+1;i<=M+N;++i)
Add(i,T,a[i]),Add(T,i,0);
Dinic();
DFS(S,1);
for(int i=1;i<=M;++i)
if(ex[i]) printf("%d ",i); putchar(10);
for(int i=1;i<=N;++i)
if(ex[M+i]) printf("%d ",i); putchar(10);
printf("%d\n",sum-Flow);
return 0;
}