网络流基础&网络流24题

时间:2024-07-17 10:07:09

dinic 网络最大流

int s,t,tot=1,head[V],cur[V],dep[V];struct edge{int v,f,next;}e[E];std::queue<int>q;
void add(int u,int v,int f){e[++tot]={v,f,head[u]},head[u]=tot,e[++tot]={u,0,head[v]},head[v]=tot;}
int bfs()
{
memset(dep+1,0x3f,t<<2),memcpy(cur+1,head+1,t<<2),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=e[i].next) if(dep[v=e[i].v]>inf&&e[i].f) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,flow=0;
for(int&i=cur[u],f;i;i=e[i].next)
if(dep[v=e[i].v]==dep[u]+1&&(f=dfs(v,std::min(lim,e[i].f))))
{
flow+=f,lim-=f,e[i].f-=f,e[i^1].f+=f;
if(!lim) break;
}
return flow;
}

zkw 最小费用流

int s,t,tot=1,head[V],vis[V],dis[V],flow,cost;struct edge{int v,f,c,next;}e[E];
void add(int u,int v,int f,int c){e[++tot]={v,f,c,head[u]},head[u]=tot,e[++tot]={u,0,-c,head[v]},head[v]=tot;}
int update()
{
int mn=inf;
for(int u=1;u<=t;++u) if(vis[u]) for(int i=head[u];i;i=e[i].next) if(!vis[e[i].v]&&e[i].f) mn=std::min(mn,dis[e[i].v]+e[i].c-dis[u]);
if(mn==inf) return 0;
for(int u=1;u<=t;++u) if(vis[u]) dis[u]+=mn;
return 1;
}
int dfs(int u,int res)
{
if(u==t) return flow+=res,cost+=dis[s]*res,res;
int use=0;vis[u]=1;
for(int i=head[u],t;i;i=e[i].next)
if(!vis[e[i].v]&&e[i].f&&dis[u]==dis[e[i].v]+e[i].c)
{
t=dfs(e[i].v,std::min(res-use,e[i].f)),use+=t,e[i].f-=t,e[i^1].f+=t;
if(use==res) return use;
}
return use;
}
void ZKW(){do do memset(vis+1,0,t<<2);while(dfs(s,inf));while(update());}

网络流24题

(Luogu)

飞行员配对方案问题

可以直接匈牙利。

dinic输出方案可以根据反向边是否有流来判断。

#include<bits/stdc++.h>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
int min(int a,int b){return a<b? a:b;}
const int N=100007,INF=1e9;
int tot=1,head[N],ver[N],edge[N],Next[N],dep[N];
queue<int>q;
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],edge[tot]=w,head[u]=tot;}
int bfs(int S,int T)
{
int i,u,v;
memset(dep,0x7f,sizeof dep),dep[S]=0,q.push(S);
while(!q.empty())
{
u=q.front(),q.pop();
for(i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>dep[u]+1&&edge[i]) dep[v]=dep[u]+1,q.push(v);
}
return dep[T]<INF;
}
int dfs(int u,int T,int lim)
{
if(!lim||u==T) return lim;
int flow=0,i,v,f;
for(i=head[u];i;i=Next[i])
if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,T,min(lim,edge[i]))))
{
flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
if(!lim) break;
}
return flow;
}
int main()
{
int m=read(),n=read(),i,u,v,ans=0,S=0,T=n+1;
do u=read(),v=read(),add(u,v,1),add(v,u,0); while(u!=-1&&v!=-1);
for(tot-=2,i=1;i<=m;++i) add(S,i,1),add(i,S,0);
for(i=m+1;i<=n;++i) add(i,T,1),add(T,i,0);
while(bfs(S,T)) ans+=dfs(S,T,INF);
if(!ans) return !printf("No Solution!");
printf("%d\n",ans);
for(i=2;i<=tot;i+=2) if(ver[i^1]^S&&ver[i]^T&&edge[i^1]) printf("%d %d\n",ver[i^1],ver[i]);
return 0;
}

负载平衡问题

均分纸牌,\(\sum(\overline a-a_i)\)。

#include<bits/stdc++.h>
using namespace std;
const int N=107;
int a[N],sum[N];
int read(){int x;scanf("%d",&x);return x;}
int abs(int a){return a<0? -a:a;}
int main()
{
int i,arr=0,n=read(),ans=0;
for(i=1;i<=n;++i) arr+=a[i]=read();
arr/=n;
for(i=1;i<=n;++i) a[i]-=arr,sum[i]=sum[i-1]+a[i];
sort(sum+1,sum+n+1),arr=sum[n/2+1];
for(i=1;i<=n;++i) ans+=abs(arr-sum[i]);
return !printf("%d",ans);
}

魔术球问题

网络流做法就是最小链覆盖=总点数-最大匹配。找到答案之后把匹配中的点对断开,剩下的就是一条条路径。

打表可以发现答案为\(\frac{n(n+2)+(n\&1)-2}{2}\)。

可以证明每次一个数从前往后(按照新加的杆子的顺序)能放就放一定不劣。

所以可以直接\(O(n^3)\)枚举。

#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N=60;
vector<int>a[N];
int read(){int x;scanf("%d",&x);return x;}
int sqr(int x){return x*x;}
int is(int x){return sqr(sqrt(x))==x;}
int main()
{
int n=read(),m=(n*(n+2)+(n&1)-2)/2,c=0;
printf("%d\n",m);
for(int i=1,j,f;i<=m;++i)
{
for(f=0,j=1;j<=c;++j) if(is(a[j].back()+i)) {a[j].eb(i),f=1;break;}
if(!f) a[++c].eb(i);
}
for(int i=1;i<=c;++i,puts("")) for(int x:a[i]) printf("%d ",x);
}

