用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:图论_3

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:jxm2001:图论_3 [2020/07/20 23:16]
jxm2001
2020-2021:teams:legal_string:jxm2001:图论_3 [2020/07/27 22:59] (当前版本)
jxm2001 ↷ 页面2020-2021:teams:legal_string:图论_3被移动至2020-2021:teams:legal_string:jxm2001:图论_3
行 2: 行 2:
  
 ===== 网络流经典例题 ===== ===== 网络流经典例题 =====
 +
 +==== 方格取数问题 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P2774|洛谷p2774]]
 +
 +=== 题意 ===
 +
 +给定一个 $n\times m$的方格图,每个方格中都有一个正整数。
 +
 +现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
 +
 +=== 题解 ===
 +
 +由于每个方格限制较少,故考虑利用限制建边。
 +
 +考虑先取所有方格,再删去权值和最小的一组方格,使得剩下的方格没有公共边。
 +
 +问题转化为建立一个模型,支持以下操作:
 +
 +  * 支持删除元素,且删除代价为元素点权
 +  * 保证操作最优或者操作可逆
 +  * 最终状态为剩余元素没有冲突
 +
 +发现可以将方格进行黑白二染色,显然同色方格间没有限制不连边,所以可以得到二分图。
 +
 +从源点向每个黑格连一条边,容量为黑格点权。如果删去该边,表示删除黑格。
 +
 +从白格向汇点连一条边,容量为白格点权。如果删去该边,表示删除白格。
 +
 +最后从黑格向有冲突的白格连一条边,容量待定。
 +
 +问题转化为最小割问题。
 +
 +事实上网络流图不连通等价于没有从源点到黑格,再经过冲突边到白格,最后到汇点的路径。
 +
 +这又等价于不会同时选择冲突的黑格和白格,即最终的目的。
 +
 +然后需要保证最小割只选择源点到黑格的边和白格到汇点的边,所以把黑格向有冲突的白格连的边容量设为 $\inf$。
 +
 +最后求最小割,答案为点权总和减去最小割。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXS=105,​MAXN=MAXS*MAXS,​MAXM=6*MAXN,​Inf=0x7fffffff;​
 +const int dir[][2]={{0,​1},​{0,​-1},​{1,​0},​{-1,​0}};​
 +struct Edge{
 + int to,​cap,​next;​
 + Edge(int to=0,int cap=0,int next=0){
 + this->​to=to;​
 + this->​cap=cap;​
 + this->​next=next;​
 + }
 +}edge[MAXM<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号 ​
 +void Insert(int u,int v,int c){
 + edge[edge_cnt]=Edge(v,​c,​head[u]);​
 + head[u]=edge_cnt++;​
 + edge[edge_cnt]=Edge(u,​0,​head[v]);​
 + head[v]=edge_cnt++;​
 +}
 +struct Dinic{
 + int s,t;
 + int pos[MAXN],​vis[MAXN],​dis[MAXN];​
 + bool bfs(int k){
 + queue<​int>​q;​
 + q.push(s);​
 + vis[s]=k,​dis[s]=0,​pos[s]=head[s];​
 + while(!q.empty()){
 + int u=q.front();​q.pop();​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(vis[v]!=k&&​edge[i].cap){
 + vis[v]=k,​dis[v]=dis[u]+1,​pos[v]=head[v];​
 + q.push(v);​
 + if(v==t)
 + return true;
 + }
 + }
 + }
 + return false;
 + }
 + int dfs(int u,int max_flow){
 + if(u==t||!max_flow)
 + return max_flow;
 + int flow=0,​temp_flow;​
 + for(int &​i=pos[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(dis[u]+1==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){
 + edge[i].cap-=temp_flow;​
 + edge[i^1].cap+=temp_flow;​
 + flow+=temp_flow;​
 + max_flow-=temp_flow;​
 + if(!max_flow)
 + break;
 + }
 + }
 + return flow;
 + }
 + int Maxflow(int s,int t){
 + this->​s=s;​this->​t=t;​
 + int ans=0,k=0;
 + mem(vis,​0);​
 + while(bfs(++k))
 + ans+=dfs(s,​Inf);​
 + return ans;
 + }
 +}solver;
 +int n,​m,​a[MAXS][MAXS];​
 +int Id(int r,int c){
 + return (r-1)*m+c;
 +}
 +bool in(int r,int c){return r>​0&&​r<​=n&&​c>​0&&​c<​=m;​}
 +int main()
 +{
 + n=read_int(),​m=read_int();​
 + int sum=0,​s=Id(n,​m)+1,​t=s+1;​
 + Clear();
 + _rep(i,​1,​n)
 + _rep(j,​1,​m){
 + a[i][j]=read_int(),​sum+=a[i][j];​
 + if((i+j)&​1)
 + Insert(Id(i,​j),​t,​a[i][j]);​
 + else{
 + Insert(s,​Id(i,​j),​a[i][j]);​
 + _for(k,​0,​4){
 + int ti=i+dir[k][0],​tj=j+dir[k][1];​
 + if(in(ti,​tj))
 + Insert(Id(i,​j),​Id(ti,​tj),​Inf);​
 + }
 + }
 + }
 + sum-=solver.Maxflow(s,​t);​
 + enter(sum);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +=== 练习题 ===
 +
 +[[https://​www.luogu.com.cn/​problem/​P2762|洛谷p2762]]
 +
 +== 题意 ==
 +
 +给定 $n$ 个可选择实验,$m$ 个实验器材。完成第 $i$ 个实验可以得到 $p_i$ 元奖金,购买第 $j$ 个实验器材需要花费 $c_j$ 元。
 +
 +完成每个实验需要若干个器材(允许多个实验共用一个器材),问最大利益及购买方案。
 +
 +== 题解 ==
 +
 +先收取所有实验的奖金。然后考虑要么舍弃实验奖金,要么购买相应器材。
 +
 +剩下思路类似方格取数。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=200,​MAXM=3000,​Inf=0x7fffffff;​
 +struct Edge{
 + int to,​cap,​next;​
 + Edge(int to=0,int cap=0,int next=0){
 + this->​to=to;​
 + this->​cap=cap;​
 + this->​next=next;​
 + }
 +}edge[MAXM<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号 ​
 +void Insert(int u,int v,int c){
 + edge[edge_cnt]=Edge(v,​c,​head[u]);​
 + head[u]=edge_cnt++;​
 + edge[edge_cnt]=Edge(u,​0,​head[v]);​
 + head[v]=edge_cnt++;​
 +}
 +struct Dinic{
 + int s,t;
 + int pos[MAXN],​vis[MAXN],​dis[MAXN];​
 + bool bfs(int k){
 + queue<​int>​q;​
 + q.push(s);​
 + vis[s]=k,​dis[s]=0,​pos[s]=head[s];​
 + while(!q.empty()){
 + int u=q.front();​q.pop();​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(vis[v]!=k&&​edge[i].cap){
 + vis[v]=k,​dis[v]=dis[u]+1,​pos[v]=head[v];​
 + q.push(v);​
 + if(v==t)
 + return true;
 + }
 + }
 + }
 + return false;
 + }
 + int dfs(int u,int max_flow){
 + if(u==t||!max_flow)
 + return max_flow;
 + int flow=0,​temp_flow;​
 + for(int &​i=pos[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(dis[u]+1==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){
 + edge[i].cap-=temp_flow;​
 + edge[i^1].cap+=temp_flow;​
 + flow+=temp_flow;​
 + max_flow-=temp_flow;​
 + if(!max_flow)
 + break;
 + }
 + }
 + return flow;
 + }
 + int Maxflow(int s,int t){
 + this->​s=s;​this->​t=t;​
 + int ans=0,k=0;
 + mem(vis,​0);​
 + while(bfs(++k))
 + ans+=dfs(s,​Inf);​
 + return ans;
 + }
 +}solver;
 +char buf[MAXN];
 +bool vis[MAXN];
 +void dfs(int u){
 + vis[u]=true;​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(vis[v]||edge[i].cap==0)
 + continue;
 + dfs(v);
 + }
 +}
 +int main()
 +{
 + Clear();
 + int n=read_int(),​m=read_int(),​s=n+m+1,​t=s+1;​
 + int c,​pos,​p,​sum=0;​
 + _rep(i,​1,​n){
 + sum+=c=read_int();​
 + Insert(s,​i,​c);​
 + gets(buf);​
 + pos=0;
 + while(sscanf(buf+pos,"​%d",&​p)==1){
 + Insert(i,​p+n,​Inf);​
 + if(p==0)
 + pos++;
 + else{
 + while(p){
 + p/=10;
 + pos++;
 + }
 + }
 + while(buf[pos]=='​ ')
 + pos++;
 + }
 + }
 + _rep(i,​1,​m)
 + Insert(i+n,​t,​read_int());​
 + int flow=solver.Maxflow(s,​t);​
 + dfs(s);
 + _rep(i,​1,​n) if(vis[i])
 + space(i);
 + puts(""​);​
 + _rep(i,​1,​m) if(vis[i+n])
 + space(i);
 + puts(""​);​
 + enter(sum-flow);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +=== 最大权闭合子图 ===
 +
 +== 定义 ==
 +
 +给定一个有向图,取图中的一些点构成点集 $V$,若对 $V$ 中的任意一个节点其后继节点均属于 $V$,则称 $V$ 及其相关边为闭合子图。
 +
 +如果每个点拥有一个点权,则称 $V$ 点权和最大的闭合子图为最大权闭合子图。
 +
 +== 性质 ==
 +
 +按如下规则构图:
 +  * 若某点点权为正,则从源点连一条容量等于点权的边到该点
 +  * 若某点点权为负,则从该点连一条容量等于点权绝对值的边到汇点
 +  * 如某条边属于原图,则将其容量改为 $\inf$
 +在该图中跑最大流,则最大权闭合子图点权和 $=$ 所有正点权之和 $-$ 最小割。
 +
 +== 证明 ==
 +
 +简单割定义:割 $(S,T)$ 中每一条割边都与 $s$ 或者 $t$ 相连。
 +
 +**任意闭合子图一定对应一个简单割**,否则取闭合子图和源点构成 $S$,其余点构成 $T$。
 +
 +如果有 $(u,​v)\subset E,u\in S,v\in T$,则表示选择 $u$但不选择 $v$,与闭合子图定义矛盾。
 +
 +**任意一个简单割对应一个闭合子图**,因为对 $(u,​v)\subset E,u\in S$,根据简单割定义,有 $v\in S$,满足闭合子图定义。
 +
 +记 $T$ 中所有正权点的权值之和为 $T_1$,$S$ 中所有正权点的权值之和为 $S_1$,$T$ 中所有负权点的权值绝对值之和为 $S_2$。
 +
 +则割的容量 $C(S,​T)=T_1+S_2$,闭合子图的权值 $W=S_1-S_2$。
 +
 +则有 $C(S,​T)+W=T_1+S_1$,即 $W=T_1+S_1-C(S,​T)$。
 +
 +$T_1+S_1$ 即为所有正点权之和,是定值。剩下只需要考虑令 $C(S,T)$ 尽量小。
 +
 +而按上述方法构建的网络流图的最小割一定为简单割,因为非简单割容量大于 $\inf$,一定不是最小割。
 +
 +所以直接求最小割即可,证毕。
 +
 +====  最小路径覆盖问题 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P2764|洛谷p2764]]
 +
 +=== 题意 ===
 +
 +给定一个 $\text{DAG}$,求最小的不相交路径集,使得每个点恰好属于其中一条路径。(允许路径长度为 $0$,此时路径仅覆盖一个点)
 +
 +=== 题解 ===
 +
 +先用 $n$ 条长度为 $0$ 的路径覆盖 $n$ 个点,然后考虑合并路径。
 +
 +将每个点拆成入点和出点,易知每个入点/​出点最多被合并一次,否则有两条路径相交。
 +
 +从源点连一条容量为 $1$ 的边到每个入点,从每个出点连一条容量为 $1$ 的边到汇点,则可以满足上述的合并次数限制。
 +
 +最后,对每条边,从入点连一条容量为 $1$ 的边到出点,表示以入点结束的路径可与以出点开始的路径合并一次。
 +
 +最后需要合并次数最多,跑最大流即可。最小路径覆盖为节点数减去最大流。
 +
 +关于路径打印,只要在求完最大流后跑残量网络即可。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=305,​MAXM=18005,​Inf=0x7fffffff;​
 +struct Edge{
 + int to,​cap,​next;​
 + Edge(int to=0,int cap=0,int next=0){
 + this->​to=to;​
 + this->​cap=cap;​
 + this->​next=next;​
 + }
 +}edge[MAXM<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号 ​
 +void Insert(int u,int v,int c){
 + edge[edge_cnt]=Edge(v,​c,​head[u]);​
 + head[u]=edge_cnt++;​
 + edge[edge_cnt]=Edge(u,​0,​head[v]);​
 + head[v]=edge_cnt++;​
 +}
 +struct Dinic{
 + int s,t;
 + int pos[MAXN],​vis[MAXN],​dis[MAXN];​
 + bool bfs(int k){
 + queue<​int>​q;​
 + q.push(s);​
 + vis[s]=k,​dis[s]=0,​pos[s]=head[s];​
 + while(!q.empty()){
 + int u=q.front();​q.pop();​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(vis[v]!=k&&​edge[i].cap){
 + vis[v]=k,​dis[v]=dis[u]+1,​pos[v]=head[v];​
 + q.push(v);​
 + if(v==t)
 + return true;
 + }
 + }
 + }
 + return false;
 + }
 + int dfs(int u,int max_flow){
 + if(u==t||!max_flow)
 + return max_flow;
 + int flow=0,​temp_flow;​
 + for(int &​i=pos[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(dis[u]+1==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){
 + edge[i].cap-=temp_flow;​
 + edge[i^1].cap+=temp_flow;​
 + flow+=temp_flow;​
 + max_flow-=temp_flow;​
 + if(!max_flow)
 + break;
 + }
 + }
 + return flow;
 + }
 + int Maxflow(int s,int t){
 + this->​s=s;​this->​t=t;​
 + int ans=0,k=0;
 + mem(vis,​0);​
 + while(bfs(++k))
 + ans+=dfs(s,​Inf);​
 + return ans;
 + }
 +}solver;
 +int Next[MAXN],​Pre[MAXN];​
 +int main()
 +{
 + int n=read_int(),​m=read_int(),​u,​v;​
 + int s=2*n+1,​t=2*n+2;​
 + Clear();
 + _rep(i,​1,​n){
 + Insert(s,​i,​1);​
 + Insert(i+n,​t,​1);​
 + }
 + while(m--){
 + u=read_int(),​v=read_int();​
 + Insert(u,​v+n,​1);​
 + }
 + int flow=solver.Maxflow(s,​t);​
 + _rep(u,​1,​n){
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(v!=s&&​edge[i].cap==0){
 + Next[u]=v-n;​
 + Pre[v-n]=u;​
 + }
 + }
 + }
 + _rep(i,​1,​n){
 + if(!Pre[i]){
 + int pos=i;
 + while(pos){
 + space(pos);​
 + pos=Next[pos];​
 + }
 + puts(""​);​
 + }
 + }
 + enter(n-flow);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +=== 练习题 ===
 +
 +[[https://​www.luogu.com.cn/​problem/​P2765|洛谷p2765]]
 +
 +== 题意 ==
 +
 +给定 $n$ 个栈,要求依次放入 $1,​2,​3,​\cdots$
 +
 +要求同一个栈中每两个相邻元素之和是平方数。
 +
 +输出最多可以放入的数的个数以及任意一个方案。
 +
 +== 题解 ==
 +
 +把原图想像成一个 $\text{DAG}$,小数向可以与它相加构成平方数的大数连一条单向边,需要栈的个数等于最小路径覆盖。
 +
 +依次向图中加入每个数,每次加入一个数时先用长度为 $0$ 的路径将其覆盖,然后在残量网络中考虑路径合并。
 +
 +残量网络流量为 $0$ 表示路径合并失败,需要使用一个新的栈。如果最小路径覆盖不超过 $n$ 即可继续加数,否则终止循环。
 +
 +方案打印同最小路径覆盖。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1e5+5,​MAXM=2e5+5,​Inf=0x7fffffff;​
 +struct Edge{
 + int to,​cap,​next;​
 + Edge(int to=0,int cap=0,int next=0){
 + this->​to=to;​
 + this->​cap=cap;​
 + this->​next=next;​
 + }
 +}edge[MAXM<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号 ​
 +void Insert(int u,int v,int c){
 + edge[edge_cnt]=Edge(v,​c,​head[u]);​
 + head[u]=edge_cnt++;​
 + edge[edge_cnt]=Edge(u,​0,​head[v]);​
 + head[v]=edge_cnt++;​
 +}
 +struct Dinic{
 + int s,t;
 + int pos[MAXN],​vis[MAXN],​dis[MAXN];​
 + bool bfs(int k){
 + queue<​int>​q;​
 + q.push(s);​
 + vis[s]=k,​dis[s]=0,​pos[s]=head[s];​
 + while(!q.empty()){
 + int u=q.front();​q.pop();​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(vis[v]!=k&&​edge[i].cap){
 + vis[v]=k,​dis[v]=dis[u]+1,​pos[v]=head[v];​
 + q.push(v);​
 + if(v==t)
 + return true;
 + }
 + }
 + }
 + return false;
 + }
 + int dfs(int u,int max_flow){
 + if(u==t||!max_flow)
 + return max_flow;
 + int flow=0,​temp_flow;​
 + for(int &​i=pos[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(dis[u]+1==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){
 + edge[i].cap-=temp_flow;​
 + edge[i^1].cap+=temp_flow;​
 + flow+=temp_flow;​
 + max_flow-=temp_flow;​
 + if(!max_flow)
 + break;
 + }
 + }
 + return flow;
 + }
 + int Maxflow(int s,int t){
 + this->​s=s;​this->​t=t;​
 + int ans=0,k=0;
 + mem(vis,​0);​
 + while(bfs(++k))
 + ans+=dfs(s,​Inf);​
 + return ans;
 + }
 +}solver;
 +const int MAXS=60;
 +int Next[MAXN],​Pre[MAXN],​s=MAXN-2,​t=MAXN-1;​
 +int main()
 +{
 + Clear();
 + int n=read_int(),​pos=0,​num=0;​
 + while(pos<​=n){
 + ++num;
 + Insert(s,​num<<​1,​1);​Insert(num<<​1|1,​t,​1);​
 + for(int i=sqrt(num)+1;​i*i<​2*num;​i++)
 + Insert((i*i-num)<<​1,​num<<​1|1,​1);​
 + if(solver.Maxflow(s,​t)==0)
 + pos++;
 + }
 + enter(--num);​
 + _rep(u,​1,​num){
 + for(int i=head[u<<​1];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(v!=s&&​edge[i].cap==0){
 + Next[u]=v>>​1;​
 + Pre[v>>​1]=u;​
 + }
 + }
 + }
 + _rep(i,​1,​num){
 + if(!Pre[i]){
 + int pos=i;
 + while(pos){
 + space(pos);​
 + pos=Next[pos];​
 + }
 + puts(""​);​
 + }
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +====  往返路线问题 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P2770|洛谷p2770]]
 +
 +=== 题意 ===
 +
 +给定 $n$ 个点,$m$ 条边。规定 $1$ 号点为起点,$n$ 号点为终点。
 +
 +要求输出一条从起点经过终点再回到起点的路径,要求路径上的节点编号先单调增,再单调减,且路径上无重复节点。
 +
 +如果存在多条路径,输出任意一条最长的路径。
 +
 +=== 题解 1 ===
 +
 +首先问题可以转化为从起点开始走两条不相交的路径。
 +
 +每个点可以走一次且记一次贡献,所以考虑拆点并连一条容量为 $1$ 权值为 $-1$ 的边。
 +
 +注意起点终点会走两遍,但贡献只计算一次,所以需要额外连一条容量为 $1$ 权值为 $0$ 的边。
 +
 +然后从汇点连一条容量为 $2$ 的边到起点,从终点连一条容量为 $2$ 的边到汇点,保证存在两条路径。
 +
 +最后根据输入的边连接网络流的相应点即可,注意这些边不一定只会走一次(例如 $n=2$ 且起点终点连通的情况),所以边的容量设为 $2$。
 +
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=205,​MAXM=6000,​Inf=0x7fffffff;​
 +struct Edge{
 + int to,​cap,​w,​next;​
 + Edge(int to=0,int cap=0,int w=0,int next=0){
 + this->​to=to;​
 + this->​cap=cap;​
 + this->​w=w;​
 + this->​next=next;​
 + }
 +}edge[MAXM<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号 ​
 +void Insert(int u,int v,int w,int c){
 + edge[edge_cnt]=Edge(v,​c,​w,​head[u]);​
 + head[u]=edge_cnt++;​
 + edge[edge_cnt]=Edge(u,​0,​-w,​head[v]);​
 + head[v]=edge_cnt++;​
 +}
 +struct Dinic{
 + int s,t;
 + int pos[MAXN],​dis[MAXN];​
 + bool inque[MAXN];​
 + bool spfa(){
 + mem(dis,​127/​3);​
 + queue<​int>​q;​
 + q.push(s);​
 + inque[s]=true,​dis[s]=0,​pos[s]=head[s];​
 + while(!q.empty()){
 + int u=q.front();​q.pop();​
 + inque[u]=false;​
 + for(int i=head[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(dis[u]+edge[i].w<​dis[v]&&​edge[i].cap){
 + dis[v]=dis[u]+edge[i].w,​pos[v]=head[v];​
 + if(!inque[v]){
 + q.push(v);​
 + inque[v]=true;​
 + }
 + }
 + }
 + }
 + return dis[t]!=dis[0];​
 + }
 + int dfs(int u,int max_flow){
 + if(u==t||!max_flow)
 + return max_flow;
 + int flow=0,​temp_flow;​
 + inque[u]=true;​
 + for(int &​i=pos[u];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(!inque[v]&&​dis[u]+edge[i].w==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){
 + edge[i].cap-=temp_flow;​
 + edge[i^1].cap+=temp_flow;​
 + flow+=temp_flow;​
 + max_flow-=temp_flow;​
 + if(!max_flow)
 + break;
 + }
 + }
 + inque[u]=false;​
 + return flow;
 + }
 + void MCMF(int s,int t,int &​flow,​LL &cost){
 + this->​s=s;​this->​t=t;​
 + cost=flow=0;​
 + int temp_flow;
 + mem(inque,​0);​
 + while(spfa()){
 + temp_flow=dfs(s,​Inf);​
 + flow+=temp_flow;​
 + cost+=1LL*temp_flow*dis[t];​
 + }
 + }
 +}solver;
 +map<​string,​int>​mp;​
 +map<​int,​string>​pt;​
 +string a,b;
 +int n,m,s,t;
 +bool vis[MAXN];
 +void dfs1(int u){
 + if(u==t)
 + return;
 + cout<<​pt[u]<<​endl;​
 + for(int i=head[u+n];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(u<​v&&​edge[i].cap<​2){
 + vis[v]=true;​
 + return dfs1(v);
 + }
 + }
 +}
 +void dfs2(int u){
 + for(int i=head[u+n];​~i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(!vis[v]&&​u<​v&&​edge[i].cap<​2){
 + dfs2(v);
 + break;
 + }
 + }
 + cout<<​pt[u]<<​endl;​
 +}
 +int main()
 +{
 + n=read_int(),​m=read_int(),​s=2*n+1,​t=2*n+2;​
 + Clear();
 + _rep(i,​1,​n){
 + cin>>​a;​
 + mp[a]=i;​pt[i]=a;​
 + Insert(i,​i+n,​-1,​1);​
 + }
 + Insert(s,​1,​0,​2);​Insert(1,​1+n,​0,​1);​
 + Insert(n,​2*n,​0,​1);​Insert(2*n,​t,​0,​2);​
 + int u,v;
 + while(m--){
 + cin>>​a>>​b;​
 + u=mp[a],​v=mp[b];​
 + if(u>​v)
 + swap(u,​v);​
 + Insert(u+n,​v,​0,​2);​
 + }
 + int flow;LL cost;
 + solver.MCMF(s,​t,​flow,​cost);​
 + if(flow<​2)
 + puts("​No Solution!"​);​
 + else{
 + enter(-cost);​
 + dfs1(1);
 + dfs2(1);
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +=== 题解 2 ===
 +
 +发现可以 $\text{dp}$ 解决,时间复杂度 $O(nm)$。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=105,​MAXM=6000;​
 +struct Edge{
 + int to,next;
 +}edge[MAXN<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Insert(int u,int v){
 + edge[++edge_cnt]=Edge{v,​head[u]};​
 + head[u]=edge_cnt;​
 +}
 +int dp[MAXN][MAXN];​
 +pair<​int,​int>​ pre[MAXN][MAXN];​
 +map<​string,​int>​mp;​
 +map<​int,​string>​pt;​
 +string a,b;
 +int n,m;
 +void dfs1(int pos1,int pos2){
 + if(pos1==1){
 + cout<<​pt[pos1]<<​endl;​
 + return;
 + }
 + while(pre[pos1][pos2].first==pos1)
 + pos2=pre[pos1][pos2].second;​
 + dfs1(pre[pos1][pos2].first,​pos2);​
 + cout<<​pt[pos1]<<​endl;​
 +}
 +void dfs2(int pos1,int pos2){
 + if(pos2==1){
 + cout<<​pt[pos2]<<​endl;​
 + return;
 + }
 + if(pos2!=n)
 + cout<<​pt[pos2]<<​endl;​
 + while(pre[pos1][pos2].second==pos2)
 + pos1=pre[pos1][pos2].first;​
 + dfs2(pos1,​pre[pos1][pos2].second);​
 +}
 +int main()
 +{
 + n=read_int(),​m=read_int();​
 + _rep(i,​1,​n){
 + cin>>​a;​
 + mp[a]=i;​pt[i]=a;​
 + }
 + int u,v;
 + while(m--){
 + cin>>​a>>​b;​
 + u=mp[a],​v=mp[b];​
 + if(u>​v)
 + swap(u,​v);​
 + Insert(u,​v);​
 + }
 + mem(dp,​-1);​
 + dp[1][1]=0;​
 + _rep(i,​1,​n)
 + _rep(j,​1,​n){
 + if(dp[i][j]==-1)
 + continue;
 + for(int k=head[i];​k;​k=edge[k].next){
 + int v=edge[k].to;​
 + if(v<​=j&&​v!=n)
 + continue;​
 + if(dp[v][j]<​dp[i][j]+1){
 + dp[v][j]=dp[i][j]+1;​
 + pre[v][j]=make_pair(i,​j);​
 + }
 + }
 + for(int k=head[j];​k;​k=edge[k].next){
 + int v=edge[k].to;​
 + if(v<​=i&&​v!=n)
 + continue;​
 + if(dp[i][v]<​dp[i][j]+1){
 + dp[i][v]=dp[i][j]+1;​
 + pre[i][v]=make_pair(i,​j);​
 + }
 + }
 + }
 + if(dp[n][n]==dp[0][0])
 + puts("​No Solution!"​);​
 + else{
 + enter(dp[n][n]);​
 + dfs1(n,​n);​
 + dfs2(n,​n);​
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
  
 ====  最长 $k$ 可重区间集问题 ==== ====  最长 $k$ 可重区间集问题 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P3358|洛谷p3358]]
  
 === 题意 === === 题意 ===
行 26: 行 847:
  
 对每个区间,仍然按上述方案连边。最后从源点连一条容量为 $k$ 的边到最左端点,从最右端点连一条容量为 $k$ 的边到汇点,跑费用流即可。 对每个区间,仍然按上述方案连边。最后从源点连一条容量为 $k$ 的边到最左端点,从最右端点连一条容量为 $k$ 的边到汇点,跑费用流即可。
 +
 +如果题目把开区间改成闭区间,拆点的时候当作 $[lef,​rig+1)$ 处理就行。
  
 <hidden 查看代码>​ <hidden 查看代码>​
 <code cpp> <code cpp>
-#include <​iostream>​ 
-#include <​cstdio>​ 
-#include <​cstdlib>​ 
-#include <​algorithm>​ 
-#include <​string>​ 
-#include <​sstream>​ 
-#include <​cstring>​ 
-#include <​cctype>​ 
-#include <​cmath>​ 
-#include <​vector>​ 
-#include <set> 
-#include <map> 
-#include <​stack>​ 
-#include <​queue>​ 
-#include <​ctime>​ 
-#include <​cassert>​ 
-#define _for(i,a,b) for(int i=(a);​i<​(b);​++i) 
-#define _rep(i,a,b) for(int i=(a);​i<​=(b);​++i) 
-#define mem(a,b) memset(a,​b,​sizeof(a)) 
-using namespace std; 
-typedef long long LL; 
-inline int read_int(){ 
- int t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline LL read_LL(){ 
- LL t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline char get_char(){ 
- char c=getchar();​ 
- while(c=='​ '​||c=='​\n'​||c=='​\r'​)c=getchar();​ 
- return c; 
-} 
-inline void write(LL x){ 
- register char c[21],​len=0;​ 
- if(!x)return putchar('​0'​),​void();​ 
- if(x<​0)x=-x,​putchar('​-'​);​ 
- while(x)c[++len]=x%10,​x/​=10;​ 
- while(len)putchar(c[len--]+48);​ 
-} 
-inline void space(LL x){write(x),​putchar('​ ');} 
-inline void enter(LL x){write(x),​putchar('​\n'​);​} 
 const int MAXN=1005,​MAXM=2005,​Inf=0x7fffffff;​ const int MAXN=1005,​MAXM=2005,​Inf=0x7fffffff;​
 struct Edge{ struct Edge{
行 178: 行 954:
 </​hidden>​ </​hidden>​
  
-=== 拓展 ​===+====  最长不下降子序列问题 ====
  
-如果题目把开区间改成闭区间,拆点的时候当作 $[lef,rig+1)$ 处理就行。 +[[https://​www.luogu.com.cn/​problem/​P2766|洛谷p2766]]
- +
-====  最小路径覆盖问题 ====+
  
 === 题意 === === 题意 ===
  
-给定一个 $\text{DAG}$,求小的相交路径集,使得每个点恰好属于其中一条路径。(允许路径长度为 $0$,此时路径仅覆盖一点)+给定一个正整数序列 ​$x_1,x_2,\cdots x_n$。 
 + 
 +  - 计算其下降子序列的长度 $s$ 
 +  - 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 $s$ 的不下降子序列 
 +  - 如果允许在取出的序列中多次使用 $x_1,x_n$,则从给定序列中最多可取出多少不同的长度为的 $s$ 不下降子序列
  
 === 题解 === === 题解 ===
  
-先用 ​$n条长度为 ​$0$ 的路径覆盖 $n$ 个点,然后考虑合并路径+第一问考虑 ​$\text{dp}求出以第 ​$i个数结尾最长序列长度
  
-每个点拆成入点和出点,易知每个入点/​出点最多被合并次,否则有两路径相交+第二问先考虑只允许使用一次这个限制条件,只需要把每个点拆成入点和出点,然后连一条容量为 $1$ 的边即可
  
-从源点连一条容量为 ​$1$ 的边到每个入点从每个出点连一条容量为 $1$ 的边到汇点,则可以满足上述的合并次数限制+接下若满足 ​$j\lt i,a_j\le a_i,​\text{dp}_j+1==\text{dp}_i$,连一条 ​$j\to i$ 的容量为 $1$ 的边。
  
-最后,对每条边,点连一条容量为 $1$ 的边到出点表示以入点结束路径可与以出开始的路径合并+最后考虑源点向 $\text{dp}_i==1$ 的点连一条容量为 $1$ 的边,从 $\text{dp}_i==s$ ​的点向汇点连条容量为 $1$ 的边
  
-最后需要合并次数最多,跑最大流即可。最小路径覆盖为节点数减去最大流。+最后跑最大流即可求出第二问答案。 
 + 
 +对第三问,考虑把第 $1,n$ 个点的入点和出点的连边容量改为 ​ $\inf$,同时把从源点到第 $1$ 个的连边的容量改为 $\inf$。 
 + 
 +如果 $\text{dp}_n==s$,还需要把从第 $n$ 个点向汇点的连边的容量改为 $\inf$然后重新一遍最大流。 
 + 
 +但实际编程中不需要修改边,额外添加边即可。同时也不需要重新跑大流,在残量网络上跑最大流然后累加上第二问答案即可。 
 + 
 +需要注意 $s=1$ 的特例,题目需要求不同的不下降子序列就是为了防止此时答案为 $\inf$ 的情况
  
 <hidden 查看代码>​ <hidden 查看代码>​
 <code cpp> <code cpp>
-#include <​iostream>​ +const int MAXN=1005,MAXM=5e5+5,​Inf=0x7fffffff;​
-#include <​cstdio>​ +
-#include <​cstdlib>​ +
-#include <​algorithm>​ +
-#include <​string>​ +
-#include <​sstream>​ +
-#include <​cstring>​ +
-#include <​cctype>​ +
-#include <​cmath>​ +
-#include <​vector>​ +
-#include <​set>​ +
-#include <​map>​ +
-#include <​stack>​ +
-#include <​queue>​ +
-#include <​ctime>​ +
-#include <​cassert>​ +
-#define _for(i,a,b) for(int i=(a);​i<​(b);​++i) +
-#define _rep(i,a,b) for(int i=(a);​i<​=(b);​++i) +
-#define mem(a,b) memset(a,​b,​sizeof(a)) +
-using namespace std; +
-typedef long long LL; +
-inline int read_int(){ +
- int t=0;bool sign=false;​char c=getchar();​ +
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} +
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} +
- return sign?​-t:​t;​ +
-+
-inline LL read_LL(){ +
- LL t=0;bool sign=false;​char c=getchar();​ +
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} +
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} +
- return sign?​-t:​t;​ +
-+
-inline char get_char(){ +
- char c=getchar();​ +
- while(c=='​ '​||c=='​\n'​||c=='​\r'​)c=getchar();​ +
- return c; +
-+
-inline void write(LL x){ +
- register char c[21],​len=0;​ +
- if(!x)return putchar('​0'​),​void();​ +
- if(x<​0)x=-x,​putchar('​-'​);​ +
- while(x)c[++len]=x%10,​x/​=10;​ +
- while(len)putchar(c[len--]+48);​ +
-+
-inline void space(LL x){write(x),​putchar('​ ');} +
-inline void enter(LL x){write(x),​putchar('​\n'​);​} +
-const int MAXN=305,MAXM=18005,​Inf=0x7fffffff;​+
 struct Edge{ struct Edge{
  int to,​cap,​next;​  int to,​cap,​next;​
行 313: 行 1052:
  }  }
 }solver; }solver;
-int Next[MAXN],Pre[MAXN];+int a[MAXN],dp[MAXN],​f[MAXN],​pos;
 int main() int main()
 { {
- int n=read_int(),​m=read_int(),​u,v;+ int n=read_int()
 + _rep(i,1,n) 
 + a[i]=read_int()
 + f[pos=0]=-Inf;​ 
 + _rep(i,1,n){ 
 + if(a[i]>​=f[pos]){ 
 + f[++pos]=a[i];​ 
 + dp[i]=pos;​ 
 +
 + else{ 
 + int t=upper_bound(f,​f+pos+1,​a[i])-f;​ 
 + f[t]=a[i];​ 
 + dp[i]=t;​ 
 +
 +
 + enter(pos);​ 
 + if(pos==1){ 
 + sort(a+1,​a+n+1);​ 
 + int ans=unique(a+1,​a+n+1)-a-1;​ 
 + enter(ans);​ 
 + enter(ans);​ 
 + return 0; 
 + }
  int s=2*n+1,​t=2*n+2;​  int s=2*n+1,​t=2*n+2;​
  Clear();  Clear();
  _rep(i,​1,​n){  _rep(i,​1,​n){
 + Insert(i,​i+n,​1);​
 + if(dp[i]==1)
  Insert(s,​i,​1);​  Insert(s,​i,​1);​
 + if(dp[i]==pos)
  Insert(i+n,​t,​1);​  Insert(i+n,​t,​1);​
 + _for(j,​1,​i){
 + if(a[j]<​=a[i]&&​dp[j]+1==dp[i])
 + Insert(j+n,​i,​1);​
 + }
  }  }
- while(m--){ + int ans=solver.Maxflow(s,t); 
- u=read_int(),v=read_int(); + enter(ans)
- Insert(u,v+n,1);+ Insert(s,1,​Inf);​Insert(1,1+n,Inf); 
 + if(dp[n]==pos){ 
 + Insert(n,2*n,Inf); 
 + Insert(2*n,​t,​Inf);
  }  }
- int flow=solver.Maxflow(s,​t);​ + ans+=solver.Maxflow(s,​t);​ 
- _rep(u,1,n){ + enter(ans); 
- for(int i=head[u];​~i;​i=edge[i].next){+ return 0; 
 +
 +</​code>​ 
 +</​hidden>​ 
 + 
 +====  星际转移问题 ==== 
 + 
 +[[https://​www.luogu.com.cn/​problem/​P2754|洛谷p2754]] 
 + 
 +=== 题意 === 
 + 
 +一共 $n$ 个点,$m$ 条船。每条船有一定容量,并且存在周期性环形航线。 
 + 
 +设船从航线中的一个点移动到另一个点花费 $1$ 个单位的时间,问最少要花多少时间才能把 $k$ 个人从起点运到终点。 
 + 
 +=== 题解 === 
 + 
 +考虑按时间拆点,起点连接源点,终点连接汇点。 
 + 
 +每个位置的相邻时间点连接一条容量无限大的边,表示停留在该位置一个单位时间。 
 + 
 +然后枚举时间,根据时间和飞船航线加边,每次在残量网络上跑最大流并累加到总流量,总流量不小于 $k$ 时结束枚举。 
 + 
 +至于无解的判定,可以一开始用并查集维护一下连通性。 
 + 
 +<hidden 查看代码>​ 
 +<code cpp> 
 +const int MAXN=1e5+5,MAXM=2e5+5,​Inf=0x7fffffff;​ 
 +struct Edge{ 
 + int to,​cap,​next;​ 
 + Edge(int to=0,int cap=0,int next=0){ 
 + this->​to=to;​ 
 + this->​cap=cap;​ 
 + this->​next=next;​ 
 +
 +}edge[MAXM<<​1]; 
 +int head[MAXN],edge_cnt; 
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号  
 +void Insert(int u,int v,int c){ 
 + edge[edge_cnt]=Edge(v,​c,​head[u]);​ 
 + head[u]=edge_cnt++;​ 
 + edge[edge_cnt]=Edge(u,​0,​head[v]);​ 
 + head[v]=edge_cnt++;​ 
 +
 +struct Dinic{ 
 + int s,t; 
 + int pos[MAXN],​vis[MAXN],​dis[MAXN];​ 
 + bool bfs(int k){ 
 + queue<​int>​q;​ 
 + q.push(s);​ 
 + vis[s]=k,​dis[s]=0,​pos[s]=head[s];​ 
 + while(!q.empty()){ 
 + int u=q.front();​q.pop();​ 
 + for(int i=head[u];​~i;​i=edge[i].next){ 
 + int v=edge[i].to;​ 
 + if(vis[v]!=k&&​edge[i].cap){ 
 + vis[v]=k,​dis[v]=dis[u]+1,​pos[v]=head[v];​ 
 + q.push(v);​ 
 + if(v==t) 
 + return true; 
 +
 +
 +
 + return false; 
 +
 + int dfs(int u,int max_flow){ 
 + if(u==t||!max_flow) 
 + return max_flow; 
 + int flow=0,​temp_flow;​ 
 + for(int &i=pos[u];​~i;​i=edge[i].next){
  int v=edge[i].to;​  int v=edge[i].to;​
- if(v!=s&&​edge[i].cap==0){ + if(dis[u]+1==dis[v]&&(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){ 
- Next[u]=v-n+ edge[i].cap-=temp_flow
- Pre[v-n]=u;+ edge[i^1].cap+=temp_flow;​ 
 + flow+=temp_flow;​ 
 + max_flow-=temp_flow;​ 
 + if(!max_flow) 
 + break;
  }  }
  }  }
 + return flow;
  }  }
- _rep(i,​1,​n){ + int Maxflow(int s,int t){ 
- if(!Pre[i]){ + this->​s=s;​this->​t=t;​ 
- int pos=i; + int ans=0,​k=0;​ 
- while(pos){ + mem(vis,​0);​ 
- space(pos); + while(bfs(++k)) 
- pos=Next[pos];+ ans+=dfs(s,​Inf);​ 
 + return ans; 
 +
 +}solver; 
 +const int MAXS=25; 
 +vector<​int>​ g[MAXS]; 
 +int p[MAXS],​f[MAXS],​T[MAXS];​ 
 +int Find(int x){return x==p[x]?​x:​p[x]=Find(p[x]);​} 
 +int main() 
 +
 + Clear(); 
 + int n=read_int()+2,​m=read_int(),​k=read_int(),​s=MAXN-2,​t=MAXN-1,​x,​y;​ 
 + _rep(i,1,n
 + p[i]=i; 
 + _for(i,0,m){ 
 + f[i]=read_int(),​T[i]=read_int();​ 
 + _for(j,​0,​T[i]){ 
 + x=read_int();​ 
 + if(x==0) 
 + x=n-1; 
 + else if(x==-1) 
 + x=n; 
 + g[i].push_back(x);​ 
 +
 + _for(j,​1,​T[i]){ 
 + x=Find(g[i][j-1]),​y=Find(g[i][j])
 + if(x!=y) 
 + p[x]=y;​ 
 +
 +
 + if(Find(n-1)!=Find(n)){ 
 + puts("​0"​);​ 
 + return 0; 
 +
 + Insert(s,​n-1,​Inf);​Insert(n,​t,​Inf);​ 
 + int ans=0; 
 + for(int flow=0;​flow<​k;​ans++){ 
 + _for(i,​1,​n) 
 + Insert(i+ans*n,​i+(ans+1)*n,​Inf);​ 
 + Insert((ans+2)*n,​(ans+1)*n,​Inf);​ 
 + _for(i,​0,​m) 
 + Insert(g[i][ans%T[i]]+ans*n,​g[i][(ans+1)%T[i]]+(ans+1)*n,​f[i]);​ 
 + flow+=solver.Maxflow(s,​t);​ 
 +
 + enter(ans);​ 
 + return 0; 
 +
 +</​code>​ 
 +</​hidden>​ 
 + 
 +====  餐巾计划问题 ==== 
 + 
 +[[https://​www.luogu.com.cn/​problem/​P1251|洛谷p1251]] 
 + 
 +=== 题意 === 
 + 
 +一个餐厅第 $i$ 天需要 $r_i$ 条新餐巾,每条新餐巾用完后得到旧餐巾,一开始餐厅没有餐巾。 
 + 
 +买一条新餐巾费用为 $p$;快洗一条旧餐巾时间为 $m$ 天,费用为 $f$;慢洗一条旧餐巾时间为 $n$ 天,费用为 $s$。 
 + 
 +数据保证 $f\gt s,m\lt n$,问餐厅营业 $N$ 天的最小花费。  
 + 
 +=== 题解 === 
 + 
 +把一天分成一天的开始和一天的结束,一天的开始需要提供 $r_i$ 条新餐巾,于是连一条容量为 $r_i$ 费用为 $0$ 的边到汇点。 
 + 
 +接下来考虑三种获取新餐巾的操作。首先可以买新餐巾,对应源点连一条容量为 $\inf$ 费用为 $p$ 的边到当天的开始。 
 + 
 +另外也可以通过清洗之前的旧餐巾获得。不妨假设如果旧餐巾洗完就会被立即使用,不然可以留到以后再洗。这样假设的目的是减少连边数量。 
 + 
 +于是第 $i$ 天的结束向第 $i+m$ 天的开始连一条容量为 $\inf$ 费用为 $f$ 的边,向第 $i+n$ 天的开始连一条容量为 $\inf$ 费用为 $s$ 的边。 
 + 
 +再考虑维护旧餐巾,旧餐巾可以通过两种方式获取。 
 + 
 +首先一天的结束将有 $r_i$ 条新餐巾变成旧餐巾,于是从源点连一条容量为 $r_i$ 费用为 $0$ 的边到一天的结束。 
 + 
 +另外旧餐巾也可以继承前一天剩下的未被清洗的旧餐巾,对应每天结束向第二天结束连一条容量为 $\inf$ 费用为 $0$ 的边。 
 + 
 +最后跑最小费用最大流即可。 
 + 
 +<hidden 查看代码>​ 
 +<code cpp> 
 +const int MAXN=5000,​MAXM=15000,​Inf=0x7fffffff;​ 
 +struct Edge{ 
 + int to,​cap,​w,​next;​ 
 + Edge(int to=0,int cap=0,int w=0,int next=0){ 
 + this->​to=to;​ 
 + this->​cap=cap;​ 
 + this->​w=w;​ 
 + this->​next=next;​ 
 +
 +}edge[MAXM<<​1];​ 
 +int head[MAXN],​edge_cnt;​ 
 +void Clear(){mem(head,​-1);​edge_cnt=0;​}//​边从0开始编号  
 +void Insert(int u,int v,int w,int c){ 
 + edge[edge_cnt]=Edge(v,​c,​w,​head[u]);​ 
 + head[u]=edge_cnt++;​ 
 + edge[edge_cnt]=Edge(u,​0,​-w,​head[v]);​ 
 + head[v]=edge_cnt++;​ 
 +
 +struct Dinic{ 
 + int s,t; 
 + int pos[MAXN],​dis[MAXN];​ 
 + bool inque[MAXN];​ 
 + bool spfa(){ 
 + mem(dis,​127/​3);​ 
 + queue<​int>​q;​ 
 + q.push(s);​ 
 + inque[s]=true,​dis[s]=0,​pos[s]=head[s];​ 
 + while(!q.empty()){ 
 + int u=q.front();​q.pop(); 
 + inque[u]=false;​ 
 + for(int i=head[u];​~i;​i=edge[i].next){ 
 + int v=edge[i].to; 
 + if(dis[u]+edge[i].w<​dis[v]&&​edge[i].cap){ 
 + dis[v]=dis[u]+edge[i].w,​pos[v]=head[v]; 
 + if(!inque[v]){ 
 + q.push(v);​ 
 + inque[v]=true;​ 
 +
 + }
  }  }
- puts(""​);​ 
  }  }
 + return dis[t]!=dis[0];​
  }  }
- enter(n-flow);+ int dfs(int u,int max_flow){ 
 + if(u==t||!max_flow) 
 + return max_flow; 
 + int flow=0,​temp_flow;​ 
 + inque[u]=true;​ 
 + for(int &​i=pos[u];​~i;​i=edge[i].next){ 
 + int v=edge[i].to;​ 
 + if(!inque[v]&&​dis[u]+edge[i].w==dis[v]&&​(temp_flow=dfs(v,​min(max_flow,​edge[i].cap)))){ 
 + edge[i].cap-=temp_flow;​ 
 + edge[i^1].cap+=temp_flow;​ 
 + flow+=temp_flow;​ 
 + max_flow-=temp_flow;​ 
 + if(!max_flow) 
 + break;​ 
 +
 +
 + inque[u]=false;​ 
 + return flow; 
 +
 + void MCMF(int s,int t,int &​flow,​LL &​cost){ 
 + this->​s=s;​this->​t=t;​ 
 + cost=flow=0;​ 
 + int temp_flow;​ 
 + mem(inque,​0);​ 
 + while(spfa()){ 
 + temp_flow=dfs(s,​Inf);​ 
 + flow+=temp_flow;​ 
 + cost+=1LL*temp_flow*dis[t];​ 
 +
 +
 +}solver; 
 +int a[MAXN]; 
 +int main() 
 +
 + Clear(); 
 + int n=read_int(),​s=n<<​1|1,​t=s+1;​ 
 + _rep(i,​1,​n) 
 + a[i]=read_int();​ 
 + int c1=read_int(),​t2=read_int(),​c2=read_int(),​t3=read_int(),​c3=read_int();​ 
 + _rep(i,​1,​n){ 
 + Insert(s,​i+n,​0,​a[i]);​ 
 + Insert(i,​t,​0,​a[i]);​ 
 + Insert(s,​i,​c1,​Inf);​ 
 + if(i+t2<​=n){ 
 + Insert(i+n,​i+t2,​c2,​Inf);​ 
 + if(i+t3<​=n) 
 + Insert(i+n,​i+t3,​c3,​Inf);​ 
 +
 +
 + _for(i,​1,​n) 
 + Insert(i+n,​i+n+1,​0,​Inf);​ 
 + int flow;LL cost; 
 + solver.MCMF(s,​t,​flow,​cost);​ 
 + enter(cost);
  return 0;  return 0;
 } }
 </​code>​ </​code>​
 </​hidden>​ </​hidden>​
 +
 +
 +
2020-2021/teams/legal_string/jxm2001/图论_3.1595258186.txt.gz · 最后更改: 2020/07/20 23:16 由 jxm2001