1.飞行员配对方案问题
最大匹配
2 太空飞行计划问题
之前写的详细题解
最大权闭合子图->最小割
#include
#include
#include
#include
#include
using namespace std;
const int N=10010;
const int inf=0x3f3f3f3f;
queue<int> Q;
int S,T,n,m,head[N],to[2*N],w[2*N],nxt[2*N],num=1,sum=0,dep[N];
bool vis[N];
void build(int u,int v,int ww)
{
num++;
to[num]=v; nxt[num]=head[u]; w[num]=ww; head[u]=num;
num++;
to[num]=u; nxt[num]=head[v]; w[num]=0; head[v]=num;
}
bool bfs()
{
while(!Q.empty()) Q.pop();
memset(vis,0,sizeof(vis));
Q.push(S); vis[S]=1; dep[S]=1;
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v]||w[i]<=0) continue;
dep[v]=dep[u]+1;
vis[v]=1; Q.push(v);
}
}
return vis[T];
}
int dfs(int u,int d)
{
if(u==T||d==0) return d;
int ret=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dep[v]!=dep[u]+1||w[i]<=0) continue;
int flow=dfs(v,min(d,w[i]));
d-=flow; ret+=flow;
w[i]-=flow; w[i^1]+=flow;
if(d==0) break;
}
if(ret==0) dep[u]=-1;
return ret;
}
int main()
{
scanf("%d%d",&n,&m);
S=n+m+1; T=n+m+2;
for(int i=1;i<=n;i++)
{
int c; scanf("%d",&c);
build(S,i,c); sum+=c;
while(getchar()==' ')
{
int x;scanf("%d",&x);
build(i,n+x,inf);
}
}
for(int i=1;i<=m;i++)
{
int c;scanf("%d",&c);
build(n+i,T,c);
}
while(bfs()) sum-=dfs(S,inf);
for(int i=1;i<=n;i++) if(vis[i]) printf("%d ",i); puts("");
for(int i=n+1;i<=n+m;i++) if(vis[i]) printf("%d ",i-n); puts("");
printf("%d\n",sum);
return 0;
}
3 最小路径覆盖问题
最小路径覆盖->点数-最大匹配
4 魔术球问题
最小路径覆盖
转化为判定性问题。
在n根柱子上最多能放多少个球->x个球n根柱子能不能放下->x个球最少需要几个柱子。
由于依次增加x是在上一次基础上增广,所以反而比二分快。
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=10100;
const int inf=0x3f3f3f3f;
queue<int> Q;
int head[N],to[N*20],nxt[N*20],w[N*20],num=1,tail=3,S,T,ans,dep[N],pre[N];
int id[N],pos[N],n;
bool vis[N];
void build(int u,int v,int ww)
{
num++;
to[num]=v; nxt[num]=head[u];
head[u]=num; w[num]=ww;
num++;
to[num]=v; nxt[num]=head[v];
head[v]=num; w[num]=0;
}
bool bfs()
{
memset(vis,0,sizeof(vis));
Q.push(S); vis[S]=1; dep[S]=1;
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v]||w[i]<=0) continue;
vis[v]=1; dep[v]=dep[u]+1;
Q.push(v);
}
}
return vis[T];
}
int dfs(int u,int d)
{
if(u==T||d==0) return d; int ret=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dep[v]!=dep[u]+1||w[i]<=0) continue;
int flow=dfs(v,min(d,w[i]));
ret+=flow; d-=flow;
if(flow) pre[u]=v;
w[i]-=flow; w[i^1]+=flow;
if(d==0) break;
}
if(ret==0) dep[u]=-1;
return ret;
}
void print(int top)
{
memset(head,0,sizeof(head)); num=1;
for(int i=1;i<=top;i++)
{
build(S,id[i],1);
build(id[i]^1,T,1);
for(int j=(int)sqrt(i);;j++)
{
int x=j*j-i;
if(x>=i) break; if(x<=0) continue;
build(id[x],id[i]^1,1);
}
}
int ret=0;
memset(pre,0,sizeof(pre));
while(bfs()) ret+=dfs(S,inf);
memset(vis,0,sizeof(vis));
for(int i=1;i<=top;i++)
{
if(vis[i]) continue;
for(int cur=i;;)
{
printf("%d ",cur);
vis[cur]=1;
int tmp=id[cur];
tmp=pre[tmp]; cur=pos[tmp];
if(!cur) {puts(""); break;}
}
}
}
int main()
{
scanf("%d",&n);
S=2; T=3; int ret=0;
for(int i=1;;i++)
{
id[i]=++tail; pos[tail]=i; tail++; pos[tail]=i;
build(S,id[i],1);
build(id[i]^1,T,1);
for(int j=(int)sqrt(i);;j++)
{
int x=j*j-i;
if(x>=i) break; if(x<=0) continue;
build(id[x],id[i]^1,1);
}
while(bfs()) ret+=dfs(S,inf);
if(i-ret>n) {ans=i-1; break;}
}
printf("%d\n",ans);
print(ans);
return 0;
}
5 圆桌问题
最大流
6 最长递增子序列问题
转化为经典模型:找出最多的不相交路径数。
#include
#include
#include
#include
#include
using namespace std;
const int inf=0x3f3f3f3f;
const int N=10000;
queue<int> Q;
int n,head[N],to[20*N],w[20*N],nxt[20*N],num=1,S,T,s;
int dp[N],a[N],dep[N];
bool vis[N];
void build(int u,int v,int ww)
{
num++;
to[num]=v; nxt[num]=head[u];
head[u]=num; w[num]=ww;
num++;
to[num]=u; nxt[num]=head[v];
head[v]=num; w[num]=0;
}
bool bfs()
{
memset(vis,0,sizeof(vis)); dep[S]=1;
Q.push(S); vis[S]=1;
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v]||w[i]<=0) continue;
vis[v]=1; dep[v]=dep[u]+1;
Q.push(v);
}
}
return vis[T];
}
int dfs(int u,int d)
{
if(u==T||d==0) return d; int ret=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dep[v]!=dep[u]+1||w[i]<=0) continue;
int flow=dfs(v,min(w[i],d));
d-=flow; ret+=flow;
w[i]-=flow; w[i^1]+=flow;
if(!d) break;
}
if(ret==0) dep[u]=-1;
return ret;
}
int main()
{
scanf("%d",&n); S=2*n+1; T=2*n+2; s=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]); dp[i]=1;
build(i,i+n,1);
for(int j=1;jif(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
if(dp[i]==1) build(S,i,inf);
else for(int j=1;jif(a[i]>a[j]&&dp[i]==dp[j]+1) build(j+n,i,1);
s=max(s,dp[i]);
}
for(int i=1;i<=n;i++) if(dp[i]==s) build(i+n,T,inf);
printf("%d\n",s);
if(s==1) {printf("%d\n%d\n",n,n); return 0;}
int ret=0;
while(bfs()) ret+=dfs(S,inf);
printf("%d\n",ret);
build(1,1+n,inf); build(n,n+n,inf);
while(bfs()) ret+=dfs(S,inf);
printf("%d\n",ret);
return 0;
}
7 试题库问题
最大流
8 机器人路径规划问题
???
9 方格取数问题
方格->染色->二分图
二分图最大点权独立集-> 总的-最小割
其实感觉和最大权闭合子图有点像?
10 餐巾计划问题
最小费用最大流
重点是找到 可以看成 入=出(xx=xx就可以看作流量平衡了) 的量,有源汇这样才能建出图。
旧代码。
#include
#include
#include
#include
#include
using namespace std;
const int N=410;
const int E=100005;
queue<int> q;
int inf,M,S,T,cnt=0;
int head[N],to[E],nxt[E],w[E],c[E],flow[N],pre[N],prev[N],dis[N],num=1,nn[205],p,m,n,f,s;
bool vis[N],inq[N];
int min(int a,int b) {return avoid build(int u,int v,int ww,int cos)
{
num++;
to[num]=v; w[num]=ww; c[num]=cos; nxt[num]=head[u]; head[u]=num;
num++;
to[num]=u; w[num]=0; c[num]=-cos; nxt[num]=head[v]; head[v]=num;
}
bool spfa()
{
while(!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
memset(inq,0,sizeof(inq));
memset(dis,127,sizeof(dis));
memset(flow,inf,sizeof(flow));
memset(pre,0,sizeof(pre));
q.push(S); dis[S]=0; inq[S]=1;
while(!q.empty())
{
int u=q.front();
q.pop(); inq[u]=0; vis[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(w[i]<=0) continue;
if(dis[v]>dis[u]+c[i])
{
dis[v]=dis[u]+c[i];
flow[v]=min(flow[u],w[i]);
pre[v]=u; prev[v]=i;
if(!inq[v]) inq[v]=1,q.push(v);
}
}
}
return vis[T];
}
int getcos()
{
int now=T; int ans=0; int d=flow[T];
while(now!=S)
{
ans+=d*c[prev[now]];
w[prev[now]]-=d;
w[prev[now]^1]+=d;
now=pre[now];
}
return ans;
}
int main()
{
scanf("%d",&M);
memset(head,0,sizeof(head)); num=1;
memset(dis,127,sizeof(dis)); inf=dis[0];
S=2*M+2; T=2*M+3;
for(int i=1;i<=M;i++) scanf("%d",&nn[i]);
scanf("%d%d%d%d%d",&p,&m,&f,&n,&s);
for(int i=1;i<=M;i++)
{
build(S,i,nn[i],0);
if(i+m<=M) build(i,i+m+M,inf,f);
if(i+n<=M) build(i,i+n+M,inf,s);
if(i1,inf,0);
build(i+M,T,nn[i],0);
build(S,i+M,inf,p);
}
int answer=0;
while(spfa()) answer+=getcos();
printf("%d",answer);
return 0;
}
11 航空路线问题
把起点拆了,找到两条最长的不相交路径 ->最大费用最大流
12 软件补丁问题
状压最短路
13 星际转移问题
妙妙。总之又是转化为判定性问题,按天数拆点,然后枚举天数加新点新边,看什么时候达到k。
要预先判无解的情况,并查集一下就好。
#include
#include
#include
#include
#include
using namespace std;
const int N=3000;
const int M=600000;
const int inf=0x3f3f3f3f;
queue<int> Q;
int head[N],to[M],nxt[M],w[M],num=1,n,m,k,H[N],P[N],R[30][30],S,T,fa[N],dep[N];
bool vis[N];
int getfa(int x) {return (fa[x]==x)?x:getfa(fa[x]);}
void merge(int x,int y)
{
int fx=getfa(x); int fy=getfa(y);
if(fx!=fy) fa[fx]=fy;
}
void build(int u,int v,int ww)
{
num++;
to[num]=v; w[num]=ww;
nxt[num]=head[u]; head[u]=num;
num++;
to[num]=u; w[num]=0;
nxt[num]=head[v]; head[v]=num;
}
bool bfs()
{
memset(vis,0,sizeof(vis)); dep[S]=1;
vis[S]=1; Q.push(S);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(w[i]<=0||vis[v]) continue;
vis[v]=1; dep[v]=dep[u]+1;
Q.push(v);
}
}
return vis[T];
}
int dfs(int u,int d)
{
if(u==T||d==0) return d; int ret=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dep[v]!=dep[u]+1||w[i]<=0) continue;
int flow=dfs(v,min(d,w[i]));
d-=flow; ret+=flow;
w[i]-=flow; w[i^1]+=flow;
if(d==0) break;
}
if(ret==0) dep[u]=-1;
return ret;
}
int main()
{
scanf("%d%d%d",&n,&m,&k); // station,ship,people
S=1,T=2; for(int i=1;i<=n+2;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&H[i],&P[i]);
for(int j=0;jscanf("%d",&R[i][j]); if(R[i][j]==0) R[i][j]=n+1; if(R[i][j]==-1) R[i][j]=n+2;}
for(int j=1;j
1],R[i][j]);
}
if(getfa(n+1)!=getfa(n+2)) {printf("0\n"); return 0;}
build(S,T+n+1,inf);// Attention!
int ret=0,ans=0;
for(int D=1;D;D++)
{
for(int i=1;i<=n+1;i++) build(T+i+(D-1)*(n+1),T+i+D*(n+1),inf); //station from last day
for(int i=1;i<=m;i++)
{
int cur=R[i][D%P[i]]; int pre=R[i][(D-1)%P[i]]; // station pre -> cur nowpos=T+D*(n+1)+x; prepos=T+(D-1)*(n+1)+x;
if(cur==n+2&&pre!=n+2) build(pre+T+(D-1)*(n+1),T,H[i]);//moon
else if(pre!=n+2) build(pre+T+(D-1)*(n+1),cur+T+D*(n+1),H[i]);
}
while(bfs()) ret+=dfs(S,inf);
if(ret>=k) {ans=D;break;}
}
printf("%d\n",ans);
return 0;
}
14 孤岛营救问题
分层图最短路
#include
#include
#include
#include
#include
using namespace std;
const int N=15;
const int MXN=100000;
const int inf=0x3f3f3f3f;
queue<int> Q;
int n,m,p,k,sss,S,T;
int id[1<<11][N][N],tail=0,Tail=0,dis[MXN],ID[N][N];
int head[MXN],to[4*MXN],nxt[4*MXN],w[4*MXN],num=0,pth[110][110],key[N][N],po[N];
int fx[4]={1,0,-1,0},fy[4]={0,1,0,-1};
bool ins[MXN];
void build(int u,int v,int ww)
{
num++;
to[num]=v; nxt[num]=head[u];
w[num]=ww; head[u]=num;
}
void spfa()
{
memset(dis,0x3f,sizeof(dis));
memset(ins,0,sizeof(ins));
dis[S]=0; ins[S]=1; Q.push(S);
while(!Q.empty())
{
int u=Q.front(); Q.pop(); ins[u]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dis[v]>dis[u]+w[i])
{
dis[v]=dis[u]+w[i];
if(!ins[v]) ins[v]=1,Q.push(v);
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=0;i<=10;i++) po[i]=1<int top=1<for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
ID[i][j]=++Tail;
for(int s=0;sscanf("%d",&k);
for(int i=1;i<=k;i++)
{
int X1,Y1,X2,Y2,G; scanf("%d%d%d%d%d",&X1,&Y1,&X2,&Y2,&G);
if(G>=1) pth[ID[X1][Y1]][ID[X2][Y2]]=pth[ID[X2][Y2]][ID[X1][Y1]]=G;
else pth[ID[X1][Y1]][ID[X2][Y2]]=pth[ID[X2][Y2]][ID[X1][Y1]]=-1;
}
scanf("%d",&sss);
for(int i=1;i<=sss;i++)
{
int x,y,q; scanf("%d%d%d",&x,&y,&q);
key[x][y]|=po[q-1];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int s=0;sfor(int d=0;d<4;d++)
{
int x=i+fx[d]; int y=j+fy[d];
if(x<=0||x>n||y<=0||y>m) continue;
int ss=s|key[x][y]; int g=pth[ID[i][j]][ID[x][y]];
if(g==-1) continue;
if(g==0||((s|po[g-1])==s)) build(id[s][i][j],id[ss][x][y],1);
}
if(i==1&&j==1&&s==0) build(S,id[s][i][j],0);
if(i==n&&j==m) build(id[s][i][j],T,0);
}
spfa();
if(dis[T]>=inf) printf("-1\n");
else printf("%d\n",dis[T]);
return 0;
}
15 汽车加油行驶问题
分层图最短路
16 数字梯形问题
最大权不相交路径->最大费用最大流
17 运输问题
最小费用最大流
18 分配问题
最小费用最大流,最大费用最大流
19 负载平衡问题
最小费用最大流
这题可以看出总流出=总流入,根据与平均值的差是正是负知道是该S->i还是i->T。
#include
#include
#include
#include
#include
using namespace std;
const int N=1500;
const int inf=0x3f3f3f3f;
queue<int> Q;
int n,a[N],aver=0,S,T,dis[N],ins[N],pre[N][2];
int head[N],to[4*N],nxt[4*N],w[4*N],c[4*N],num=1;
void build(int u,int v,int cc,int ww)
{
num++;
to[num]=v; nxt[num]=head[u];
w[num]=ww; c[num]=cc; head[u]=num;
num++;
to[num]=u; nxt[num]=head[v];
w[num]=0; c[num]=-cc; head[v]=num;
}
bool spfa()
{
memset(dis,0x3f,sizeof(dis));
memset(ins,0,sizeof(ins));
dis[S]=0; Q.push(S); ins[S]=1;
while(!Q.empty())
{
int u=Q.front(); Q.pop(); ins[u]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i]; if(w[i]<=0) continue;
if(dis[v]>dis[u]+c[i])
{
dis[v]=dis[u]+c[i];
pre[v][0]=u; pre[v][1]=i;
if(!ins[v]) ins[v]=1,Q.push(v);
}
}
}
return dis[T]int get()
{
int flow=inf; int ret=0; int tmp=T;
while(tmp!=S)
{
flow=min(flow,w[pre[tmp][1]]);
tmp=pre[tmp][0];
}
tmp=T;
while(tmp!=S)
{
ret+=c[pre[tmp][1]]*flow;
w[pre[tmp][1]]-=flow;
w[pre[tmp][1]^1]+=flow;
tmp=pre[tmp][0];
}
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),aver+=a[i];
aver/=n; S=n+1; T=n+2;
for(int i=1;i<=n;i++)
{
int ww=a[i]-aver;
if(ww>0) build(S,i,0,ww);
else build(i,T,0,-ww);
int Pre=(i==1)?n:i-1;
int Nxt=(i==n)?1:i+1;
build(i,Nxt,1,inf);
build(i,Pre,1,inf);
}
int ret=0; while(spfa()) ret+=get();
printf("%d\n",ret);
return 0;
}
20 深海机器人问题
最小费用最大流
21 最长k可重区间集问题
最大权不相交路径->最大费用最大流
最朴素的建图自然是S向每个区间连边(费用区间长度),每个区间向它所覆盖的点连边,每个点向T连边。然后限制每个点的流出量。 边数是n^2的。
想要减少边的数量,问题在于区间的建边,显然不能和区间的长度相关,最好每个区间只有 O(1)条边。
这道题妙的地方在于转化成了:求K条权之和最大的不相交路径,每条路径为一些不相交的区间序列。 这样建图也就容易想了,边数是O(n)的。
关于这个转化,我的思考过程是这样的:
首先,要保证每个点经过次数,其实只要看端点就好了,那么要离散化。
然后,我们想把“每个区间向它所覆盖的点连边”和“限制每个点的流出量”拍扁在端点的序列上。
这个序列上,前一个点向后一个点连边, 因为可以不走区间那么就是连费用0的边,而要保证走区间就是区间首向尾连容量1费用长度的边。
于是就变成了上面的那张图。
#include
#include
#include
#include
#include
#include
using namespace std;
map<int,int> mp;
const int N=100005;
const int inf=0x3f3f3f3f;
queue<int> Q;
int head[N],to[4*N],nxt[4*N],w[4*N],c[4*N],n,k,L[N],R[N],S,T,tail=2,num=1;
int dis[N],pre[N][2],val[2*N],tot=0;
bool ins[N];
void build(int u,int v,int cc,int ww)
{
num++;
to[num]=v; nxt[num]=head[u];
head[u]=num; w[num]=ww; c[num]=cc;
num++;
to[num]=u; nxt[num]=head[v];
head[v]=num; w[num]=0; c[num]=-cc;
}
bool spfa()
{
memset(ins,0,sizeof(ins));
memset(dis,-1,sizeof(dis));
dis[S]=0; ins[S]=1; Q.push(S);
while(!Q.empty())
{
int u=Q.front(); Q.pop(); ins[u]=0;
for(int i=head[u];i;i=nxt[i])
{
if(w[i]<=0) continue; int v=to[i];
if(dis[v]0]=u; pre[v][1]=i;
if(!ins[v]) ins[v]=1,Q.push(v);
}
}
}
return dis[T]!=-1;
}
int get()
{
int flow=inf; int ret=0; int tmp=T;
while(tmp!=S)
{
flow=min(flow,w[pre[tmp][1]]);
tmp=pre[tmp][0];
}
tmp=T;
while(tmp!=S)
{
ret+=flow*c[pre[tmp][1]];
w[pre[tmp][1]]-=flow;
w[pre[tmp][1]^1]+=flow;
tmp=pre[tmp][0];
}
return ret;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d%d",&L[i],&R[i]),val[++tot]=L[i],val[++tot]=R[i];
S=1,T=2,tail=2;
sort(val+1,val+tot+1);
for(int i=1,last=0;i<=tot;i++) if(last!=val[i])
{
mp[val[i]]=++tail;
if(last==0) build(S,tail,0,k);
else build(tail-1,tail,0,k);
last=val[i];
}
build(tail,T,0,k);
for(int i=1;i<=n;i++) build(mp[L[i]],mp[R[i]],R[i]-L[i],1);
int ret=0;
while(spfa()) ret+=get();
printf("%d\n",ret);
return 0;
}
22 最长k可重线段集问题
最大权不相交路径->最大费用最大流
23 火星探险问题
最小费用最大流
24 骑士共存问题
方格染色二分图->最大独立集->最小割
总结:
对于网络流的模型转化,很多时候都需要找到 xx=xx的等式,
例如像bzoj1061的不等式->等式,要求一个变量出现两次,
或者像是负载平衡问题 ,餐巾问题。
这都是由于网络流流入量=流出量的性质所延伸的问题。
很多求最大最小的问题,可转化成经典的判定性问题+二分/枚举。
同时,像是 方格->二分图等的经典模型也提醒我们观察是否有可以构成二分图的东西。
另外,这24题中的很多问题都可以用动归求解,方法不止一种。
除了以上的那些经典建图,网络流的“后悔机制”往往也生出一些比较精妙的模型,
例如之前多校联训Day6的T3,以及bzoj2504危桥,bzoj4669抢夺。
以及一些看似有后效性的东西,为了预先计算对后面的影响,设定一些特殊的费用以转化模型,
例如bzoj1070修车,bzoj2597剪刀石头布。
很多题目的模型都不是轻松能转化过来的,要细心观察。
最后附上 ByVoid神的题解集合:https://www.byvoid.com/zhs/blog/lpf24-solution?amp=1