孤岛营救问题

这是个锤子的网络流。

因为门的种类不多,所以状压一下然后BFS最短路。

#include<bits/stdc++.h>
#define pi pair<int,int>
#define fi first
#define se second
#define eb emplace
using namespace std;
int read(){int x;cin>>x;return x;}
const int N=11,dx[4]={1,-1,0,0},dy[4]={0,0,1,-1},inf=0x3f3f3f3f;
struct node{int x,y,s;node(int a=0,int b=0,int c=0):x(a),y(b),s(c){}}u;
int n,m,p,k,s,dis[N][N][1<<14],a[N][N][N][N],key[N][N];
queue<node>q;
int trans(int x){return !x? inf:x-1;}
int in(int x,int y){return x>=1&&y>=1&&x<=n&&y<=m;}
int main()
{
n=read(),m=read(),p=read(),k=read(),memset(a,-1,sizeof a),memset(dis,0x3f,sizeof dis);
for(int i=1,x,y,X,Y;i<=k;++i) x=read(),y=read(),X=read(),Y=read(),a[x][y][X][Y]=a[X][Y][x][y]=trans(read());
s=read();
for(int i=1,x,y;i<=s;++i) x=read(),y=read(),key[x][y]|=1<<(read()-1);
dis[1][1][key[1][1]]=0,q.eb(1,1,key[1][1]);
for(int x,y,nx,ny,k;!q.empty();)
for(u=q.front(),q.pop(),x=u.x,y=u.y,k=0;k<4;++k)
if(in(nx=x+dx[k],ny=y+dy[k])&&a[x][y][nx][ny]^inf&&(!~a[x][y][nx][ny]||u.s>>a[x][y][nx][ny]&1)&&dis[nx][ny][u.s|key[nx][ny]]>dis[x][y][u.s]+1)
dis[nx][ny][u.s|key[nx][ny]]=dis[x][y][u.s]+1,q.push({nx,ny,u.s|key[nx][ny]});
int ans=inf;for(int i=0;i<1<<s;++i)ans=min(ans,dis[n][m][i]);
return !printf("%d",ans==inf? -1:ans);
}

圆桌问题

直接最大流是可行的。

然后我们可以想到一个贪心的做法,或者说模拟最大流。

把桌子按剩余人数降序排序,每一个团队选一段前缀,然后重新排序。

显然这样是最优的解法。

#include<bits/stdc++.h>
using namespace std;
int read(){int x;cin>>x;return x;}
const int N=300;
int n,m,a[N];struct node{int s,id;};
int operator<(node a,node b){return a.s<b.s;}
vector<int>ans[N];vector<node>t;priority_queue<node>q;
int main()
{
m=read(),n=read();
for(int i=1;i<=m;++i) a[i]=read();
for(int i=1;i<=n;++i) q.push({read(),i});
for(int i=1;i<=m;++i)
{
while(a[i])
{
if(q.empty()) return !printf("0");
node x=q.top();
ans[i].push_back(x.id),q.pop(),--a[i];
if(x.s>1) t.push_back({x.s-1,x.id});
}
while(!t.empty()) q.push(t.back()),t.pop_back();
}
puts("1");
for(int i=1;i<=m;++i,puts("")) for(int x:ans[i]) printf("%d ",x);
}

汽车加油行驶问题

这是个锤子的网络流。

思想是建分层图跑SPFA。

实际上图可以不用建出来,就几种情况转移的时候讨论一下就可以了。

注意如果当前点有加油站而油不满必须加油。

#include<bits/stdc++.h>
#define pi pair<int,int>
using namespace std;
struct node{int x,y,l;node(int a=0,int b=0,int c=0):x(a),y(b),l(c){}};
int read(){int x;scanf("%d",&x);return x;}
int d[101][101][11],mp[101][101],dx[4]={0,0,1,-1},dy[4]={1,-1,0,0},n,k,a,b,c,inq[101][101][11];queue<node>q;
int in(int x,int y){return x>=1&&y>=1&&x<=n&&y<=n;}
int main()
{
n=read(),k=read(),a=read(),b=read(),c=read(),memset(d,0x7f,sizeof d),inq[1][1][k]=1,d[1][1][k]=0,q.push({1,1,k});
for(int i=1,j;i<=n;++i) for(j=1;j<=n;++j) mp[i][j]=read();
while(!q.empty())
{
node u=q.front();int x=u.x,y=u.y,l=u.l,dis=d[x][y][l];
q.pop(),inq[x][y][l]=0;
if(mp[x][y]&&l^k)
{
if(d[x][y][k]>dis+a)
{
d[x][y][k]=dis+a;
if(!inq[x][y][k]) inq[x][y][k]=1,q.push({x,y,k});
}
continue;
}
if(d[x][y][k]>dis+a+c)
{
d[x][y][k]=dis+a+c;
if(!inq[x][y][k]) inq[x][y][k]=1,q.push({x,y,k});
}
if(!l) continue;
for(int i=0;i<4;++i)
{
int nx=x+dx[i],ny=y+dy[i],nl=l-1,dis=d[x][y][l];
if(nx<x||ny<y) dis+=b;
if(in(nx,ny)&&dis<d[nx][ny][nl])
{
d[nx][ny][nl]=dis;
if(!inq[nx][ny][nl]) q.push({nx,ny,nl}),inq[nx][ny][nl]=1;
}
}
}
int ans=2147483647;
for(int i=0;i<=k;++i) ans=min(ans,d[n][n][i]);
printf("%d",ans);
}

