从零开始的网络流24题
我不是柳橙汁
2019-10-02 09:34:18
很早之前学网络流的时候,就有这么一个想法,要做完网络流24题
后来因为退役就鸽了
现在回来打acm,又重新爬回来填坑
### 1 飞行员配对方案问题
[luogu P2756](https://www.luogu.org/problem/P2756)
一个非常正常的二分图,要求输出匹配
做法也很简单,看反向边是否出现增广路,就表示使用了这个匹配
```cpp
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define MAXN (2147000000)
struct edge{
long long to,val,next;
}e[100010];
long long m,n,ans=0,len=1;
long long head[210],d[210],gap[210];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
}
long long sap(long long u,long long flow){
if(u==m+2) return flow;
long long res=flow;
travel(i,u,v) if(e[i].val&&d[u]==d[v]+1){
long long t=sap(v,fmin(res,e[i].val));
e[i].val-=t;
e[i^1].val+=t;
res-=t;
if(!res) return flow;
}
if(--gap[d[u]]==0) d[m+1]=m+2;
gap[++d[u]]++;
return flow-res;
}
void inpt(){
n=v_in(),m=v_in();
fr(i,1,n) addedge(m+1,i,1),addedge(i,m+1,0);
fr(i,n+1,m) addedge(i,m+2,1),addedge(m+2,i,0);
//printf("%lld\n",len);
while(true){
long long x=v_in(),y=v_in();
if(x==-1&&y==-1) break;
addedge(x,y,1);
addedge(y,x,0);
}
}
void work(){
gap[0]=m+2;
while(d[m+1]<m+2) ans+=sap(m+1,MAXN);
}
void outp(){
if(ans==0){
printf("No Solution!\n");
return;
}
printf("%lld\n",ans);
for(register long long i=2+m+m;i<=len;i+=2) if(e[i^1].val!=0) printf("%lld %lld\n",e[i^1].to,e[i].to);
}
int main(){
inpt();
work();
outp();
return 0;
}
```
### 2 方格取数问题
[luogu P2774](https://www.luogu.org/problem/P2774)
比起选数,不如先选上所有数,然后再取消其中一些数的选择
我们需要相邻的点不能选择,所以联想到割,于是:
1.将方格中的点按黑白染色区分,建立源点汇点;
2.源点连向黑点,边权为黑点点权;
3.黑点连向白点,边权为无限大;
4.白点连向汇点,边权为白点点权;
5.求最小割,也就是最大流。
这是最小割的经典题,最大点权覆盖问题(套路++)
```cpp
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define MAXN (2147000000)
#define poi(a,b) (((a)-1)*n+(b))
#define fmin(a,b) ((a)<(b)?(a):(b))
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
struct edge{
long long to,val,next;
}e[400010];
long long m,n,st,ed,len=1,ans=0,tot=0;
long long head[10010],gap[10010],d[10010],step[10][5];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
}
long long sap(long long u,long long flow){
if(u==ed) return flow;
long long res=flow;
travel(i,u,v) if(e[i].val&&d[u]==d[v]+1){
long long t=sap(v,fmin(res,e[i].val));
e[i].val-=t;
e[i^1].val+=t;
res-=t;
if(!res) return flow;
}
if(--gap[d[u]]<=0) d[st]=m*n+2;
gap[++d[u]]++;
return flow-res;
}
void inpt(){
m=v_in(),n=v_in();
st=m*n+1,ed=st+1;
step[1][1]=1; step[1][2]=0;
step[2][1]=-1; step[2][2]=0;
step[3][1]=0; step[3][2]=-1;
step[4][1]=0; step[4][2]=1;
fr(i,1,m) fr(j,1,n){
long long w=v_in();
tot+=w;
if(((i^j)&1)==0){
addedge(st,poi(i,j),w);
addedge(poi(i,j),st,0);
fr(k,1,4){
long long x=i+step[k][1],y=j+step[k][2];
if(x<=m&&x>=1&&y<=n&&y>=1){
addedge(poi(i,j),poi(x,y),MAXN);
addedge(poi(x,y),poi(i,j),0);
}
}
}
else{
addedge(poi(i,j),ed,w);
addedge(ed,poi(i,j),0);
}
}
}
void work(){
gap[0]=m*n+2;
long long x=MAXN+MAXN+MAXN;
while(d[st]<m*n+2) ans+=sap(st,x);
printf("%lld\n",tot-ans);
}
int main(){
inpt();
work();
return 0;
}
```
### 3 太空飞行计划问题
[luogu P2762](https://www.luogu.org/problem/P2762)
一道最小割的套路题,最大权闭合子图
这里贴一个大佬的[链接](https://www.cnblogs.com/dilthey/p/7565206.html)
本身应该是一个板子,但是要求记录一组合适方案
我们考虑枚举每一条器材与汇点的边,如果删除后的最大流与本身的最大流的差值为这条边的权值,说明这条边满流,需要使用
```cpp
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define re register
#define f(i,a,b) for(re int i=(a);i<=(b);i=-(~i))
using namespace std;
const int MAX=2147000000,N=2005,M=2005;
struct node
{
int u,v,w,next;
}p[2000005],e[2000005];
int head[N],d[N],gap[2000005],val[N],cost[N];
vector<int> req[N];
char c[10010];
bool bo[N],have[N];
int tot=1,s,t,ans,n,m,sum,ulen,flow;
inline void read(int &s)
{
char c=getchar();int f=1;s=0;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){s=(s<<3)+(s<<1)+(c^48);c=getchar();}
s*=f;
}
inline void add(int u,int v,int w)
{
p[++tot].u=u,p[tot].w=w,p[tot].v=v,p[tot].next=head[u];
e[tot]=p[tot];
head[u]=tot;
}
inline void prework()
{
memset(d,0,sizeof(d));
memset(gap,0,sizeof(gap));
f(i,1,tot)p[i]=e[i];
}
int sap(int u,int fl)
{
if(u==t)return fl;
int res=fl;
for(re int i=head[u];i;i=p[i].next)
{
int v=p[i].v;
if(!bo[v]&&p[i].w&&d[u]==d[v]+1)
{
int k=sap(v,min(res,p[i].w));
p[i].w-=k;p[i^1].w+=k;res-=k;
if(!res)return fl;
}
}
gap[d[u]]--;
if(!gap[d[u]])d[u]=t+1;
d[u]++;
gap[d[u]]++;
return fl-res;
}
inline void build()
{
f(i,1,n)
{
add(s,i,val[i]),add(i,s,0);
f(j,0,req[i].size()-1)
add(i,n+req[i][j],MAX),add(n+req[i][j],i,0);
}
f(i,1,m)add(n+i,t,cost[i]),add(t,n+i,0);
}
inline void work(){
gap[0]=t+1;
while(d[s]<t+1) flow+=sap(s,MAX);
}
inline void getequip(){
int fl=0;
f(i,1,m){
bo[i+n]=1,fl=0;
prework();
gap[0]=t+1;
while(d[s]<t+1) fl+=sap(s,MAX);
if(flow-fl==cost[i]) have[i]=1;
bo[i+n]=0;
}
}
inline void getexper()
{
f(i,1,n)
{
bool fl=0;
f(j,0,req[i].size()-1)
if(!have[req[i][j]])fl=1;
if(!fl)printf("%d ",i);
}
}
int main()
{
int x,y,z,num;
scanf("%d%d",&n,&m);
f(i,1,n)
{
scanf("%d",&val[i]);
sum+=val[i];
memset(c,0,sizeof(c));
cin.getline(c,10000);
ulen=0;
while (sscanf(c+ulen,"%d",&num)==1)
{
req[i].push_back(num);
if (num==0) ulen++;
else while (num)
{
num/=10;
ulen++;
}
ulen++;
}
}
f(i,1,m)scanf("%d",&cost[i]);
s=0,t=n+m+1;
build();
work();
getequip();
getexper();
puts("");
f(i,1,m)if(have[i])printf("%d ",i);
puts("");
ans=sum-flow;
printf("%d\n",ans);
return 0;
}
```
### 4 负载平衡问题
[luogu P4016](https://www.luogu.org/problem/P4016)
一个费用流的简单建图问题
最开始在做这道题的时候,考虑的是最大流没有考虑费用,导致建图建对了反而一直wa
我们可以建这样的图:
1.首先将每个地方拆成两个点,建立源点汇点
2.计算每个地方与目标的差距,如果需要引出则与源点相连,如果需要引入则与汇点相连,流量为差距的绝对值,费用为0
3.既然需要分配,所以我们考虑将相邻的地方,从这个地方的第一个点与相邻地点的两个点都相连,流量无限,费用为1,表示可以调配
```cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define fmin(a,b) ((a)<(b)?(a):(b))
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define MAXN (2147000000)
struct edge{
long long to,val,cost,next;
}e[10010];
queue<long long>q;
long long a[510],head[510],pre[510],last[510],dis[510],flow[510];
long long n,st,ed,sum=0,len=1,mincost=0;
bool vis[510];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z,long long w){
e[++len].to=y;
e[len].val=z;
e[len].cost=w;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].cost=-w;
e[len].next=head[y];
head[y]=len;
}
bool SPFA(long long s,long long t){
memset(dis,0x7f7f7f7f,sizeof(dis));
memset(flow,0x7f7f7f7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s),vis[s]=1,dis[s]=0,pre[t]=-1;
while(!q.empty()){
long long u=q.front(); q.pop();
vis[u]=0;
travel(i,u,v) if(e[i].val&&dis[v]>dis[u]+e[i].cost){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=fmin(flow[u],e[i].val);
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
return pre[t]!=-1;
}
void inpt(){
n=v_in();
st=n+n+1,ed=st+1;
fr(i,1,n) a[i]=v_in(),sum+=a[i];
sum/=n;
fr(i,1,n){
a[i]-=sum;
if(a[i]>0) addedge(st,i,a[i],0);
if(a[i]<0) addedge(n+i,ed,-a[i],0);
}
fr(i,2,n-1){
addedge(i,n+i-1,MAXN,1);
addedge(i,i-1,MAXN,1);
addedge(i,n+i+1,MAXN,1);
addedge(i,i+1,MAXN,1);
}
addedge(1,n+2,MAXN,1);
addedge(1,2,MAXN,1);
addedge(1,n+n,MAXN,1);
addedge(1,n,MAXN,1);
addedge(n,n+1,MAXN,1);
addedge(n,1,MAXN,1);
addedge(n,n+n-1,MAXN,1);
addedge(n,n-1,MAXN,1);
}
void work(){
while(SPFA(st,ed)){
long long now=ed;
mincost+=dis[ed]*flow[ed];
while(now!=st){
e[last[now]].val-=flow[ed];
e[last[now]^1].val+=flow[ed];
now=pre[now];
}
}
printf("%lld\n",mincost);
}
int main(){
inpt();
work();
return 0;
}
```
### 5 餐巾计划问题
[luogu P1251](https://www.luogu.org/problem/P1251)
这道题主要是考察建图思想。
首先我们需要考虑将一天拆成早上和晚上
其中晚上的餐巾是可以过夜的,但是并不能在第二天使用,于是前一天晚上向后一天晚上连一条流量无限,费用为0的边
如果送到快洗部,即可以从第i天晚上向第i+m天早上连一条流量无限,费用为f的边
同理,送到慢洗部也应该连边
如果是新购买,可以直接从源点向每天早晨连一条流量无限,费用为p的边
最后源点向每天晚上连一条流量为r[i],费用为0的边,表示每天晚上获得r[i]条脏餐巾
每天早上向汇点连一条流量为r[i],费用为0的边,表示每天早上需要消耗r[i]条新餐巾
最后跑最小费用最大流即可
```cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define MAXN (214700000)
struct edge{
long long to,flow,val,next;
}e[100010];
queue<long long>q;
long long N,p,m,f,n,s,len=1,st,ed,mincost=0;
long long r[20010],head[20010],pre[20010],last[20010],flow[20010],dis[20010];
bool vis[20010];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long u,long long v,long long w,long long z){
e[++len].to=v;
e[len].flow=w;
e[len].val=z;
e[len].next=head[u];
head[u]=len;
e[++len].to=u;
e[len].flow=0;
e[len].val=-z;
e[len].next=head[v];
head[v]=len;
}
bool SPFA(long long s,long long t){
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s),vis[s]=1,dis[s]=0,pre[t]=-1;
while(!q.empty()){
long long u=q.front(); q.pop();
vis[u]=0;
travel(i,u,v) if(e[i].flow&&dis[v]>dis[u]+e[i].val){
dis[v]=dis[u]+e[i].val;
pre[v]=u;
last[v]=i;
flow[v]=fmin(flow[u],e[i].flow);
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
return pre[t]!=-1;
}
void inpt(){
N=v_in();
fr(i,1,N) r[i]=v_in();
p=v_in(),m=v_in(),f=v_in(),n=v_in(),s=v_in();
//build
st=N*2+1,ed=st+1;
fr(i,1,N){
addedge(st,i+N,r[i],0);
addedge(i,ed,r[i],0);
}
fr(i,1,N){
if(i+1<=N) addedge(i+N,i+N+1,MAXN,0);
if(i+m<=N) addedge(i+N,i+m,MAXN,f);
if(i+n<=N) addedge(i+N,i+n,MAXN,s);
addedge(st,i,MAXN,p);
}
}
void work(){
while(SPFA(st,ed)){
long long now=ed;
mincost+=flow[ed]*dis[ed];
while(now!=st){
e[last[now]].flow-=flow[ed];
e[last[now]^1].flow+=flow[ed];
now=pre[now];
}
}
printf("%lld\n",mincost);
}
int main(){
inpt();
work();
return 0;
}
```
### 6.试题库问题
[luogu P2763](https://www.luogu.org/problem/P2763)
很简单的一道二分图问题
只不过类型流向汇点时,边权变成题中所给的要求数量即可
输出时只需要判断试题连向类型的边有没有增广路即可
```cpp
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define MAXN (100000000)
struct edge{
long long to,val,next;
}e[100010];
long long n,m,st,ed,len=1,sum=0;
long long head[2010],d[2010],gap[2010];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].next=head[y];
head[y]=len;
}
long long sap(long long u,long long flow){
if(u==ed) return flow;
long long res=flow,t;
travel(i,u,v) if(e[i].val&&d[u]==d[v]+1){
t=sap(v,fmin(e[i].val,res));
e[i].val-=t;
e[i^1].val+=t;
res-=t;
if(!res) return flow;
}
if(--gap[d[u]]<=0) d[st]=m+n+2;
gap[++d[u]]++;
return flow-res;
}
void inpt(){
n=v_in(),m=v_in();
st=m+n+1,ed=st+1;
fr(i,1,n){
long long x=v_in();
sum+=x;
addedge(m+i,ed,x);
}
fr(i,1,m){
addedge(st,i,1);
long long opt=v_in();
fr(j,1,opt){
long long x=v_in();
addedge(i,m+x,1);
}
}
}
void work(){
long long ans=0;
gap[0]=m+n+2;
while(d[st]<m+n+2) ans+=sap(st,MAXN);
if(ans!=sum){
printf("No Solution!\n");
return;
}
//travel(i,ed,v) printf("%lld %lld\n",v,e[i].val);
fr(u,m+1,m+n){
printf("%lld: ",u-m);
travel(i,u,v) if(v<=m&&v>=1&&e[i].val!=0) printf("%lld ",v);
printf("\n");
}
}
int main(){
inpt();
work();
return 0;
}
```
### 7 最小路径覆盖问题
[luogu P2764](https://www.luogu.org/problem/P2764)
这是一道板子题
ans就是用n减去二分图匹配的最大值
我们假设本身由n条路径,每条路径覆盖图中不重复的每一个点
我们可以二分图来合并这些路径
如果两个在图中相连,我们就可以将这两条路径合并
最后得到的最大流,就是能够合并路径的最大数量,于是我们用总路径数n减去最大流,得到的就是最小路径覆盖
关于记录路径
我们可以考虑每一个点,如果他没有被流入,说明他是起点
然后从这个点开始依次寻找它流向的点,即合并的路径,依次输出即可
```cpp
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define MAXN (100000000)
struct edge{
long long to,val,next;
}e[100010];
long long n,m,st,ed,len=1;
long long head[2010],d[2010],gap[2010];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].next=head[y];
head[y]=len;
}
long long sap(long long u,long long flow){
if(u==ed) return flow;
long long res=flow,t;
travel(i,u,v) if(e[i].val&&d[u]==d[v]+1){
t=sap(v,fmin(e[i].val,res));
e[i].val-=t;
e[i^1].val+=t;
res-=t;
if(!res) return flow;
}
if(--gap[d[u]]<=0) d[st]=m+n+2;
gap[++d[u]]++;
return flow-res;
}
void inpt(){
n=v_in(),m=v_in();
st=n+n+1,ed=st+1;
fr(i,1,n) addedge(st,i,1),addedge(n+i,ed,1);
fr(i,1,m){
long long u=v_in(),v=v_in();
addedge(u,n+v,1);
}
}
void work(){
long long ans=0;
gap[0]=m+n+2;
while(d[st]<m+n+2) ans+=sap(st,MAXN);
fr(u,n+1,n+n){
bool mark=false;
travel(i,u,v) if(v<=n&&v>=1&&e[i].val){
mark=true;
break;
}
if(!mark){
long long now=u-n;
while(1){
printf("%lld ",now);
long long nxt=0;
travel(i,now,v){
//printf(" *%lld ",v);
if(v>=n+1&&v<=n+n&&e[i].val==0){
nxt=v-n;
break;
}
}
if(!nxt) break;
now=nxt;
}
printf("\n");
}
}
printf("%lld\n",n-ans);
}
int main(){
inpt();
work();
return 0;
}
```
### 8 CTSC 1999 家园
[luogu P2754](https://www.luogu.org/problem/P2754)
考虑有没有解时,可以用并查集来判断地球和月球是否处于同一并查集内,若不在则无解,或者直接超过一定范围时,若还未结束则说明无解
我们考虑对时间分层
先建立源点汇点
每天对每个太空站(包括地球月球)建立一个点
源点朝第0天的地球连一条流量为$k$的边,表示需要运输$k$个人
除了月球以外的地点前一天朝后一天连一条无限流量的边,表示可以停留
每一天的月球朝汇点连边,表示每一天到的人都算作到达
当然可以直接让第0天的月球朝汇点连边,之后的天的月球朝前连边
飞船每天所在的位置朝下一天所在的位置连一条流量为$h_p[i]$的边,表示飞船运输人
如此建图跑一遍最大流就可以了
```cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define inf (1000000000)
#define st 1
#define ed 2
#define poi(a,b) (2+(b)*(n+2)+(a))
struct edge{
long long to,val,next;
}e[200010];
queue<long long>q;
long long n,m,k,ans=0,len=1;
long long head[10010],dep[10010],cur[10010],h[10010],t[10010],d[10010][50];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].next=head[y];
head[y]=len;
//printf("*%lld %lld %lld\n",x,y,z);
}
bool bfs(long long s,long long t){
memset(dep,0x7f,sizeof(dep));
while(!q.empty()) q.pop();
fr(i,1,n) cur[i]=head[i];
dep[s]=0;
q.push(s);
while(!q.empty()){
long long u=q.front();
q.pop();
travel(i,u,v){
if(dep[v]>inf&&e[i].val){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t]<inf) return true;
return false;
}
long long dfs(long long u,long long t,long long limit){
if(limit==0||u==t) return limit;
long long flow=0,f;
travel(i,u,v){
cur[u]=i;
if(dep[v]==dep[u]+1&&(f=dfs(v,t,fmin(limit,e[i].val)))){
flow+=f;
limit-=f;
e[i].val-=f;
e[i^1].val+=f;
if(!limit) break;
}
}
return flow;
}
void inpt(){
n=v_in(),m=v_in(),k=v_in();
fr(i,1,m){
h[i]=v_in(),t[i]=v_in();
fr(j,0,t[i]-1){
d[i][j]=v_in();
if(d[i][j]==0) d[i][j]=n+1;
if(d[i][j]==-1) d[i][j]=n+2;
}
}
}
void build_work(){
addedge(st,poi(n+1,0),k);
addedge(poi(n+2,0),ed,k);
for(register long long T=1;T<=500;T++){
fr(i,1,n+1) addedge(poi(i,T-1),poi(i,T),inf);
addedge(poi(n+2,T),poi(n+2,T-1),inf);
fr(i,1,m) addedge(poi(d[i][(T-1)%t[i]],T-1),poi(d[i][T%t[i]],T),h[i]);
while(bfs(st,ed)) ans+=dfs(st,ed,inf);
//printf("%lld %lld\n",T,ans);
if(ans==k){
printf("%lld\n",T);
return;
}
}
printf("0\n");
}
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
inpt();
build_work();
return 0;
}
```
### 9 魔术球问题
[luogu P2765](https://www.luogu.org/problem/P2765)
我觉得这个题很有意思
把每个柱子考虑成一条路径,两球值之和为平方数连边,就转化成一道最小路径点覆盖的问题
然后按球大小顺序枚举建图即可
当找到一个数,正好需要n+1个柱子时,此时这个数一定会重新列出一个柱子
我们只需要特判这个数并且把柱子数减一按要求输出就可以了
```cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define fmin(a,b) ((a)<(b)?(a):(b))
#define inf (1000000000)
#define st 1
#define ed 2
#define poi(a,b) ((b)+((a)<<1))
struct edge{
long long to,val,next;
}e[200010];
queue<long long>q;
long long n,m,hd=0,tl=0,t=0,ans=0,len=1;
long long head[10010],dep[10010],cur[10010],sqre[10010];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].next=head[y];
head[y]=len;
//printf("*%lld %lld\n",(x-1)>>1,(y-1)>>1);
}
bool bfs(long long s,long long t){
memset(dep,0x7f,sizeof(dep));
while(!q.empty()) q.pop();
fr(i,1,n) cur[i]=head[i];
dep[s]=0;
q.push(s);
while(!q.empty()){
long long u=q.front();
q.pop();
travel(i,u,v){
if(dep[v]>inf&&e[i].val){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t]<inf) return true;
return false;
}
long long dfs(long long u,long long t,long long limit){
if(limit==0||u==t) return limit;
long long flow=0,f;
travel(i,u,v){
cur[u]=i;
if(dep[v]==dep[u]+1&&(f=dfs(v,t,fmin(limit,e[i].val)))){
flow+=f;
limit-=f;
e[i].val-=f;
e[i^1].val+=f;
if(!limit) break;
}
}
return flow;
}
void oupt(){
printf("%lld\n",t-1);
fr(j,1,t-1){
bool flag=true;
long long u=poi(j,2);
travel(i,u,v) if(v>=3&&v<=2+t*2&&e[i].val!=0){
flag=false;
break;
}
if(flag){
long long now=j;
while(1){
bool flag2=true;
printf("%lld ",now);
travel(i,poi(now,1),v) if(e[i].val==0){
now=((v-1)>>1);
flag2=false;
break;
}
if(now==0) break;
if(flag2) break;
}
printf("\n");
}
}
}
void work(){
while(1){
t++;
//build
addedge(st,poi(t,1),1);
addedge(poi(t,2),ed,1);
while(sqre[hd]<=t) hd++;
while(sqre[tl+1]<t*2) tl++;
fr(i,hd,tl) addedge(poi(sqre[i]-t,1),poi(t,2),1);
//work
while(bfs(st,ed)) ans+=dfs(st,ed,inf);
if(t-ans>n){
oupt();
return;
}
}
}
int main(){
n=v_in();
fr(i,0,10000) sqre[i]=i*i;
work();
return 0;
}
```
### 圆桌问题
[luogu P3254](https://www.luogu.org/problem/P3254)
一道二分图板子题
输出方案时只需要判断增广路即可。
```cpp
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#define fr(i,a,b) for(register long long i=a;i<=b;i++)
#define travel(i,a,b) for(register long long i=head[a],b=e[i].to;i!=0;i=e[i].next,b=e[i].to)
#define MAXN (2147000000)
#define fmin(a,b) ((a)<(b)?(a):(b))
struct edge{
long long to,val,next;
}e[200010];
long long n,m,st,ed,len=1,ans=0,sum=0;
long long head[10010];
long long gap[10010],d[10010];
inline long long v_in(){
char ch=getchar();long long sum=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar();
return sum*f;
}
inline void addedge(long long x,long long y,long long z){
e[++len].to=y;
e[len].val=z;
e[len].next=head[x];
head[x]=len;
e[++len].to=x;
e[len].val=0;
e[len].next=head[y];
head[y]=len;
}
long long sap(long long u,long long flow){
if(u==ed) return flow;
long long res=flow;
travel(i,u,v) if(e[i].val&&d[u]==d[v]+1){
long long t=sap(v,fmin(res,e[i].val));
e[i].val-=t;
e[i^1].val+=t;
res-=t;
if(!res) return flow;
}
if(--gap[d[u]]==0) d[st]=n+m+2;
gap[++d[u]]++;
return flow-res;
}
void oupt(){
fr(u,1,n){
travel(i,u,v) if(v>=n+1&&v<=n+m&&e[i].val==0){
printf("%lld ",v-n);
}
printf("\n");
}
}
void inpt(){
n=v_in(),m=v_in();
st=n+m+1,ed=st+1;
fr(i,1,n) fr(j,1,m) addedge(i,j+n,1);
fr(i,1,n){
long long x=v_in();
sum+=x;
addedge(st,i,x);
}
fr(i,1,m){
long long x=v_in();
addedge(i+n,ed,x);
}
}
void work(){
gap[0]=n+m+2;
while(d[st]<n+m+2) ans+=sap(st,MAXN);
//printf("%lld\n",ans);
if(ans==sum){
printf("1\n");
oupt();
}
else{
printf("0\n");
}
}
int main(){
inpt();
work();
return 0;
}
```