速通网络流的代价就是忘的巨快
总结 网络流中常出现边与点的转化
具体的,拆点以表示点必需的信息,边中插入点以表示边必需的信息
这里有一些建模,有时间去看看
最大流 理论: $\text{bfs}$ 找 $S$ 到每个点的经过最少边的路径,并对残量网络分层,根据层次反复做 $\text{dfs}$ 每次找到一条增广路并更新
对于每一条从 $x$ 出发并处理完毕的边都不需要重复只用,用 $\text{cur[x]}$ 表示上次处理 $x$ 时遍历的最后一条边($x$ 的当前弧),每次 $\text{bfs}$ 后都应将 $\text{cur}$ 初始化为 $\text{head}$
尤其在稠密图中效果显著
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 namespace Webflow{ inline int bfs (int st,int ed) { for (rei i=0 ;i<N;++i) cur[i]=head[i],depth[i]=0 ; he=1 ,ta=0 ; depth[st]=1 ,que[++ta]=st; while (he<=ta){ rei x=que[he++]; for (rei i=head[x],y;i;i=Next[i]){ if (flow[i] && !depth[ y=ver[i] ]){ depth[y]=depth[x]+1 ; que[++ta]=y; if (y==ed) return 1 ; } } } return 0 ; } inline int dfs (int x,int T,int left_flow) { if (!left_flow || x==T) return left_flow; rei tmp=0 ; for (rei i=cur[x],y,f;i;i=Next[i]){ cur[x]=i; if (depth[ y=ver[i] ]==depth[x]+1 && (f=dfs (y,T,min (left_flow-tmp,flow[i])))){ flow[i]-=f; flow[i^1 ]+=f; tmp+=f; if (!(left_flow-tmp)) break ; } } return tmp; } inline void Dinic (int S,int T) { maxflow=0 ; while (bfs (S,T)) maxflow+=dfs (S,T,INF);} } }
例题 三倍经验的三分图匹配,将两个要匹配 $n1,n2$ 的先分别与源汇点相连,再将用来匹配的一种点 $n$ 拆点以保证只选一次,再读入条件将匹配条件连边
$i:1\rightarrow n1\qquad$ add(S,i,1),add(i,S,0);
$i:1\rightarrow n2\qquad$ add(i+n1,T,1),add(T,i+n1,0);
$i:1\rightarrow n\ \ \qquad$ add(i+n1+n2,i+n+n1+n2,1),add(i+n+n1+n2,i+n1+n2,0);
$u,v\in n1\ \ \qquad$ add(v,u+n1+n2,1),add(u+n1+n2,v,0);
$u,v\in n2\ \ \qquad$ add(u+n+n1+n2,v+n1,1),add(v+n1,u+n+n1+n2,0);
有/无源汇上下界可行/最大流 对于边的上下界 $MAX,MIN$ ,一个简单的想法是整体向下偏移,将下界变为 $0$ ,上界变为 $MAX-MIN$ ,但如此的话并不满足流量守恒
设 $du_i=in_i-out_i$ ,即每个节点的入出之差
当 $du_i=0$ 那么对于该点,流量守恒
当 $du_i>0$ ,从 $S$ 向 $i$ 连一条流量 $du_i$ 的边,当 $du_i<0$ ,从 $i$ 向 $T$ 连一条 $-du_i$ 的边
那么求一次最大流,当所有附加的边均满流时(即,$du_i<=0$) 有可行解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 for (rei i=1 ,u,v,MIN,MAX;i<=m;++i){ scanf ("%d%d%d%d" ,&u,&v,&MIN,&MAX); add (u,v,MAX-MIN,i),add (v,u,0 ,0 ); du[u]-=MIN,du[v]+=MIN; ori[i]=MIN; } S=n+1 ,T=S+1 ,ori_tot=tot; for (rei i=1 ;i<=n;++i){ if (du[i]>0 ) add (S,i,du[i],0 ),add (i,S,0 ,0 ); else if (du[i]<0 ) add (i,T,-du[i],0 ),add (T,i,0 ,0 ); } Webflow::Dinic (S,T); bool flag=1 ;for (rei i=head[S];i;i=Next[i]) if (flow[i]>0 ){ flag=0 ; break ;}if (!flag) return puts ("NO" ),0 ;for (rei i=2 ;i<=ori_tot;i+=2 ) ans[ id[i] ]=flow[i^1 ];puts ("YES" );for (rei i=1 ;i<=m;++i) printf ("%d\n" ,ans[i]+ori[i]);
其实题目的意思是:求一个流使源点总流入等于汇点总流出,其他点满足流量守恒,每条边满足上下界限制,求最大的总流量
而不是直接用上界跑最大流,这一定是出题人的语文问题
如何转化为无源汇:变成循环流即可,即从源点到汇点建一条流量无限的边
再建超级源点超级汇点,可行流的判断与上题一样
第一次跑的最大流是流满下界的流,图中还剩有 $s\rightarrow t$ 的自由流
那么再删去超级源点汇点以及与之相连的边,再跑 $s\rightarrow t$ 的最大流
答案就是 $流满下界的流+剩下的自由流的最大流$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 for (rei i=1 ,u,v,MIN,MAX;i<=m;++i){ scanf ("%d%d%d%d" ,&u,&v,&MIN,&MAX); add (u,v,MAX-MIN,i),add (v,u,0 ,0 ); du[u]-=MIN,du[v]+=MIN; } S=n+1 ,T=S+1 ,ori_tot=tot; for (rei i=1 ;i<=n;++i){ if (du[i]>0 ) add (S,i,du[i],0 ),add (i,S,0 ,0 ); else if (du[i]<0 ) add (i,T,-du[i],0 ),add (T,i,0 ,0 ); } add (t,s,INF,0 ),add (s,t,0 ,0 );Webflow::Dinic (S,T); bool flag=1 ;for (rei i=head[S];i;i=Next[i]) if (flow[i]>0 ){ flag=0 ; break ;}if (!flag) return puts ("please go home to sleep" ),0 ;ans=flow[ head[t]^1 ]; for (rei i=1 ;i<=tot;++i) if (!id[i]) flow[i]=0 ;head[S]=head[T]=0 ; Webflow::Dinic (s,t); printf ("%d\n" ,ans+maxflow);
建立超级源点汇点之后跑一次最大流,$t\rightarrow s$ 建一条无限大的边,再跑一次最大流
答案就是无限大的边流过的流量:
由于整个图满足流量平衡,$t$ 的流入等于流出,由于其流出的只有无穷大的边,最小六就是使那条边流过的流量尽量小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ll MIN,MAX; for (rei i=1 ,u,v;i<=m;++i){ scanf ("%d%d%lld%lld" ,&u,&v,&MIN,&MAX); add (u,v,MAX-MIN),add (v,u,0 ); du[u]-=MIN,du[v]+=MIN; } S=n+1 ,T=S+1 ,ori_tot=tot; for (rei i=1 ;i<=n;++i){ if (du[i]>0 ) add (S,i,du[i]),add (i,S,0 ); else if (du[i]<0 ) add (i,T,-du[i]),add (T,i,0 ); } Webflow::Dinic (S,T); add (t,s,INF),add (s,t,0 );Webflow::Dinic (S,T); bool flag=1 ;for (rei i=head[S];i;i=Next[i]) if (flow[i]>0 ){ flag=0 ; break ;}if (!flag) return puts ("please go home to sleep" ),0 ;printf ("%lld\n" ,flow[tot]);
最小割 理论
最大流最小割定理:
网络中最大流量等于最小割中割边的容量之和
证略
例题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void re_dfs (int x) { vis[x]=1 ; for (rei i=head[x],y;i;i=Next[i]) if (flow[i]>0 && !vis[ y=ver[i] ]) re_dfs (y); } int main () { scanf ("%d%d%d%d" ,&n,&m,&S,&T); T+=n; for (rei i=1 ,x;i<=n;++i) scanf ("%d" ,&x),add (i,i+n,x),add (i+n,i,0 ); for (rei i=1 ,x,y;i<=m;++i) scanf ("%d%d" ,&x,&y),add (x+n,y,INF),add (y,x+n,0 ),add (y+n,x,INF),add (x,y+n,0 ); Webflow::Dinic (S,T); re_dfs (S); for (rei i=2 ;i<tot;i+=2 ) if (vis[ ver[i^1 ] ] && !vis[ ver[i] ]) ans.push_back (ver[i^1 ]); sort (ans.begin (),ans.end ()); for (rei i=0 ,SS=ans.size ()-1 ;i<=SS;++i) printf ("%d%c" ,ans[i],i==SS ? 10 : 32 ); getchar (),getchar (); return 0 ; }
最大权闭合子图 理论 对于一个有向连通图,每个点带有一个权值,建超级源点汇点,将所有正点权的点连到 $S$ 上,负点权的点连到 $T$ 上,边权为点权的绝对值
特殊的,点权为 $0$ 的点可以被忽略掉,而原图中的边权值设为 $INF$
该图有以下性质:
关于 $S-T$ 的最小割是简单割,即,割集中所有边都与 $S,T$ 相连接
简单割产生的两个子图中,有点 $S$ 的图是闭合图,即,图中任一点的的出边指向的终点仍在该图中
最小割产生的 $图S、图T$ 中,$图S$ 为最大权闭合子图
那么对于有点权的图,求解最大点权的闭合子图步骤为:
记录整个图中所有正点权之和 $sum$
建立流网络,求 $S-T$ 最大流 $maxflow$
最大权闭合子图的权值和即为 $sum-maxflow$
考虑题中的条件得:植物 $i$ 能被攻击当且仅当植物 $i+1$ 与保护 $i$ 的植物都被攻击
与最大权闭合子图中:每个点出边指向的点都属于闭合子图相仿
那么将图转置,所有能被攻击的植物构成一个闭合子图,
同时,注意拓扑判环删去无敌的点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 const int N=1100 ,M=1e6 +100 ;const int INF=0x3f3f3f3f ;int n,m,S,T;int que[N],he,ta;int head[N],ver[M],Next[M],flow[M],cur[M],tot=1 ,depth[N];int maxflow,sum;int vis[N],in[N],score[N];vector<int > out[N]; queue<int > q; inline int get_id (int i,int j) { return i*31 +j;}inline void topsort () { for (rei i=1 ;i<=n;++i) for (rei j=1 ;j<=m;++j){ rei id=get_id (i,j); if (!in[id]) q.push (id),vis[id]=1 ; } while (q.size ()){ rei x=q.front (); q.pop (); for (rei i=0 ,SS=**out**[x].size ()-1 ;i<=SS;++i){ rei y=out[x][i]; --in[y]; if (!vis[y] && !in[y]) q.push (y),vis[y]=1 ; } } } int main () { scanf ("%d%d" ,&n,&m); S=N-1 ,T=N-2 ; for (rei i=1 ;i<=n;++i) for (rei j=1 ,w;j<=m;++j){ scanf ("%d%d" ,&score[ get_id (i,j) ],&w); for (rei k=1 ,r,c;k<=w;++k){ scanf ("%d%d" ,&r,&c),++r,++c; out[ get_id (i,j) ].push_back (get_id (r,c)); ++in[ get_id (r,c) ]; } if (j<m){ out[ get_id (i,j+1 ) ].push_back (get_id (i,j)); ++in[ get_id (i,j) ]; } } topsort (); for (rei i=1 ;i<=n;++i) for (rei j=1 ;j<=m;++j){ if (!in[ get_id (i,j) ]){ rei id=get_id (i,j); if (!vis[id]) continue ; if (score[id]>=0 ){ add (S,id,score[id]),add (id,S,0 ); sum+=score[id]; } else add (id,T,-score[id]),add (T,id,0 ); for (rei k=0 ,SS=out[id].size ()-1 ;k<=SS;++k){ rei y=out[id][k]; if (vis[y]) add (y,id,INF),add (id,y,0 ); } } } Webflow::Dinic (S,T); printf ("%d\n" ,sum-maxflow); getchar (),getchar (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { scanf ("%d%d" ,&n,&m); S=n+m+1 ,T=S+1 ; for (rei i=1 ,p;i<=n;++i) scanf ("%d" ,&p),add (i,T,p),add (T,i,0 ); for (rei i=1 ,a,b,c;i<=m;++i){ scanf ("%d%d%d" ,&a,&b,&c); sum+=c; add (S,n+i,c),add (n+i,S,0 ); add (n+i,a,INF),add (a,n+i,0 ); add (n+i,b,INF),add (b,n+i,0 ); } Webflow::Dinic (S,T); printf ("%lld\n" ,sum-maxflow); getchar (),getchar (); return 0 ; }
$n$ 种寿司,每种的类型为 $a_i$ ,吃了第 $i\sim j$ 种寿司会获得 $d_{i,j}$ 的收益,吃了 $c$ 种类型为 $x$ 的寿司会付出 $mx^2+cx$ 的代价,最大化收益与代价差
考虑如何建模:对于每个 $d_{i,j}$ 都看成一种物品,那么选择 $d_{i,j}$ 的条件是已经选择 $d_{i,j+1},d_{i-1,j}$
对于付出的代价,转化为吃每种类型 $x$ 的寿司需付出 $x$ 的代价,吃过类型 $x$ 的寿司需付出 $mx^2$ 的代价
那么选择 $d_{i,i}$ 即表示吃掉第 $i$ 种寿司,需要付出 $a_i$ 的代价,以及 $m\times a_i^2$ 的代价,即,将每个类型也看成物品,选择收益 $d_{i,i}$ 的条件是选择类型 $a_i$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int main () { scanf ("%d%d" ,&n,&m); for (rei i=1 ;i<=n;++i) scanf ("%d" ,&a[i]),MAX=max (MAX,a[i]); S=0 ,T=N-1 ; for (rei i=1 ;i<=n;++i) for (rei j=i;j<=n;++j) scanf ("%d" ,&f[i][j]),id[i][j]=++cnt; for (rei i=1 ;i<=n;++i) for (rei j=i;j<=n;++j){ rei cost=f[i][j]; if (i==j){ if (m) Webflow::add (id[i][j],a[i]+cnt,INF); cost-=a[i]; } else { Webflow::add (id[i][j],id[i+1 ][j],INF); Webflow::add (id[i][j],id[i][j-1 ],INF); } if (cost>0 ) Webflow::add (S,id[i][j],cost),ans+=cost; else if (cost<0 ) Webflow::add (id[i][j],T,-cost); } if (m) for (rei i=1 ;i<=MAX;++i) Webflow::add (++cnt,T,i*i); Webflow::Dinic (S,T); printf ("%lld\n" ,ans-maxflow); getchar (),getchar (); return 0 ; }
$n$ 个城市,存在一些合作 $(x,y)$ ,对于两两都在合作的一些城市称为城市群,保证当前情况下只有最多两个城市群。选出两个之前不合作的城市并使其合作,要求最大城市群大小至少比之前的大 $1$
求所有满足该条件的两两城市
一个很显然的想法是建反图,即,在反图上没有边的点之间存在一条边
那么原图的一个城市群两两有边,对应到反图上两两无边,即对应反图的一个独立集,又由于原图最多两个群,那么反图即是一个二分图
问题转化为,求如何删去反图上一条边使反图的独立集变大
不难有:二分图最大独立集 $=$ 总点数 $-$ 最小点覆盖,其中最小点覆盖 $=$ 二分图最大匹配
那么转化为哪些边必定出现在二分图最大匹配中
那么求最大流后对残量网络缩点,必定有的边即是满流且两点不在同一连通块内的点
证明显然,即,若在同一连通块内则一定有另一匹配边可以替换
注意在 $\text{tarjan}$ 缩点的过程中不去缩满流的边,即 if(!flow[i]) continue;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { scanf ("%d%d" ,&n,&m); for (rei i=1 ,u,v;i<=m;++i) scanf ("%d%d" ,&u,&v),G[u].push_back (v),G[v].push_back (u); for (rei i=1 ;i<=n;++i) if (!col[i]) paint (i,2 ); S=0 ,T=n+1 ; for (rei i=1 ;i<=n;++i){ if (col[i]==2 ){ Webflow::add (S,i,1 ); for (auto v:G[i]) Webflow::add (i,v,1 );} else Webflow::add (i,T,1 ); } Webflow::Dinic (S,T); for (rei i=S;i<=T;++i) if (!low[i]) tarjan (i); for (rei x=1 ;x<=n;++x) if (col[x]==2 ) for (rei i=head[x];i;i=Next[i]){ rei y=ver[i]; if (flow[i] || y==S || y==T) continue ; if (scc[x]!=scc[y]) ans.push_back (x<y ? mk (x,y) : mk (y,x)); } sort (ans.begin (),ans.end ()); printf ("%d\n" ,ans.size ()); for (auto x:ans) printf ("%d %d\n" ,x.first,x.second); getchar (),getchar (); return 0 ; }
最小割树
给定 $n$ 个点的无向连通图,对于其中所有点对的最小割容量,一共能得到 $\frac{n\times (n-1)}{2}$ 个数,求有多少互不相同的
对于一个最小割,它会将图分为两个连通块,联想到树上删边后整棵树分为两个连通块
那么若将最小割视为一条边,那么可以考虑将最小割之间的关系用树表示,即:
对于树上任意两个相邻节点 $S,T$ ,确保删去树上的边 $(S,T)$ 后两个不连通点集与原图上执行 $S,T$ 的最小割之后两个不连通点集的点的分布情况相同,且使边 $(S,T)$ 的边权为最小割
为了满足树的情况,所以需要至少 $n-1$ 个点满足上述条件,此时,对于路径 $(u,v)$ ,枚举路径上的每一条边,最小割即是边权最小的边的权值
那么用 set
维护一下个数,size
即是所求
注意建边的时候建双向边且权值均为 val
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int vis[N],p[N];set<int > s; void dfs (int x) { vis[x]=1 ; for (rei i=head[x];i;i=Next[i]){ rei y=ver[i]; if (!vis[y] && flow[i]) dfs (y); } } inline void clear () { memcpy (flow,ori_flow,sizeof flow); memset (vis,0 ,sizeof vis);}inline void solve (int ss,int tt) { clear (); maxflow=0 ; Webflow::Dinic (ss,tt); s.insert (maxflow); dfs (ss); } int main () { scanf ("%d%d" ,&n,&m); for (rei i=1 ,u,v,w;i<=m;++i) scanf ("%d%d%d" ,&u,&v,&w),Webflow::add (u,v,w); for (rei i=2 ;i<=n;++i) p[i]=1 ; for (rei i=2 ;i<=n;++i){ S=i,T=p[i]; solve (S,T); for (rei j=i;j<=n;++j) if (p[j]==T && vis[j]) p[j]=S; } printf ("%d\n" ,s.size ()); getchar (),getchar (); return 0 ; }
最小割树的经典应用
分治暴力更新点对答案即可
注意 dfs
的时候需要传参而不是直接用全局的 T
qwq
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <bits/stdc++.h> #define rei register int using namespace std;const int N=210 ,M=4e4 +100 ;const int INF=0x3f3f3f3f ;int n,m;int que[N],he,ta;int head[N],Next[M],ver[M],cur[N],tot=1 ,depth[N],flow[M],ori_flow[M];int vis[N],p[N],ans[N][N],node[N];namespace Webflow{ inline void add (int u,int v,int val) { ver[++tot]=v,Next[tot]=head[u],head[u]=tot; flow[tot]=ori_flow[tot]=val; ver[++tot]=u,Next[tot]=head[v],head[v]=tot; flow[tot]=ori_flow[tot]=val; } inline int bfs (int st,int ed) { for (rei i=0 ;i<N;++i) cur[i]=head[i],depth[i]=0 ; he=1 ,ta=0 ; depth[st]=1 ,que[++ta]=st; while (he<=ta){ rei x=que[he++]; for (rei i=head[x],y;i;i=Next[i]){ if (flow[i] && !depth[ y=ver[i] ]){ depth[y]=depth[x]+1 ; que[++ta]=y; if (y==ed) return 1 ; } } } return 0 ; } int dfs (int x,int T,int left_flow) { if (!left_flow || x==T) return left_flow; rei tmp=0 ; for (rei i=cur[x],y,f;i;i=Next[i]){ cur[x]=i; if (depth[ y=ver[i] ]==depth[x]+1 && (f=dfs (y,T,min (left_flow-tmp,flow[i])))){ flow[i]-=f,flow[i^1 ]+=f; tmp+=f; if (!(left_flow-tmp)) break ; } } return tmp; } inline int Dinic (int S,int T,int res=0 ) { while (bfs (S,T)) res+=dfs (S,T,INF); return res;} } void dfs (int x) { vis[x]=1 ; for (rei i=head[x];i;i=Next[i]){ rei y=ver[i]; if (!vis[y] && flow[i]) dfs (y); } } inline void clear () { memcpy (flow,ori_flow,sizeof flow); memset (vis,0 ,sizeof vis);}inline void init () { memset (head,0 ,sizeof head); tot=1 ; memset (flow,0 ,sizeof flow); memset (ori_flow,0 ,sizeof ori_flow); memset (ver,0 ,sizeof ver); memset (Next,0 ,sizeof Next); memset (ans,0x3f ,sizeof ans); for (rei i=1 ;i<=n;++i) node[i]=i; } void solve (int l,int r) { if (l==r) return ; clear (); int t[2 ][N]={0 }; rei tmp=Webflow::Dinic (node[l],node[r]); dfs (node[l]); for (rei i=1 ;i<=n;++i) if (vis[i]) for (rei j=1 ;j<=n;++j) if (!vis[j]) ans[i][j]=ans[j][i]=min (ans[i][j],tmp); for (rei i=l;i<=r;++i) t[ vis[node[i]] ][ ++t[vis[node[i]]][0 ] ]=node[i]; for (rei i=l,j=1 ;j<=t[0 ][0 ];++j,++i) node[i]=t[0 ][j]; for (rei i=l+t[0 ][0 ],j=1 ;j<=t[1 ][0 ];++j,++i) node[i]=t[1 ][j]; solve (l,l+t[0 ][0 ]-1 ),solve (l+t[0 ][0 ],r); } int main () { rei T; scanf ("%d" ,&T); while (T--){ scanf ("%d%d" ,&n,&m); init (); for (rei i=1 ,u,v,w;i<=m;++i) scanf ("%d%d%d" ,&u,&v,&w),Webflow::add (u,v,w); solve (1 ,n); rei q; scanf ("%d" ,&q); for (rei k=1 ,x;k<=q;++k){ scanf ("%d" ,&x); rei res=0 ; for (rei i=1 ;i<=n;++i) for (rei j=i+1 ;j<=n;++j) if (ans[i][j]<=x) ++res; printf ("%d\n" ,res); } puts ("" ); } getchar (),getchar (); return 0 ; }
比上面的更简单,对于询问 u v
直接输出 ans[u][v]
即可
板子,输出所有 ans
即可,注意特判 i=j
时输出 0
好吧其实这全是板子题,因为没考过所以有懒狗不去练其他的题了qwq
ZKW费用流 板子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 namespace ZKW{ bool flag=0 ; bool SPFA (int S,int T,int n) { memset (dis,INF,sizeof dis); memset (vis,0 ,sizeof vis); memset (level,0 ,sizeof level); dis[S]=0 ,level[S]=1 ,vis[S]=true ; deque<int > q; q.push_back (S); while (q.size ()){ rei x=q.front (); q.pop_front (); vis[x]=0 ; for (rei i=head[x],y;i;i=Next[i]){ if (dis[ y=ver[i] ]>dis[x]+cost[i] && flow[i]>0 ){ dis[y]=dis[x]+cost[i]; level[y]=level[x]+1 ; if (!vis[y]){ vis[y]=1 ; (q.size () && dis[y]<dis[ q.front () ]) ? q.push_front (y) : q.push_back (y); } } } } return dis[T]!=dis[n+1 ]; } int dfs (int x,int T,int t,ll &fflow,ll &ccost) { if (x==T) return fflow+=t,flag=1 ,t; rei num=0 ,newflow=0 ; for (rei i=head[x],y;i;i=Next[i]){ if (t==num) break ; if (dis[x]+cost[i]==dis[ y=ver[i] ] && level[x]+1 ==level[y] && flow[i]>0 ){ newflow=dfs (y,T,min (t-num,flow[i]),fflow,ccost); num+=newflow; ccost+=newflow*cost[i]; flow[i]-=newflow; flow[i^1 ]+=newflow; } } return num; } void costflow (int S,int T,int n) { while (SPFA (S,T,n)){ flag=1 ; while (flag){ flag=0 ; dfs (S,T,INF,maxflow,mincost); } } } }
$K$ 架飞机,$N$ 个机场,$M$ 个要求,请求在 $s$ 时刻从 $a$ 起飞,$t$ 时刻降落到 $b$ ,获利 $c$ ,求总收益最大
最大费用最大流,存负边权即可
考虑建模对象:点或请求
以点来建边的话需要对时间讨论,不如选择请求建边,可以处理请求与请求的关系
对于源点
判断从起始点 $1$ 是否能直达请求的起点,建流量 $INF$ ,费用 $w_{1,qa}$,有
if(q[i].s>=t[1][ q[i].a ]) add(Fake_S,i,INF,w[1][ q[i].a ]),add(i,Fake_S,0,-w[1][ q[i].a ]);
由于 $k$ 架飞机,再建真正的源点,向 $1$ 连边,流量 $k$ 费用 $0$
add(S,Fake_S,k,0),add(Fake_S,S,0,0);
对于汇点
判断请求结束后能不能再规定时间内回到 $1$ ,有
if(q[i].t+t[ q[i].b ][1]<=tim) add(i+m,T,INF,w[ q[i].b ][1]),add(T,i+m,0,-w[ q[i].b ][1]);
请求的转移
对于每个请求的结束地 $qi_b$,枚举所有的请求,找是否有从结束点 $qi_a$ 赶到起始点 $qj_a$ 后能赶上的情况
for(rei j=1;j<=m;++j) if(i!=j && q[i].t+t[ q[i].b ][ q[j].a ]<=q[j].s) add(i+m,j,INF,w[ q[i].b ][ q[j].a ]),add(j,i+m,0,-w[ q[i].b ][ q[j].a ]);
注意每个请求只能进行一次
套路的对进行请求拆点,流量设为 $1$
add(i,i+m,1,-q[i].c),add(i+m,i,0,q[i].c);
1 2 3 4 5 6 7 8 9 10 11 12 13 S=0 ,T=2 *m+2 ,Fake_S=T-1 ; for (rei i=1 ;i<=n;++i) for (rei j=1 ;j<=n;++j) scanf ("%d" ,&t[i][j]);for (rei i=1 ;i<=n;++i) for (rei j=1 ;j<=n;++j) scanf ("%d" ,&w[i][j]);for (rei i=1 ;i<=m;++i) scanf ("%d%d%d%d%d" ,&q[i].a,&q[i].b,&q[i].s,&q[i].t,&q[i].c),++q[i].a,++q[i].b;for (rei i=1 ;i<=m;++i){ add (i,i+m,1 ,-q[i].c),add (i+m,i,0 ,q[i].c); if (q[i].t+t[ q[i].b ][1 ]<=tim) add (i+m,T,INF,w[ q[i].b ][1 ]),add (T,i+m,0 ,-w[ q[i].b ][1 ]); if (q[i].s>=t[1 ][ q[i].a ]) add (Fake_S,i,INF,w[1 ][ q[i].a ]),add (i,Fake_S,0 ,-w[1 ][ q[i].a ]); for (rei j=1 ;j<=m;++j) if (i!=j && q[i].t+t[ q[i].b ][ q[j].a ]<=q[j].s) add (i+m,j,INF,w[ q[i].b ][ q[j].a ]),add (j,i+m,0 ,-w[ q[i].b ][ q[j].a ]); } add (S,Fake_S,k,0 ),add (Fake_S,S,0 ,0 );ZKW::costflow ();
一张有向图,边有容量与扩容费用 $w$,其中 $w$ 表示将容量扩大 $1$ 所需的费用,求不扩容情况下 $1$ 到 $n$ 的最大流,以及最大流增加 $k$ 的最小扩容费用
对于第一问,显然跑费用为 $0$ 的费用流即可
考虑第二问需要的增加 $k$ ,有一个很好的 $\text{trick}$ 是:对于原汇点 $T$ ,建 $TT$ ,并连 $T\rightarrow TT$ ,其容量设为 $maxflow+k$ ,费用为 $0$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { scanf ("%d%d%d" ,&n,&m,&k); S=1 ,T=n; TT=n+1 ; for (rei i=1 ;i<=m;++i){ scanf ("%d%d%d%d" ,&U[i],&V[i],&cap[i],&val[i]); ZKW::add (U[i],V[i],cap[i],0 ); } ZKW::costflow (S,T,n); printf ("%lld " ,maxflow); clear (); for (rei i=1 ;i<=m;++i){ ZKW::add (U[i],V[i],cap[i],0 ); ZKW::add (U[i],V[i],INF,val[i]); } ZKW::add (T,TT,maxflow+k,0 ); maxflow=0 ,mincost=0 ; ZKW::costflow (S,TT,n+1 ); printf ("%lld\n" ,mincost); getchar (),getchar (); return 0 ; }
神奇的建模
给定序列 $A$ ,对于 $A_i$ 有删除代价 $b_i$ 和附加值 $c_i$ ,删除若干项使 $A$ 的最长上升子序列长度减少至少 $1$ ,且付出代价之和最小并输出方案。对于多种方案,输出按删去项附加值大小排序后字典序最小的情况
一个思路是:用一个流量表示一个 $\text{LIS}$
设 $dp_i$ 表示以 $i$ 为结尾的最长上升子序列长度,那么显然位置 $i$ 在 $\text{LIS}$ 中只能位于第 $dp_i$ 个位置,即 $\text{LIS}$ 的下标序列 $pos$ 一定满足 $dp_{pos_i}=i$
对于所有满足 $i<j$ 且 $dp_i=dp_j-1$ 的位置连 $i\rightarrow j$ ,设 $\text{LIS}$ 长度为 $len$ ,那么任意一条 $\text{LIS}$ 可被表示为路径 $dp_s=1 \Rightarrow dp_t=len$ ,那么要破坏所有这种路径
那么显然可以拆点,并将边流量设为 $b_i$ ,那么最小割即为最小破坏代价
对于 $dp_i=1$ ,连 $S\rightarrow i$ ,对于 $dp_j=len$ ,连 $j\rightarrow T$
对于所有满足 $i<j 且 dp_i=dp_j-1$ ,连 $i\rightarrow j$ ,流量为 $INF$ ,即不可破坏该路径
那么 $S\rightarrow T$ 的最小割即为最小代价
对于附加值大小,对 $c$ 数组排序,考虑每条边能否成为割边,即,从该点的入点能否不通过流量为 $0$ 的点到达出点,用 bfs
即可
强制使其为割边,将所有边复原后,将其与反向边流量均置为 $0$ ,再跑 $S\rightarrow T$ 最小割得到新图的最小割
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 int a[N],dp[N],MAX,b[N];struct node { int val,id; friend bool operator <(const node &a,const node &b){ return a.val<b.val;} }c[N]; inline void clear () { memset (head,0 ,sizeof head),memset (ver,0 ,sizeof ver),memset (Next,0 ,sizeof Next),memset (flow,0 ,sizeof flow); tot=1 ; MAX=0 ; } signed main () { rei TT; scanf ("%lld" ,&TT); while (TT--){ clear (); scanf ("%lld" ,&n); S=0 ,T=(n<<1 )+1 ; for (rei i=1 ;i<=n;++i) scanf ("%lld" ,&a[i]); for (rei i=1 ;i<=n;++i){ dp[i]=1 ; for (rei j=1 ;j<i;++j) if (a[i]>a[j]) dp[i]=max (dp[i],dp[j]+1 ); MAX=max (MAX,dp[i]); } for (rei i=1 ,x;i<=n;++i) scanf ("%lld" ,&x),Webflow::add (i,i+n,x); for (rei i=1 ;i<=n;++i){ if (dp[i]==1 ) Webflow::add (S,i,INF); if (dp[i]==MAX) Webflow::add (i+n,T,INF); for (rei j=i+1 ;j<=n;++j) if (a[i]<a[j] && dp[j]==dp[i]+1 ) Webflow::add (i+n,j,INF); } for (rei i=1 ,x;i<=n;++i) scanf ("%lld" ,&x),c[i]=(node){x,i}; sort (c+1 ,c+1 +n); maxflow=Webflow::Dinic (S,T); rei num=0 ; for (rei i=1 ;i<=n;++i){ rei x=c[i].id,y=x+n; if (!Webflow::bfs (x,y)){ b[++num]=x,flow[x<<1 ]=flow[(x<<1 )+1 ]=0 ; for (rei i=2 ;i<=tot;i+=2 ) flow[i]+=flow[i^1 ],flow[i^1 ]=0 ; while (Webflow::bfs (S,T)) Webflow::dfs (S,T,INF); } } sort (b+1 ,b+1 +num); printf ("%lld %lld\n" ,maxflow,num); for (rei i=1 ;i<=num;++i) printf ("%lld%c" ,b[i],i==num ? 10 : 32 ); } getchar (),getchar (); return 0 ; }
题面好长不写了
自然的想法是将三维点 $(x,y,z)$ 用二维的 $(x-z,y-z)$ 表示,不难发现这样符合题面,具体的,用 map
维护点编号
要求剩余总合最大显然求最小割,并拿总价值减去
对于一个能量源,对其周围的六个点黑白染色,形成共振当且仅当相邻的黑点白点能量源点均存在
那么每个点进行拆点,相邻的三类点之间用 $INF$ 连接,最小割即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 inline int get_mod (int x) { return ((X[x]+Y[x])%3 +3 )%3 ;}int main () { S=0 ,T=N-1 ; scanf ("%d" ,&n); for (rei i=1 ,v;i<=n;++i){ scanf ("%d%d%d%d" ,&X[i],&Y[i],&Z[i],&v); X[i]-=Z[i],Y[i]-=Z[i]; v=((X[i]+Y[i])%3 +3 )%3 ? v*10 : v*11 ; ans+=v; if (!mp[ mk (X[i],Y[i]) ]) mp[ mk (X[i],Y[i]) ]=++mp_cnt; val[ mp[ mk (X[i],Y[i]) ] ]+=v; } for (rei i=1 ;i<=n;++i){ rei now=mp[ mk (X[i],Y[i]) ]; if (vis[now]) continue ; vis[now]=1 ; Webflow::add (now,now+n,val[now]); if (get_mod (i)==1 ) Webflow::add (S,now,INF); else if (get_mod (i)==2 ) Webflow::add (now+n,T,INF); else { rei tmp_s,tmp_t ; tmp_s=mp[ mk (X[i]-1 ,Y[i]-1 ) ]; if (tmp_s) Webflow::add (tmp_s+n,now,INF); tmp_s=mp[ mk (X[i]+1 ,Y[i]) ]; if (tmp_s) Webflow::add (tmp_s+n,now,INF); tmp_s=mp[ mk (X[i],Y[i]+1 ) ]; if (tmp_s) Webflow::add (tmp_s+n,now,INF); tmp_t =mp[ mk (X[i]+1 ,Y[i]+1 )]; if (tmp_t ) Webflow::add (now+n,tmp_t ,INF); tmp_t =mp[ mk (X[i]-1 ,Y[i]) ]; if (tmp_t ) Webflow::add (now+n,tmp_t ,INF); tmp_t =mp[ mk (X[i],Y[i]-1 ) ]; if (tmp_t ) Webflow::add (now+n,tmp_t ,INF); } } ans-=Webflow::Dinic (S,T); printf ("%.1lf\n" ,(double ) ans/10.0 ); getchar (),getchar (); return 0 ; }