试题库问题

在网络流24题中,你甚至可以用网络流来解决问题。

每个题建一个点\(u_i\),每个类建一个点\(v_i\)。

\(s\rightarrow u_i:1\)

\(u_i\rightarrow v_j:1\)(\(j\)是第\(i\)题所属的类)

\(v_j\rightarrow t:c_j\)(\(c_j\)是第\(i\)类题的要求数目)

看一下是否满流(指到\(t\)的全部流满)即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1050,M=50007,inf=1e9;
int n,k,m,s,t,head[N],cur[N],dep[N],ver[M],edge[M],Next[M],tot=1;queue<int>q;vector<int>ans[N];
int read(){int x;scanf("%d",&x);return x;}
int min(int a,int b){return a<b? a:b;}
void add(int u,int v,int f){ver[++tot]=v,Next[tot]=head[u],edge[tot]=f,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memcpy(cur,head,sizeof cur),memset(dep,0x3f,sizeof dep),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int main()
{
k=read(),n=read(),s=n+k+1,t=n+k+2;
for(int i=1,x;i<=k;++i) add(i+n,t,x=read()),m+=x;
for(int i=1;i<=n;++i) add(s,i,1);
for(int i=1,j;i<=n;++i) for(j=read();j;--j) add(i,read()+n,1);
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
if(maxflow^m) return puts("No Solution!"),0;
for(int u=1,i,v;u<=n;++u) for(i=head[u];i;i=Next[i]) if((v=ver[i])^s&&edge[i^1]) ans[v-n].push_back(u);
for(int i=1;i<=k;++i)
{
printf("%d:",i);
for(int x:ans[i]) printf(" %d",x);
puts("");
}
}

最长k可重区间集问题

先离散化,设离散化后的范围为\(m\)。

\(\forall i\in[1,m],i-1\rightarrow i:(k,0)\)

\(\forall i\in[1,n],l_i\rightarrow r_i:(k,len_i)\)

源点可以设成\(0\),汇点就是\(m\)。跑最大费用最大流。

正确性显然。

#include<bits/stdc++.h>
#define eb emplace_back
#define IT vector<node>::iterator
#define ll long long
#define pi pair<int,int>
#define fi first
#define se second
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=1050;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int n,m,k,s,h[N],flow[N],inq[N];ll dis[N];pi a[N];IT id[N];
vector<node>E[N];queue<int>q;
void add(int u,int v,int f,int c){E[u].eb(u,v,f,c,E[v].size()),E[v].eb(v,u,0,-c,E[u].size()-1);}
int spfa()
{
memset(dis,0x7f,sizeof dis),memset(flow,0x7f,sizeof flow),dis[0]=0,inq[0]=1,q.push(0),id[m]=E[m].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v=it->v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[m]!=E[m].begin();
}
int main()
{
n=read(),k=read();
for(int i=1,l,r;i<=n;++i) h[++m]=l=read(),h[++m]=r=read(),a[i]={l,r};
sort(h+1,h+m+1),m=unique(h+1,h+m+1)-h-1;
for(int i=1;i<=n;++i) a[i].fi=lower_bound(h+1,h+m+1,a[i].fi)-h,a[i].se=lower_bound(h+1,h+m+1,a[i].se)-h;
for(int i=1;i<=m;++i) add(i-1,i,k,0);
for(int i=1;i<=n;++i) add(a[i].fi,a[i].se,1,h[a[i].fi]-h[a[i].se]);
ll mincost=0;
for(int p;spfa();) for(p=m,mincost+=1ll*flow[p]*dis[p];p;p=id[p]->u) id[p]->f-=flow[m],E[p][id[p]->p].f+=flow[m];
return !printf("%lld",-mincost);
}

最长k可重线段集问题

就是最大带权k重线段集。把费用改成线段长度就好了。

注意可能有\(x_0=x_1\)的情况,直接跑会有负环。

读入计算完长度之后我们先把\(l,r\)都\(*2\)。

如果\(l=r\)就\(r+=1\),否则\(l+=1\)。

这样就能处理掉负环的情况了。正确性显然。

#include<bits/stdc++.h>
#define eb emplace_back
#define IT vector<node>::iterator
#define ll long long
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=1050;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
struct line{int l,r,len;}a[N];
int n,m,k,s,h[N],flow[N],inq[N];ll dis[N];IT id[N];
vector<node>E[N];queue<int>q;
ll sqr(int x){return 1ll*x*x;}
int cal(int x,int X,int y,int Y){return (int)(sqrt(sqr(x-X)+sqr(y-Y))+1e-8);}
void add(int u,int v,int f,int c){E[u].eb(u,v,f,c,E[v].size()),E[v].eb(v,u,0,-c,E[u].size()-1);}
int spfa()
{
memset(dis,0x7f,sizeof dis),memset(flow,0x7f,sizeof flow),dis[0]=0,inq[0]=1,q.push(0),id[m]=E[m].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v=it->v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[m]!=E[m].begin();
}
int main()
{
n=read(),k=read();
for(int i=1,l,r,x,y,w;i<=n;++i)
{
l=read(),x=read(),r=read(),y=read(),w=cal(l,r,x,y);
if(l>r) swap(l,r);
l<<=1,r<<=1;
if(l==r) r|=1; else l|=1;
h[++m]=l,h[++m]=r,a[i]={l,r,w};
}
sort(h+1,h+m+1),m=unique(h+1,h+m+1)-h-1;
for(int i=1;i<=n;++i) a[i].l=lower_bound(h+1,h+m+1,a[i].l)-h,a[i].r=lower_bound(h+1,h+m+1,a[i].r)-h;
for(int i=1;i<=m;++i) add(i-1,i,k,0);
for(int i=1;i<=n;++i) add(a[i].l,a[i].r,1,-a[i].len);
ll mincost=0;
for(int p;spfa();) for(p=m,mincost+=1ll*flow[p]*dis[p];p;p=id[p]->u) id[p]->f-=flow[m],E[p][id[p]->p].f+=flow[m];
return !printf("%lld",-mincost);
}

分配问题

直接跑最大/小费用最大流。

#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
#define IT vector<node>::iterator
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=207;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int n,s,t,flow[N],inq[N],a[N][N];IT id[N];ll dis[N];
queue<int>q;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
ll EK()
{
ll mincost=0;
for(int p;spfa();) for(p=t,mincost+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
return mincost;
}
int main()
{
n=read(),s=2*n+1,t=2*n+2;
for(int i=1;i<=n;++i) add(s,i,1,0),add(i+n,t,1,0);
for(int i=1,j;i<=n;++i) for(j=1;j<=n;++j) add(i,j+n,1,a[i][j]=read());
printf("%lld\n",EK());
for(int i=1;i<=t;++i) E[i].clear();
for(int i=1;i<=n;++i) add(s,i,1,0),add(i+n,t,1,0);
for(int i=1,j;i<=n;++i) for(j=1;j<=n;++j) add(i,j+n,1,-a[i][j]);
printf("%lld\n",-EK());
}

运输问题

跟上一题基本一样。

#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
#define IT vector<node>::iterator
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=207,inf=1e9;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int n,m,s,t,flow[N],inq[N],a[N][N],b[N],c[N];IT id[N];ll dis[N];
queue<int>q;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
ll EK()
{
ll mincost=0;
for(int p;spfa();) for(p=t,mincost+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
return mincost;
}
int main()
{
n=read(),m=read(),s=n+m+1,t=n+m+2;
for(int i=1;i<=n;++i) add(s,i,b[i]=read(),0);
for(int i=1;i<=m;++i) add(i+n,t,c[i]=read(),0);
for(int i=1,j;i<=n;++i) for(j=1;j<=m;++j) add(i,j+n,inf,a[i][j]=read());
printf("%lld\n",EK());
for(int i=1;i<=t;++i) E[i].clear();
for(int i=1;i<=n;++i) add(s,i,b[i],0);
for(int i=1;i<=m;++i) add(i+n,t,c[i],0);
for(int i=1,j;i<=n;++i) for(j=1;j<=m;++j) add(i,j+n,inf,-a[i][j]);
printf("%lld\n",-EK());
}

火星探险问题

因为贡献在点上且每个点的贡献只能被算一次,所以我们考虑拆点,每个点拆成入点和出点。

每个点的出点向它的能够到达的点的入点连一条\((1,0)\)边。

如果一个点不是障碍,那么从它的入点到它的出点连一条\((+\infty,0)\)的边。

如果一个点是岩石,那么从它的入点到它的出点连一条\((1,1)\)的边。

从源点到\((1,1)\)这个点的入点连一条\((n,0)\)的边。

跑最大费用最大流。

输出方案可以从\((1,1)\)开始dfs,每次找一条路径,找的时候记得把反向边的流量\(-1\)。

#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
#define IT vector<node>::iterator
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=2600,inf=1e9;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int n,p,q,s,t,flow[N],inq[N],dis[N],a[40][40],h[40][40];IT id[N];
queue<int>que;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),que.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!que.empty();)
{
u=que.front(),que.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) que.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
void EK(){for(int p;spfa();) for(p=t;p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];}
void dfs(int id,int u)
{
for(auto e:E[u])
if(e.v-p*q>u&&E[e.v][e.p].f)
{
printf("%d %d\n",id,e.v-p*q==u+1? 1:0);
--E[e.v][e.p].f,dfs(id,e.v-p*q);
return ;
}
}
int main()
{
n=read(),p=read(),q=read(),s=p*q*2+1,t=p*q,add(s,1,n,0);
for(int i=1;i<=q;++i) for(int j=1;j<=p;++j) a[i][j]=read(),h[i][j]=(i-1)*p+j;
for(int i=1;i<=q;++i)
for(int j=1;j<=p;++j)
{
if(i^q) add(h[i][j],h[i+1][j]+p*q,inf,0);
if(j^p) add(h[i][j],h[i][j+1]+p*q,inf,0);
if(a[i][j]!=1) add(h[i][j]+p*q,h[i][j],inf,0);
if(a[i][j]==2) add(h[i][j]+p*q,h[i][j],1,-1);
}
EK();
for(int i=1;i<=n;++i) dfs(i,1);
}

深海机器人问题

因为贡献在边上所以不用拆点了。

建图还是建一条\((+\infty,0)\)的边再建一条\((1,w_i)\)的边。

从源点到每个出发点建\((k_i,0)\)的边。

从每个结束点到汇点建\((k_i,0)\)的边。

然后跑最大费用最大流。

注意输入比较恶心。

#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
#define IT vector<node>::iterator
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=300,inf=1e9;
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int a,b,p,q,s,t,flow[N],inq[N],dis[N],h[20][20];IT id[N];
queue<int>que;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),que.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!que.empty();)
{
u=que.front(),que.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) que.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
void EK()
{
int mincost=0;
for(int p;spfa();) for(mincost+=flow[t]*dis[t],p=t;p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
printf("%d",-mincost);
}
int main()
{
a=read(),b=read(),p=read()+1,q=read()+1,s=p*q+1,t=p*q+2;
for(int i=1;i<=q;++i) for(int j=1;j<=p;++j) h[i][j]=(j-1)*q+i;
for(int i=1;i<=p;++i) for(int j=1;j<q;++j) add(h[j][i],h[j+1][i],inf,0),add(h[j][i],h[j+1][i],1,-read());
for(int i=1;i<=q;++i) for(int j=1;j<p;++j) add(h[i][j],h[i][j+1],inf,0),add(h[i][j],h[i][j+1],1,-read());
for(int k,x,y;a;--a) k=read(),y=read(),x=read(),add(s,h[x+1][y+1],k,0);
for(int k,x,y;b;--b) k=read(),y=read(),x=read(),add(h[x+1][y+1],t,k,0);
EK();
}

太空飞行计划问题

如果我们把每个实验到它需要的仪器连有向边,并且实验点权为正,仪器点权为负,那么答案实际上就是最大权闭合子图。

然后我们考虑如何求方案。

删掉扩展图中某个仪器到汇点的边,如果新的最小割恰好等于原来的最小割减去该仪器的价格,那么说明选了这个仪器。

对于一个实验如果它需要的所有仪器都被选了的话我们肯定会选这个实验。

#include<bits/stdc++.h>
using namespace std;
const int N=107,M=3007,inf=1e9;
int n,m,s,t,head[N],ver[M],flow[M],edge[M],Next[M],tot=1,cur[N],dep[N],is[N],id[N],cnm;queue<int>q;vector<int>A,B;
int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();if(c=='\n'||c=='\r')cnm=1;return x;}
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],flow[tot]=w,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memset(dep,0x3f,sizeof dep),memcpy(cur,head,sizeof cur),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int check(int u)
{
for(int i=head[u];i;i=Next[i]) if(ver[i]^s&&!is[ver[i]]) return 0;
return 1;
}
int dinic()
{
memcpy(edge,flow,sizeof edge);
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}
int main()
{
n=read(),m=read(),s=n+m+1,t=n+m+2;int sum=0,maxflow;
for(int i=1,x;i<=n;++i)
{
add(s,i,x=read()),sum+=x;
for(cnm=0;!cnm;) add(i,read()+n,inf);
}
for(int i=1;i<=m;++i) add(i+n,t,read()),id[i]=tot^1;
maxflow=dinic();
for(int i=1,x;i<=m;++i)
{
x=flow[id[i]],flow[id[i]]=0;
if(dinic()==maxflow-x) is[i+n]=1,B.push_back(i);
flow[id[i]]=x;
}
for(int u=1;u<=n;++u) if(check(u)) A.push_back(u);
for(auto it=A.begin();it!=A.end();++it) printf("%d",*it),putchar(next(it)==A.end()?'\n':' ');
for(auto it=B.begin();it!=B.end();++it) printf("%d",*it),putchar(next(it)==B.end()?'\n':' ');
printf("%d",sum-maxflow);
}

