用户工具

站点工具


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

差别

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

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:jxm2001:图论_3 [2020/07/21 18:57]
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
行 33: 行 33:
 最后从黑格向有冲突的白格连一条边,容量待定。 最后从黑格向有冲突的白格连一条边,容量待定。
  
-问题转化为最小割问题事实上图不连通等价于没有从源点到黑格,黑格经过冲突边到白格,白格再到汇点的路径。+问题转化为最小割问题。 
 + 
 +事实上网络流图不连通等价于没有从源点到黑格,经过冲突边到白格,最后到汇点的路径。
  
 这又等价于不会同时选择冲突的黑格和白格,即最终的目的。 这又等价于不会同时选择冲突的黑格和白格,即最终的目的。
行 43: 行 45:
 <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 MAXS=105,​MAXN=MAXS*MAXS,​MAXM=6*MAXN,​Inf=0x7fffffff;​ const int MAXS=105,​MAXN=MAXS*MAXS,​MAXM=6*MAXN,​Inf=0x7fffffff;​
 const int dir[][2]={{0,​1},​{0,​-1},​{1,​0},​{-1,​0}};​ const int dir[][2]={{0,​1},​{0,​-1},​{1,​0},​{-1,​0}};​
行 186: 行 141:
 </​hidden>​ </​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]] [[https://​www.luogu.com.cn/​problem/​P2770|洛谷p2770]]
行 213: 行 594:
 <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=205,​MAXM=6000,​Inf=0x7fffffff;​ const int MAXN=205,​MAXM=6000,​Inf=0x7fffffff;​
 struct Edge{ struct Edge{
行 400: 行 734:
 <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=105,​MAXM=6000;​ const int MAXN=105,​MAXM=6000;​
 struct Edge{ struct Edge{
行 560: 行 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{
行 707: 行 949:
  solver.MCMF(s,​t,​flow,​cost);​  solver.MCMF(s,​t,​flow,​cost);​
  enter(-cost);​  enter(-cost);​
- return 0; 
-} 
-</​code>​ 
-</​hidden>​ 
- 
-=== 拓展 === 
- 
-如果题目把开区间改成闭区间,拆点的时候当作 $[lef,​rig+1)$ 处理就行。 
- 
-====  最小路径覆盖问题 ==== 
- 
-[[https://​www.luogu.com.cn/​problem/​P2764|洛谷p2764]] 
- 
-=== 题意 === 
- 
-给定一个 $\text{DAG}$,求最小的不相交路径集,使得每个点恰好属于其中一条路径。(允许路径长度为 $0$,此时路径仅覆盖一个点) 
- 
-=== 题解 === 
- 
-先用 $n$ 条长度为 $0$ 的路径覆盖 $n$ 个点,然后考虑合并路径。 
- 
-将每个点拆成入点和出点,易知每个入点/​出点最多被合并一次,否则有两条路径相交。 
- 
-从源点连一条容量为 $1$ 的边到每个入点,从每个出点连一条容量为 $1$ 的边到汇点,则可以满足上述的合并次数限制。 
- 
-最后,对每条边,从入点连一条容量为 $1$ 的边到出点,表示以入点结束的路径可与以出点开始的路径合并一次。 
- 
-最后需要合并次数最多,跑最大流即可。最小路径覆盖为节点数减去最大流。 
- 
-关于路径打印,只要在求完最大流后跑残量网络即可。 
- 
-<hidden 查看代码>​ 
-<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=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;  return 0;
 } }
行 925: 行 988:
 <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=5e5+5,​Inf=0x7fffffff;​ const int MAXN=1005,​MAXM=5e5+5,​Inf=0x7fffffff;​
 struct Edge{ struct Edge{
行 1111: 行 1127:
 <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=1e5+5,​MAXM=2e5+5,​Inf=0x7fffffff;​ const int MAXN=1e5+5,​MAXM=2e5+5,​Inf=0x7fffffff;​
 struct Edge{ struct Edge{
行 1267: 行 1236:
 </​code>​ </​code>​
 </​hidden>​ </​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;​
 + }
 + }
 + }
 + }
 + 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;
 +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;
 +}
 +</​code>​
 +</​hidden>​
 +
 +
 +
2020-2021/teams/legal_string/jxm2001/图论_3.1595329066.txt.gz · 最后更改: 2020/07/21 18:57 由 jxm2001