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");
}

网络流基础&网络流24题的更多相关文章

  1. P4480 「BJWC2018」「网络流与线性规划24题」餐巾计划问题

    刷了n次用了奇淫技巧才拿到rk1,亥 这道题是网络流二十四题中「餐巾计划问题」的加强版. 于是怀着试一试的心情用费用流交了一发: 哇塞,过了9个点!(强烈谴责出题人用*造数据 下面是费用流解法简述: ...

  2. Libre 6003 「网络流 24 题」魔术球 (网络流,最大流)

    Libre 6003 「网络流 24 题」魔术球 (网络流,最大流) Description 假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为 1,2,3,4......的球. (1)每次只 ...

  3. COGS743. [网络流24题] 最长k可重区间集

    743. [网络流24题] 最长k可重区间集 ★★★   输入文件:interv.in   输出文件:interv.out   简单对比时间限制:1 s   内存限制:128 MB «问题描述: «编 ...

  4. Cogs 14. [网络流24题] 搭配飞行员

    这道题其实蛮好想的,因为分为正,副飞行员.所以就把正飞行员当作Boy,副飞行员当作Girl.然后做Hungry即可. #include<bits/stdc++.h> using names ...

  5. 【线性规划与网络流 24题】已完成(3道题因为某些奇怪的原因被抛弃了QAQ)

    写在前面:SDOI2016 Round1滚粗后蒟蒻开始做网络流来自我拯救(2016-04-11再过几天就要考先修课,现在做网络流24题貌似没什么用←退役节奏) 做的题目将附上日期,见证我龟速刷题. 1 ...

  6. cogs_14_搭配飞行员_(二分图匹配+最大流,网络流24题#01)

    描述 http://cojs.tk/cogs/problem/problem.php?pid=14 有一些正飞行员和副飞行员,给出每个正飞行员可以和哪些副飞行员一起飞.一架飞机上必须一正一副,求最多多 ...

  7. BZOJ_1221_ [HNOI2001]_软件开发(最小费用流,网络流24题#10)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1221 n天,每天需要r个毛巾,用完以后可以洗,要么花fa洗a天,要么花fb洗b天,毛巾不够了可 ...

  8. 【网络流24题】 No.12 软件补丁问题(最小转移代价 最短路)

    [题意] T 公司发现其研制的一个软件中有 n 个错误, 随即为该软件发放了一批共 m 个补丁程序. 每一个补丁程序都有其特定的适用环境, 某个补丁只有在软件中包含某些错误而同时又不包含另一些错误时才 ...

  9. 【网络流24题】No.1 搭配飞行员(飞行员配对方案问题)

    [问题描述]     飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾驶员.由于种种原因,例如相互配合的问题,有些驾驶员不能在同一架飞机上飞 ...

随机推荐

  1. maven项目无法查看类库的源码

    一个Maven项目有两种类库,分别是JRE System Libaray和Maven Dependencies. JRE System Libaray 一般有两种方法指定. 1.由pom.xml中的m ...

  2. NSString的导出

    字符串的导出,写到某个文件中去 void stringExport(){ NSString *str=@"123456"; //if file not exist will not ...

  3. JAVA基础知识|Socket

    一.什么是Socket? Socket本身并不是协议,是一套完成TCP.UDP协议的调用接口(API),通过socket我们才能使用TCP/IP协议(JAVA基础知识|TCP/IP协议).Socket ...

  4. Go语言函数之可变参数

    package main //.... 加参数类型 func Sum(nums ...int)int{ total:=0 for _,num:=range nums{ total+=num } ret ...

  5. MySQL的概述和基础(学习整理)

    1. 数据库基本概念 数据库(DataBase,DB)是用来存储和管理数据的仓库.与其他种类存储和管理数据的方式有所不同的是,数据库是兼持久化存储数据.便捷存储管理数据.使用统一的方式操作数据库几个特 ...

  6. FIREDAC返回多结果集

    FIREDAC返回多结果集 以前使用ADO, 如果SQL返回的结果集有多个 可以通过NextRecordset来依次获取 代码移植到FireDAC, 对于多结果集处理差不多, 但是还是有一些不一样的地 ...

  7. Event---事件详解

    1.焦点事件 焦点:使浏览器能够区分用户输入的对象,当一个元素有焦点的时候,那么他就可以接收用户的输入. 可以通过以下方式给元素设置焦点: 点击.tab.js 不是所有元素都能够接收焦点的,能够响应用 ...

  8. [转][C#]AutoFac 使用方法总结

    AutoFac使用方法总结:Part I 转自:http://niuyi.github.io/blog/2012/04/06/autofac-by-unit-test/ AutoFac是.net平台下 ...

  9. LC 592. Fraction Addition and Subtraction

    Given a string representing an expression of fraction addition and subtraction, you need to return t ...

  10. linux 查看网络流量命令

    转: linux 查看网络流量命令 2019年01月31日 14:22:00 weixin_33894992 阅读数 893   sar命令参数很多,有时间man一下. -n参数很有用,他有6个不同的 ...