方格取数问题

我们要求的是网格图的最大权独立集。

因为网格图就是二分图,所以我们可以转化为总权值减最小割。

建图的话遍历一遍就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=10007,M=40007,inf=1e9;
int n,m,s,t,head[N],ver[M],edge[M],Next[M],tot=1,cur[N],dep[N],a[107][107],f[107][107];queue<int>q;
int read(){int x;scanf("%d",&x);return x;}
int h(int x,int y){return (x-1)*n+y;}
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],edge[tot]=w,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memset(dep,0x3f,sizeof dep),memcpy(cur,head,sizeof cur),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int dinic()
{
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}
void dfs(int x,int y,int id)
{
if(f[x][y]) return ;
f[x][y]=1;
if(!id)
{
add(s,h(x,y),a[x][y]);
if(x^m) add(h(x,y),h(x+1,y),inf),dfs(x+1,y,id^1);
if(y^n) add(h(x,y),h(x,y+1),inf),dfs(x,y+1,id^1);
}
else
{
add(h(x,y),t,a[x][y]);
if(x^m) add(h(x+1,y),h(x,y),inf),dfs(x+1,y,id^1);
if(y^n) add(h(x,y+1),h(x,y),inf),dfs(x,y+1,id^1); }
}
int main()
{
m=read(),n=read(),s=n*m+1,t=n*m+2;int sum=0;
for(int i=1;i<=m;++i) for(int j=1;j<=n;++j) sum+=a[i][j]=read();
dfs(1,1,0),printf("%d",sum-dinic());
}

