一张具有流量的图上,源点是s,汇点是t

割的定义:去掉一些边,使得s->t不存在任何流量

最小割:去掉代价最小的一些边,使得s->t不存在任何流量

定理:从s跑到t的最大流就是s到t的最小割,最大流=最小割

感性的理解: 最 大 流 包 含 所 有 s 流 向 t 的 可 能 最大流包含所有s流向t的可能 最大流包含所有s流向t的可能

所以割至少要包含这些流向t的边,就可以使s不可达t

有 一 类 问 题 叫 做 最 大 权 闭 合 图 \color{Red}有一类问题叫做最大权闭合图 有一类问题叫做最大权闭合图

你暂时不需要知道这是什么,先看下面这道例题

​P2762 太空飞行计划问题(最大全闭合图模板)​

对 于 这 道 题 , 先 说 做 法 , 再 来 证 明 . 对于这道题,先说做法,再来证明. 对于这道题,先说做法,再来证明.

构 图 \color{Red}构图 构图

Ⅰ . 源 点 s 向 实 验 连 一 条 流 量 为 实 验 利 润 的 边 Ⅰ.源点s向实验连一条流量为实验利润的边 Ⅰ.源点s向实验连一条流量为实验利润的边

Ⅱ . 每 个 实 验 向 对 应 的 器 材 连 一 条 容 量 无 穷 的 边 Ⅱ.每个实验向对应的器材连一条容量无穷的边 Ⅱ.每个实验向对应的器材连一条容量无穷的边

Ⅲ . 器 材 向 汇 点 t 连 一 条 流 量 为 器 材 代 价 的 边 Ⅲ.器材向汇点t连一条流量为器材代价的边 Ⅲ.器材向汇点t连一条流量为器材代价的边

结论

从 s s s向 t t t跑最大流以求出 s s s到 t t t的最小割

用所有实验的理论和-最小割就是答案.

证明

回忆一下题目

题目意思是选择了某个实验,就必须选择对应的一些器材,求最大收益

那 我 们 求 最 小 割 时 就 是 让 s 不 能 到 t 那我们求最小割时就是让s不能到t 那我们求最小割时就是让s不能到t

假 如 最 小 割 选 择 割 掉 s 到 实 验 u 的 边 , 那 么 最 小 割 的 代 价 加 上 u 实 验 利 润 \color{Red}假如最小割选择割掉s到实验u的边,那么最小割的代价加上u实验利润 假如最小割选择割掉s到实验u的边,那么最小割的代价加上u实验利润

最后用利润总和(包含u的利润)减去最小割(包含u的利润),相当于没选这个实验

假 如 最 小 割 没 有 割 掉 s 到 实 验 u 的 边 , 那 么 设 u 对 应 的 器 材 是 v \color{Red}假如最小割没有割掉s到实验u的边,那么设u对应的器材是v 假如最小割没有割掉s到实验u的边,那么设u对应的器材是v

v 到 t 的 边 一 定 会 被 割 掉 , 假 如 没 有 被 割 掉 , s 到 t 就 联 通 了 , 就 不 是 一 个 割 v到t的边一定会被割掉,假如没有被割掉,s到t就联通了,就不是一个割 v到t的边一定会被割掉,假如没有被割掉,s到t就联通了,就不是一个割

割 掉 v 到 t 的 边 , 最 小 割 的 代 价 加 上 v 器 材 的 花 费 割掉v到t的边,最小割的代价加上v器材的花费 割掉v到t的边,最小割的代价加上v器材的花费

最 后 用 利 润 总 和 ( 包 含 u 的 利 润 ) 减 去 最 小 割 ( 包 含 u 对 应 器 材 花 费 ) , 相 当 于 选 u 实 验 且 选 u 实 验 需 要 的 器 材 最后用利润总和(包含u的利润)减去最小割(包含u对应器材花费),相当于选u实验且选u实验需要的器材 最后用利润总和(包含u的利润)减去最小割(包含u对应器材花费),相当于选u实验且选u实验需要的器材

综上所诉,求最小割就考虑到了全部的情况

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int inf=1e9;
int read() {
static char c;
static int x;
if(c == '\n') return c = 0, -1;
while(!isdigit(c = getchar()));
x = c ^ 48;
while(isdigit(c = getchar()))
x = x * 10 + (c ^ 48);
return x;
}
int s,t,n,m,maxflow,sumn;
struct p{
int to,flow,nxt;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int flow){
d[++cnt]=(p){v,flow,head[u] },head[u]=cnt;
}
int dis[maxn];
bool bfs()
{
queue<int>q;
for(int i=0;i<=2*t;i++) dis[i]=0;
dis[s]=1;
q.push(s);
while( !q.empty() )
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=d[i].nxt )
{
int v=d[i].to;
if( d[i].flow&&!dis[v] )
{
dis[v]=dis[u]+1;
if( v==t ) return true;
q.push( v );
}
}
}
return false;
}
int dinic(int u,int flow)
{
if( u==t ) return flow;
int res=flow;
for(int i=head[u];i;i=d[i].nxt)
{
int v=d[i].to;
if( dis[v]==dis[u]+1&&d[i].flow )
{
int temp=dinic(v,min(res,d[i].flow) );
if( temp==0 ) dis[v]=0;
res-=temp;
d[i].flow-=temp;
d[i^1].flow+=temp;
}
if( res==0 ) break;
}
return flow-res;
}
int main()
{
cin >> m >> n;
s=n+m+1,t=s+1;
for(int i=1,x;i<=m;i++ )
{
scanf("%d",&x);
add( s,i,x );
add( i,s,0 );
sumn+=x;
while(~(x = read())) add(i, m + x, inf),add(m+x,i,0);
}
for(int i=1,x; i<=n; i++)
{
scanf("%d",&x);
add(i+m,t,x); add(t,i+m,0);
}
while( bfs() )
maxflow+=dinic( s,inf );
for(int i=1;i<=m;i++)
if( dis[i] ) printf("%d ",i);
puts("");
for(int i=1;i<=n;i++)
if( dis[i+m] ) printf("%d ",i);
printf("\n%d",sumn-maxflow);
}