骑士共存问题

我们发现这个图还是没有奇环,依旧是一个二分图。所以可以直接做二分图最大独立集即总点数减最小割。

分类的话还是可以按照横坐标+纵坐标的奇偶性分。

#include<bits/stdc++.h>
using namespace std;
const int N=40007,M=400007,inf=1e9;
int n,m,s,t,head[N],ver[M],edge[M],Next[M],tot=1,cur[N],dep[N],a[207][207],dx[8]={1,2,2,1,-1,-2,-2,-1},dy[8]={2,1,-1,-2,-2,-1,1,2};queue<int>q;
int read(){int x;scanf("%d",&x);return x;}
int h(int x,int y){return (x-1)*n+y;}
int in(int x,int y){return x>=1&&y>=1&&x<=n&&y<=n;}
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],edge[tot]=w,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memset(dep,0x3f,sizeof dep),memcpy(cur,head,sizeof cur),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int dinic()
{
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}
int main()
{
n=read(),m=read(),s=n*n+1,t=n*n+2;int sum=n*n-m;
for(int i=1,x,y;i<=m;++i) x=read(),y=read(),a[x][y]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(!a[i][j])
{
if((i+j)&1) {add(h(i,j),t,1);continue;}
add(s,h(i,j),1);
for(int k=0;k<8;++k)
{
int x=i+dx[k],y=j+dy[k];
if(!in(x,y)) continue;
add(h(i,j),h(x,y),inf);
}
}
printf("%d",sum-dinic());
}

最长不下降子序列问题

第一问\(O(n^2)\)暴力DP。设\(f_i\)表示以\(a_i\)结尾的最长不下降子序列的最长长度,答案为\(l\)。

然后我们考虑一下,一个最长不下降子序列一定是从一个\(f_i=1\)的位置开始直到\(f_i=l\)的位置结束。并且每个点只能用一次。

那么我们很容易想到分层图+拆点的做法,但是边和点都太多了很不优秀。

我们考虑如果把这个图的分层去掉实际上也是没有问题的。

大概就如下建图(流量都为\(1\)):

从源点到每个\(f_i=1\)的A点连边。

从每个\(i\)的A点到所有\(f_j\ge f_i\wedge f_j=f_i+1\)的\(j\)的B点连边。

从每个\(i\)的B点到它的A点连边。

从每个\(f_i=l\)的B点到汇点连边。

易证这样建图的正确性。跑一个最大流就是第二问的答案了。

第三问就是把\(s\rightarrow 1A,nB\rightarrow t\)这两条边的流量改成\(inf\)。(如果存在)

因为相同的最长不下降子序列只能算一次所以这样做是没有问题的。

#include<bits/stdc++.h>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=1007,M=1000007,inf=1e9;
int n,s,t,a[N],f[N],head[N],ver[M],edge[M],flow[M],Next[M],tot=1,cur[N],dep[N];queue<int>q;
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],flow[tot]=w,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memset(dep,0x3f,sizeof dep),memcpy(cur,head,sizeof cur),dep[s]=0,q.push(s);
for(int i,u,v;!q.empty();) for(u=q.front(),q.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,q.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int dinic()
{
memcpy(edge,flow,sizeof flow);
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}
int main()
{
n=read(),s=2*n+1,t=2*n+2;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) for(int j=0;j<i;++j) if(a[i]>=a[j]) f[i]=max(f[j]+1,f[i]);
int l=0;for(int i=1;i<=n;++i) l=max(l,f[i]);
if(l==1) return !printf("%d\n%d\n%d",l,n,n);
printf("%d\n",l);
for(int i=1;i<=n;++i)
{
if(f[i]==1) add(s,i,1);
if(f[i]==l) add(i+n,t,1);
add(i+n,i,1);
}
for(int i=1;i<n;++i) for(int j=i+1;j<=n;++j) if(a[j]>=a[i]&&f[j]==f[i]+1) add(i,j+n,1);
printf("%d\n",dinic());
for(int i=head[1];i;i=Next[i]) if(ver[i]==s) flow[i^1]=inf;
for(int i=head[n*2];i;i=Next[i]) if(ver[i]==t) flow[i]=inf;
printf("%d\n",dinic());
}

最小路径覆盖问题

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=151;
vector<int>E[N];int vis[N],T,mat[N],a[N];deque<int>q[N];
int dfs(int u)
{
for(int v:E[u])
if(vis[v]^T)
{
vis[v]=T;
if(!mat[v]||dfs(mat[v])) return mat[v]=u;
}
return 0;
}
int read(){int x;cin>>x;return x;}
int main()
{
int n=read(),m=read(),i,j,u,v,c=0,f;
for(i=1;i<=m;++i) u=read(),v=read(),E[u].pb(v);
for(i=1;i<=n;++i) ++T,dfs(i);
for(i=1;i<=n;++i)
{
if(!mat[i]) continue;
for(f=0,j=1;!f&&j<=c;++j)
{
if(q[j].back()==mat[i]) q[j].push_back(i),f=1;
if(q[j].front()==i) q[j].push_front(mat[i]),f=1;
}
if(!f) q[++c].push_back(mat[i]),q[c].push_back(i);
}
for(i=1;i<=c;puts(""),++i) while(!q[i].empty()) a[q[i].front()]=1,printf("%d ",q[i].front()),q[i].pop_front();
for(i=1;i<=n;++i) if(!a[i]) printf("%d\n",i),++c;
printf("%d",c);
}

餐巾计划问题

拆点,每天拆成两个点,A点表示产生的脏餐巾,B点表示获得的干净餐巾。

\(s\rightarrow A_i:(num_i,0)\)表示每天都会产生\(num_i\)条脏餐巾。

\(B_i\rightarrow t:(num_i,0)\)表示每天都需要\(num_i\)条干净餐巾,只有把所有的这些边流满才能够达到最大流。

\(s\rightarrow B_i:(+\infty,p)\)表示购买干净餐巾。

\(A_i\rightarrow A_{i+1}:(+\infty,0)\)表示每天的脏餐巾可以留到下一天。

\(A_i\rightarrow B_{i_n}:(+\infty,s)\)表示慢洗。

\(A_i\rightarrow B_{i_m}:(+\infty,f)\)表示快洗。

看上去很玄乎不过这个建图是没有问题的。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb emplace_back
#define IT vector<node>::iterator
const int N=4007,inf=1e9;
int read(){int x;scanf("%d",&x);return x;}
namespace NFW
{
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int s,t,flow[N],inq[N];IT id[N];ll dis[N];
queue<int>q;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
void EK()
{
ll mincost=0;
for(int p;spfa();) for(p=t,mincost+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
printf("%lld",mincost);
}
}using namespace NFW;
int a[N],n,c,t1,c1,t2,c2;
int main()
{
n=read(),s=n*2+1,t=n*2+2;
for(int i=1;i<=n;++i) a[i]=read();
c=read(),t1=read(),c1=read(),t2=read(),c2=read();
for(int i=1;i<=n;++i) add(s,i,a[i],0),add(i+n,t,a[i],0),add(s,i+n,inf,c);
for(int i=1;i<n;++i) add(i,i+1,inf,0);
for(int i=1;i<=n-t1;++i) add(i,i+t1+n,inf,c1);
for(int i=1;i<=n-t2;++i) add(i,i+t2+n,inf,c2);
EK();
}

软件补丁问题

状压一下问题,然后跑最短路就行了。

一些卡时间/空间的办法。

\(1.\)用spfa而非dijkstra。spfa在这种图上基本上卡不掉,而dij的那一个\(\log\)是实打实的。

\(2.\)不要建边,在跑最短路的时候对于每一个状态直接遍历所有的补丁。这题看上去真正用到的点数就很少对吧。实际上这题在LOJ上的数据中spfa最多会运行5K次左右

#include<bits/stdc++.h>
#define pi pair<int,int>
using namespace std;
const int N=1<<20,M=107;
char s[20];queue<int>q;
int n,m,U,dis[N],inq[N];struct node{int t,s1,s2,t1,t2;}a[M];
int read(){int x;scanf("%d",&x);return x;}
pi get(int f)
{
scanf("%s",s);int f1=0,f2=0;
for(int i=0;i<n;++i) if(s[i]^'0') if((s[i]=='+')^f) f1|=1<<i; else f2|=1<<i;
return {f1,f2};
}
int main()
{
n=read(),m=read();
for(int i=1,t,s1,s2,t1,t2;i<=m;++i) t=read(),tie(s1,s2)=get(0),tie(t1,t2)=get(1),t1^=(1<<n)-1,a[i]={t,s1,s2,t1,t2};
memset(dis,0x3f,sizeof dis),q.push((1<<n)-1),dis[(1<<n)-1]=0,inq[(1<<n)-1]=1;
for(int i,u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(i=1;i<=m;++i)
if((u&a[i].s1)==a[i].s1&&!(u&a[i].s2))
if(dis[v=(u&a[i].t1)|a[i].t2]>dis[u]+a[i].t)
{
dis[v]=dis[u]+a[i].t;
if(!inq[v]) q.push(v),inq[v]=1;
}
}
printf("%d",dis[0]==0x3f3f3f3f? 0:dis[0]);
}

数字梯形问题

考虑最大费用最大流。

注意:是从第一行的每个数开始往下的一条路径,所以不用担心路径条数的限制,也不用担心路径重合。

先看第一问。

因为我们有每个点只能经过一次的性质,所以我们考虑拆点。

每个点拆成\(A,B\)两点,\(B\)点负责接收,\(A\)点负责送出,并且我们把经过的点的贡献放到这里,从每个点的\(B\)到它的\(A\)连一条费用为当前点权的边。

\(s\rightarrow B_{1,i}:(1,0)\)

\(\forall i\ne n,A_{i,j}\rightarrow B_{i+1,j},B_{i+1,j+1}:(1,0)\)

\(A_{n,i}\rightarrow t:(1,0)\)

基本上就就是模拟建图。

再看第二问。

因为一个点的贡献可以被计算多次,所以其实是没有必要拆点的,可以直接把每个点的贡献放在连向它的那条边上。

不过既然第一问写了一个建图我们就直接在上面做一些小小的修改吧。

把每个点的\(B\)点到\(A\)点的流量改成\(m\)就行了。

再看第三问。

把\(A\rightarrow B\)和\(A\rightarrow t\)的边的流量改成\(m\)就行了。

因为第一行每个点都要经过一遍所以\(s\rightarrow B\)的边的流量可以不用修改。

#include<bits/stdc++.h>
#define IT vector<node>::iterator
#define pb emplace_back
#define ll long long
using namespace std;
const int N=1400;
int read(){int x;scanf("%d",&x);return x;}
namespace NFW
{
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int s,t,flow[N],inq[N];IT id[N];ll dis[N];
queue<int>q;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
void EK()
{
ll mincost=0;
for(int p;spfa();) for(p=t,mincost+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
for(int i=1;i<=t;++i) E[i].clear();
printf("%lld\n",-mincost);
}
}using namespace NFW;
int n,m,sum,a[27][47];
int h(int x,int y){return y+(m+m+x)*(x-1)/2;}
int main()
{
m=read()-1,n=read(),sum=n*m+n*(n+1)/2,s=2*sum+1,t=s+1;
for(int i=1,j;i<=n;++i) for(j=1;j<=i+m;++j) a[i][j]=read(); for(int i=1;i<=m+1;++i) add(s,i+sum,1,0);
for(int i=1;i<=m+n;++i) add(sum-m-n+i,t,1,0);
for(int i=1,j,p;i<=n;++i)
for(j=1;j<=i+m;++j)
{
p=h(i,j);
add(p+sum,p,1,-a[i][j]);
if(i^n) add(p,h(i+1,j)+sum,1,0),add(p,h(i+1,j+1)+sum,1,0);
}
EK(); for(int i=1;i<=m+1;++i) add(s,i+sum,1,0);
for(int i=1;i<=m+n;++i) add(sum-m-n+i,t,m+1,0);
for(int i=1,j,p;i<=n;++i)
for(j=1;j<=i+m;++j)
{
p=h(i,j);
add(p+sum,p,m+1,-a[i][j]);
if(i^n) add(p,h(i+1,j)+sum,1,0),add(p,h(i+1,j+1)+sum,1,0);
}
EK(); for(int i=1;i<=m+1;++i) add(s,i+sum,1,0);
for(int i=1;i<=m+n;++i) add(sum-m-n+i,t,m+1,0);
for(int i=1,j,p;i<=n;++i)
for(j=1;j<=i+m;++j)
{
p=h(i,j);
add(p+sum,p,m+1,-a[i][j]);
if(i^n) add(p,h(i+1,j)+sum,m+1,0),add(p,h(i+1,j+1)+sum,m+1,0);
}
EK();
}

航空路线问题

拆点是肯定得拆的,一个比较显然的想法是每个点拆成6个点。

观察到题目给我们的限制:除起点终点以外每个点最多只能经过一次。

因此我们要做的实际上就是找\(2\)条起点到终点的不交路径。

这样每个点只要拆成出点和入点就行了。

方案dfs一下就好了。

#include<bits/stdc++.h>
#define pb emplace_back
#define IT vector<node>::iterator
using namespace std;
const int N=207;
int read(){int x=0;scanf("%d",&x);return x;}
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
int n,m,s,t,dis[N],flow[N],inq[N];IT id[N];
queue<int>q;vector<node>E[N];
void add(int u,int v,int f,int c){E[u].pb(u,v,f,c,(int)E[v].size()),E[v].pb(v,u,0,-c,(int)E[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=E[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=E[u].begin();it!=E[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=E[t].begin();
}
void EK()
{
int mincost=0;
for(int p;spfa();) for(p=t,mincost+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],E[p][id[p]->p].f+=flow[t];
if(mincost) printf("%d\n",-mincost-2); else puts("No Solution!"),exit(0);
}
string str,S[N];unordered_map<string,int>mp;
void dfs1(int u)
{
cout<<S[u]<<endl;
if(u==n) return ;
for(auto it:E[u]) if(it.v!=s&&it.v>u+n&&E[it.v][it.p].f) return --E[it.v][it.p].f,dfs1(it.v-n);
}
void dfs2(int u)
{
if(u^n) cout<<S[u]<<endl;
if(u==1) return ;
for(auto it:E[u+n]) if(it.v<u&&it.f) return dfs2(it.v);
}
int main()
{
n=read(),m=read(),s=n*2+1,t=n*2+2;
for(int i=1;i<=n;++i) cin>>S[i],mp[S[i]]=i;
for(int i=1,u,v;i<=m;++i)
{
cin>>str,u=mp[str],cin>>str,v=mp[str];
if(u>v) u^=v^=u^=v;
if(u==1&&v==n) add(1,n+n,2,0);
else add(u,v+n,1,0);
}
for(int i=1;i<=n;++i) add(i+n,i,i==1||i==n? 2:1,-1);
add(s,1+n,2,0),add(n,t,2,0),EK(),dfs1(1),dfs2(n);
}

星际转移

直接做似乎不太好做,感觉费用流做不了。

我们可以转而枚举答案然后检查。

对于一种情况检查其正确性可以通过建\(T\)层图然后跑最大流。

因为用dinic可以在残量网络上直接跑不用建新图所以直接枚举答案然后建新边和新点似乎比二分答案重新建图更快。

判断无解可以用并查集。

#include<bits/stdc++.h>
using namespace std;
const int N=1007,M=9007,inf=1e9;
int read(){int x;scanf("%d",&x);return x;}
int s,t,head[N],ver[M],edge[M],Next[M],tot=1,cur[N],dep[N];queue<int>que;
void add(int u,int v,int w){ver[++tot]=v,Next[tot]=head[u],edge[tot]=w,head[u]=tot,ver[++tot]=u,Next[tot]=head[v],head[v]=tot;}
int bfs()
{
memset(dep,0x3f,sizeof dep),memcpy(cur,head,sizeof cur),dep[s]=0,que.push(s);
for(int i,u,v;!que.empty();) for(u=que.front(),que.pop(),i=head[u];i;i=Next[i]) if(dep[v=ver[i]]>inf&&edge[i]) dep[v]=dep[u]+1,que.push(v);
return dep[t]<inf;
}
int dfs(int u,int lim)
{
if(!lim||u==t) return lim;
int v,f,flow=0;
for(int&i=cur[u];i;i=Next[i]) if(dep[v=ver[i]]==dep[u]+1&&(f=dfs(v,min(lim,edge[i])))) flow+=f,lim-=f,edge[i]-=f,edge[i^1]+=f;
return flow;
}
int dinic()
{
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}
int n,m,k,st[30][30],p[30],q[30];
int main()
{
n=read(),m=read(),k=read(),s=0,t=N-1;int maxflow=0;
for(int i=1,j;i<=m;++i) for(q[i]=read(),p[i]=read(),j=0;j<p[i];++j) st[i][j]=read();
for(int i=1;i<=m;++i) for(int j=0;j<p[i];++j) if(!st[i][j]) st[i][j]=n+1;
for(int T=1,i,u,v;T<30;++T)
{
add(s,T*(n+1),inf);
for(i=1;i<=m;++i) u=(T-1)%p[i],v=T%p[i],u=(!~st[i][u]? t:(T-1)*(n+1)+st[i][u]),v=(!~st[i][v]? t:T*(n+1)+st[i][v]),add(u,v,q[i]);
while(bfs()) maxflow+=dfs(s,inf);
if(maxflow>=k) return !printf("%d",T);
for(i=1;i<=n+1;++i) add((T-1)*(n+1)+i,T*(n+1)+i,inf);
}
puts("0");
}