文章目录
- [Luogu P4298 [CTSC2008]祭祀](https://www.luogu.com.cn/problem/P4298)
- [LOJ #115. 无源汇有上下界可行流](https://loj.ac/problem/115)
- [LOJ #116. 有源汇有上下界最大流](https://loj.ac/problem/116)
- [LOJ #117. 有源汇有上下界最小流](https://loj.ac/problem/117)
- 有源汇上下界最小费用可行流
- 最小割树
- [Luogu P5029 T'ill It's Over](https://www.luogu.com.cn/problem/P5029)
- [CF852D Exploration plan](https://www.luogu.com.cn/problem/CF852D)
- [CF546E Soldier and Traveling](https://www.luogu.com.cn/problem/CF546E)
- [Luogu P1231 教辅的组成](https://www.luogu.com.cn/problem/P1231)
- [Luogu P2825 [HEOI2016/TJOI2016]游戏](https://www.luogu.com.cn/problem/P2825)
- [P6517 [CEOI2010 day1] alliances](https://www.luogu.com.cn/problem/P6517)
Luogu P4298 [CTSC2008]祭祀
毒瘤网络流题.
前置知识:Dilworth
第一行:
|反链|=|最小可重复链覆盖|= n − n- n−最大匹配= n − n- n−最小点覆盖
对于第二行:
实际上,比较麻烦的是构造方案.
设拆点二分图的最大独立集为
I
I
I,对于拆点后都属于
I
I
I的点集设为
A
,
A,
A,最大匹配为$m $
则有:
∣
I
∣
−
∣
A
∣
<
=
n
|I|-|A|<=n
∣I∣−∣A∣<=n(单次出现的点)
又
∣
I
∣
=
2
n
−
m
|I|=2n-m
∣I∣=2n−m,所以
∣
A
∣
>
=
n
−
m
|A|>=n-m
∣A∣>=n−m.
又因为每一条链最多选一个点,最多又
n
−
m
n-m
n−m条链,所以
∣
A
∣
=
n
−
m
|A|=n-m
∣A∣=n−m.
再证明一下, A A A为反链.
最大独立集是最小点覆盖的补集.
如果我们在残余网络中与st联通的标记一下.
那么左部已标记,右边未标记为最大独立集.
所以相连的点不可能同时被标记.否则和标记的结果矛盾.
对于第三行:
我们暴力选择一个点加入反链,然后求一下剩余部分的反链大小即可.
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=210,M=N*N,inf=-1u/2;
void qr(int &x) {scanf("%d",&x);}
int n,m,b[N][N],ans,c[N],d[N],q[N],l,r,st,ed;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}
bool v[N];
void bfs() {
q[l=r=1]=ed; memset(c,0,sizeof c); memset(d,0,sizeof d);
++c[d[ed]=1]; memcpy(cur,last,ed+1<<2);
for(int x=q[l];l<=r;x=q[++l])
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z&&d[y]+1==d[x]) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!--c[d[x]]) d[st]=ed+1;
++c[++d[x]]; cur[x]=last[x]; return s;
}
int calc() {
/*最大反链=n-最小可重复链覆盖=总点数-最大匹配
证明: 设拆点二分图的最大独立集为I,对于拆点后都属于I的点集设为A,最大匹配为m
则有: I-|A|<=n(I可以把拆出的点都包含)
又I=2n-m,所以|A|>=n-m.
又因为每一条链最多选一个点,最多又n-m条链,所以|A|=n-m*/
int tot=0,res;
len=1; memset(last,0,sizeof last);
for(int i=1;i<=n;i++) if(!v[i]) q[++tot]=i,add(st,i,1),add(i+n,ed,1);
for(int i=1;i<=tot;i++) {
int x=q[i];
for(int j=1;j<=tot;j++) if(j^i) {
int y=q[j];
if(b[x][y])
add(x,y+n,1);
}
}
bfs(); res=tot; tot=tot*2+1;
while(d[st]<=tot)
res-=dfs(st,inf);
return res;
}
bool vis[N];
void DFS(int x) {
vis[x]=1;
for(int k=last[x],y;k;k=a[k].next)
if(a[k].c&&!vis[y=a[k].y]) DFS(y);
}
void solve1() {
printf("%d\n",ans=calc()); DFS(st);
for(int i=1;i<=n;i++) putchar((vis[i]&!vis[i+n])+'0');
puts("");
}
int main() {
qr(n); qr(m); st=0; ed=n*2+1;
for(int i=1,x,y;i<=m;i++) qr(x),qr(y),b[x][y]=1;
for(int i=1;i<=n;i++) b[i][i]=1;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
b[i][j]|=b[i][k]&b[k][j];
solve1();
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) v[j]=b[i][j]||b[j][i];
putchar((calc()+1==ans)+'0');
}
return 0;
}
LOJ #115. 无源汇有上下界可行流
给你一个无源汇网络,每条边的流量 [ L i , R i ] [L_i,R_i] [Li,Ri],求一个可行流
我们先钦定每个边的流量为 L i L_i Li,然后考虑增加某些边(附加边)的流量(容量上限为 R i − L i R_i-L_i Ri−Li)使得流量守恒.
流量守恒式子: ∑ i n f l o w ( i n , x ) = ∑ o u t f l o w ( x , o u t ) \sum_{in} flow(in,x)=\sum_{out} flow(x,out) ∑inflow(in,x)=∑outflow(x,out).
我们设 s u m x = f l o w i n − f l o w o u t sum_x=flowin-flowout sumx=flowin−flowout.
则可以表示 x x x对附加边流量的需求.
- s u m x < 0 , 说 明 x 需 要 更 多 的 流 入 sum_x<0,说明x需要更多的流入 sumx<0,说明x需要更多的流入
- s u m x > 0 , 说 明 x 需 要 更 多 的 流 出 sum_x>0,说明x需要更多的流出 sumx>0,说明x需要更多的流出
- s u m x = 0 , 说 明 x 增 加 的 流 入 = 流 出 sum_x=0,说明x增加的流入=流出 sumx=0,说明x增加的流入=流出
由于:
- 无源汇
- 附加边在原图上跑流量(忽略 L i L_i Li的影响)并不需要满足流量守恒.
这启示我们要加源汇 s s , t t ss,tt ss,tt.
然后根据 s u m x sum_x sumx连边.
- s u m x < 0 , 附 加 边 流 入 的 s u m x 流 量 需 要 流 出 的 方 向 , 所 以 连 ( x , e d , − s u m x ) ( 给 他 一 个 宣 泄 的 地 方 ) sum_x<0,附加边流入的sum_x流量需要流出的方向,所以连(x,ed,-sum_x)(给他一个宣泄的地方) sumx<0,附加边流入的sumx流量需要流出的方向,所以连(x,ed,−sumx)(给他一个宣泄的地方)
- s u m x > 0 , 附 加 边 流 出 的 s u m x 流 量 需 要 流 入 的 方 向 , 所 以 连 ( s t , x , s u m x ) sum_x>0,附加边流出的sum_x流量需要流入的方向,所以连(st,x,sum_x) sumx>0,附加边流出的sumx流量需要流入的方向,所以连(st,x,sumx)
一个显然的式子 ∑ s u m x = 0 \sum sum_x=0 ∑sumx=0.
所以跑完最大流,如果跟 s s ss ss相连的边的容量流尽,说明所有的需求均满足,合法.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=210,M=2.1e4+10,inf=1<<30;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m,st,ed,d[N],c[N],q[N],sum[N],L[M],R[M],ans,s;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}
void bfs() {
int l,r; q[l=r=1]=ed; ++c[d[ed]=1];
memcpy(cur,last,sizeof cur);
for(int x=q[l];l<=r;x=q[++l])
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=min(f-s,a[k].c);
if(z&&d[y]+1==d[x]) {
s+=t=dfs(y,z);
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!--c[d[x]]) d[st]=n+5;
++c[++d[x]]; cur[x]=last[x]; return s;
}
int main() {
qr(n); qr(m);
for(int i=1,x,y;i<=m;i++) {
qr(x); qr(y); qr(L[i]); qr(R[i]);
add(x,y,R[i]-L[i]);
sum[y]+=L[i];
sum[x]-=L[i];
}
st=n+1; ed=n+2;
for(int i=1;i<=n;i++) {
int x=sum[i];
if(x>0) add(st,i,x),s+=x;
if(x<0) add(i,ed,-x);
}
bfs(); while(d[st]<=ed) ans+=dfs(st,inf);
if(ans^s) puts("NO");
else {
puts("YES");
for(int i=1;i<=m;i++) pr2(R[i]-a[i*2].c);
}
return 0;
}
LOJ #116. 有源汇有上下界最大流
做这题之前有必要重新了解网络流的定义.
转自百度百科
可行流 ( f e a s i b l e f l o w ) (feasible~ flow) (feasible flow)图论的一个重要概念。它是满足一定条件的网络流。
对一个网络的某些点指定为发点,规定出提供能力;某些点指定为收点,规定出接收能力。
若一个流对每一发点满足总流出量与总流入量之差不大于提供能力,对每一收点满足总流入量与总流出量之差不小于接收能力,则称这个流为可行流。
也就是网络的流量实际上=汇点流入量-汇点流出量.
步入正题.
这个不是无源汇咋办?直接连 ( T , S , I N F ) (T,S,INF) (T,S,INF),使得原图可以跑出无源汇有上下界可行流,这条边流去的实际流量就是一种可行流.
为了最大化,我们直接再以原图 s t , e d st,ed st,ed跑一次最大流即可.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=210,M=2.1e4+10,inf=1<<30;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m,st,ed,S,T,d[N],c[N],q[N],sum[N],L[M],R[M],ans,s,cnt;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}
void bfs() {
int l,r; q[l=r=1]=ed;
memset(d,0,sizeof d);
memset(c,0,sizeof c);
++c[d[ed]=1];
memcpy(cur,last,sizeof cur);
for(int x=q[l];l<=r;x=q[++l])
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=min(f-s,a[k].c);
if(z&&d[y]+1==d[x]) {
s+=t=dfs(y,z);
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!--c[d[x]]) d[st]=n+5;
++c[++d[x]]; cur[x]=last[x]; return s;
}
int main() {
qr(n); qr(m); qr(S); qr(T);
for(int i=1,x,y;i<=m;i++) {
qr(x); qr(y); qr(L[i]); qr(R[i]);
add(x,y,R[i]-L[i]);
sum[y]+=L[i];
sum[x]-=L[i];
}
st=n+1; ed=n+2;
for(int i=1;i<=n;i++) {
int x=sum[i];
if(x>0) add(st,i,x),s+=x;
if(x<0) add(i,ed,-x);
}
add(T,S,inf);
bfs(); while(d[st]<=n) ans+=dfs(st,inf);
if(ans^s) puts("please go home to sleep");
else {
ans=0; st=S; ed=T;
bfs(); while(d[st]<=n) ans+=dfs(st,inf);
printf("%d\n",ans+a[len].c);
}
return 0;
}
LOJ #117. 有源汇有上下界最小流
类似上题,构造出一组可行解后退流即可.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=5e4+10,M=25e4+N*4,inf=1<<30;
void qr(int &x) {
char c=gc; x=0;
while(!isdigit(c))c=gc;
while(isdigit(c))x=x*10+c-'0',c=gc;
}
int b[N],n,m,st,ed,d[N],c[N],q[N],l,r,S,T,ans,s;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}
void bfs() {
q[l=r=1]=ed;
memset(d,0,sizeof d);
memset(c,0,sizeof c);
++c[d[ed]=1];
memcpy(cur,last,sizeof cur);
for(int x=ed;l<=r;x=q[++l])
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]) { ++c[d[y]=d[x]+1]; q[++r]=y;}
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=min(f-s,a[k].c);
if(z&&d[y]+1==d[x]) {
s+=t=dfs(y,z);
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!--c[d[x]]) d[st]=ed+1;
++c[++d[x]]; cur[x]=last[x]; return s;
}
int main() {
qr(n); qr(m); qr(S); qr(T);
for(int i=1,x,y,z,w;i<=m;i++) {
qr(x); qr(y); qr(z); qr(w);
add(x,y,w-z);
b[y]+=z;
b[x]-=z;
}
st=n+1; ed=n+2;
for(int i=1;i<=n;i++) {
int x=b[i];
if(x>0) add(st,i,x),s+=x;
if(x<0) add(i,ed,-x);
}
add(T,S,inf);
bfs(); while(d[st]<=ed) ans+=dfs(st,inf);
if(ans<s) puts("please go home to sleep");
else {
st=T; ed=S; ans=a[len].c; a[len].c=a[len^1].c=0;bfs();
while(d[st]<=n) ans-=dfs(st,inf);
printf("%d\n",ans);
}
return 0;
}
有源汇上下界最小费用可行流
转成无源汇再搞EK.
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define gc getchar()
using namespace std;
const int N=330,M=1e4+4*N,inf=1<<30;
int n,m,q[N],d[N],f[N],p[N],st,ed,ans,l,r,sum[N]; bool v[N];
struct edge{int y,next,d,c;}a[M]; int len=1,last[N];
void ins(int x,int y,int d,int c) {a[++len]=(edge){y,last[x],d,c}; last[x]=len;}
void add(int x,int y,int d,int c) {ins(x,y,d,c); ins(y,x,-d,0);}
int EK() {
int res=0;
while(1) {
l=r=0; q[r++]=st; f[st]=inf;
memset(d,63,sizeof(d)); d[st]=0;
while(l^r) {
int x=q[l++]; v[x]=0; if(l==N-5) l=0;
for(int k=last[x],y;k;k=a[k].next)
if(a[k].c&&d[y=a[k].y]>d[x]+a[k].d) {
d[y]=d[x]+a[k].d;
f[y]=min(f[x],a[k].c);
p[y]=k;
if(!v[y]) {
q[r++]=y; v[y]=1;
if(r==N-5) r=0;
}
}
}
if(d[ed]==d[0]) return res;
int x=ed,y=f[x];
res+=d[x]*y;
while(x^st) {
int k=p[x];
a[k].c-=y;
a[k^1].c+=y;
x=a[k^1].y;
}
}
}
int main() {
scanf("%d",&n);
for(int i=1,k,x,y;i<=n;i++) {
scanf("%d",&k); sum[i]-=k;
while(k--) {
scanf("%d %d",&x,&y);
sum[x]++; ans+=y; add(i,x,y,inf);
}
if(i>1) add(i,1,0,inf);
}
st=n+1; ed=n+2;
for(int i=1,x;i<=n;i++) {
x=sum[i];
if(x>0) add(st,i,0,x);
if(x<0) add(i,ed,0,-x);
}
ans+=EK();
printf("%d\n",ans); return 0;
}
最小割树
最小割树是求解无向图任意两点间的最小割的有力工具.
算法流程:
对于一个点集,任选两个点 x , y x,y x,y,求一次最小割,划分出两个点集 S , T S,T S,T.
然后递归处理 S , T S,T S,T,知道集合只剩余1个点.
(这可以看作一棵分治树,实际上我们称之为最小割树)
最小割树满足一个很强的性质 : 两点之间的最小割 等于 在最小割树上两点间路径中边的最小权(权值为划分时的最小割)。
-
引理1:定义 c u t ( x , y ) 为 x , y 间 的 最 小 割 cut(x,y)为x,y间的最小割 cut(x,y)为x,y间的最小割.
若最小割分割出了两个连通块 S , T ( x ∈ S , y ∈ T ) S,T(x\in S,y\in T) S,T(x∈S,y∈T),
则 ∀ u ∈ S , v ∈ T , c u t ( u , v ) ≤ c u t ( x , y ) \forall u\in S,v\in T ,cut(u,v)\le cut(x,y) ∀u∈S,v∈T,cut(u,v)≤cut(x,y).
证明:由于只要割掉 c u t ( x , y ) cut(x,y) cut(x,y)对应的边, S , T S,T S,T就不连通,所以 c u t ( u , v ) ≤ c u t ( x , y ) cut(u,v)\le cut(x,y) cut(u,v)≤cut(x,y).
-
引理2: c u t ( x , y ) , c u t ( x , z ) , c u t ( y , z ) cut(x,y),cut(x,z),cut(y,z) cut(x,y),cut(x,z),cut(y,z)的最小值至少出现两次.
假设 c u t ( x , y ) cut(x,y) cut(x,y)为最小值, z ∈ S z\in S z∈S.
则有 c u t ( z , y ) ≤ c u t ( x , y ) cut(z,y)\le cut(x,y) cut(z,y)≤cut(x,y),由于 c u t ( x , y ) cut(x,y) cut(x,y)最小,所以 c u t ( z , y ) = c u t ( x , y ) cut(z,y)=cut(x,y) cut(z,y)=cut(x,y),证毕!
- 推论1: c u t ( x , y ) ≥ min ( c u t ( x , z ) , c u t ( z , y ) ) cut(x,y)\ge \min(cut(x,z),cut(z,y)) cut(x,y)≥min(cut(x,z),cut(z,y)).讨论 c u t ( x , y ) cut(x,y) cut(x,y)是否是最小值即可.
- 推论2:由推论1拓展得: c u t ( x , y ) ≥ m i n ( c u t ( x , a 1 ) , c u t ( a 1 , a 2 ) , . . . c u t ( a k , y ) ) cut(x,y)\ge min(cut(x,a_1),cut(a_1,a_2),...cut(a_k,y)) cut(x,y)≥min(cut(x,a1),cut(a1,a2),...cut(ak,y)).
-
引理3(最小割树定理):
( x , y ) (x,y) (x,y)的最小割为路径上的边权最小值.
证明:设边权依次为 a 1 , a 2 . . . a k a_1,a_2...a_k a1,a2...ak.
由引理1可得: c u t ( x , y ) ≤ a i , i ∈ [ 1 , k ] ∩ N cut(x,y)\le a_i,i\in [1,k]\cap \N cut(x,y)≤ai,i∈[1,k]∩N.
有推论2可得: c u t ( x , y ) ≥ m i n ( c u t ( x , a 1 ) , c u t ( a 1 , a 2 ) , . . . c u t ( a k , y ) ) cut(x,y)\ge min(cut(x,a_1),cut(a_1,a_2),...cut(a_k,y)) cut(x,y)≥min(cut(x,a1),cut(a1,a2),...cut(ak,y)).
所以成立.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=510,M=3100,inf=1<<30;
void qr(int &x) {
char c=gc; x=0;
while(!isdigit(c))c=gc;
while(isdigit(c))x=x*10+c-'0',c=gc;
}
void qw(int x) {
if(x>9) qw(x/10);
putchar(x%10+'0');
}
void pr2(int x) {qw(x); puts("");}
int n,m,d[N],q[N],l,r,st,ed;
struct edge{int y,next,c;}a[M],b[M],A[2*N]; int len=1,last[N],cur[N],Len=1,Last[N];
void add(int x,int y,int c) {
a[++len]=(edge){y,last[x],c}; last[x]=len;
a[++len]=(edge){x,last[y],c}; last[y]=len;
}
void ADD(int x,int y,int c) {
A[++Len]=(edge){y,Last[x],c}; Last[x]=Len;
A[++Len]=(edge){x,Last[y],c}; Last[y]=Len;
}
bool bfs() {
q[l=r=0]=st; memset(d,0,sizeof d); d[st]=1;
memcpy(cur,last,sizeof cur);
for(int x=st;l<=r;x=q[++l])
for(int k=last[x],y;k;k=a[k].next)
if(a[k].c&&!d[y=a[k].y]) {d[y]=d[x]+1; q[++r]=y;}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=min(a[k].c,f-s);
if(z&&d[y]==d[x]+1) {
s+=t=dfs(y,z);
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
memcpy(a,b,sizeof a);
while(bfs())
s+=dfs(st,inf);
return s;
}
int p[N],u[N],v[N];
void solve(int l,int r) {
if(l==r) return ;
st=p[l]; ed=p[r];
ADD(st,ed,dicnic());
int x=0,y=0;
for(int i=l;i<=r;i++)
if(d[p[i]]) u[++x]=p[i];
else v[++y]=p[i];
int mid=l+x-1;
for(int i=l;i<=r;i++)
p[i]=x?u[x--]:v[y--];
solve(l,mid); solve(mid+1,r);
}
int vis[N],now,f[N][N];
void DFS(int x,int t) {
vis[x]=now; f[now][x]=t;
for(int k=Last[x],y;k;k=A[k].next)
if(vis[y=A[k].y]^now) DFS(y,min(t,A[k].c));
}
int main() {
qr(n); qr(m); n++;
for(int i=1,x,y,z;i<=m;i++) qr(x),qr(y),qr(z),add(++x,++y,z);
memcpy(b,a,sizeof b);
for(int i=1;i<=n;i++) p[i]=i;
solve(1,n);
for(now=1;now<=n;now++) DFS(now,inf);
qr(m); int x,y; while(m--) {
qr(x); qr(y);
pr2(f[++x][++y]);
}
return 0;
}
Luogu P5029 T’ill It’s Over
给你n个1,你需要尽可能多地把他们变为k.
有m种操作,每种操作都有一个限定次数.
操作有4种类型:
- x y 把一个x变为y
- x y a 把一个 [ x , y ] [x,y] [x,y]内的数变为a
- x a b把一个x变为 [ a , b ] [a,b] [a,b]中的一个数.
- x y a b把一个 [ x , y ] [x,y] [x,y]内的数变为 [ a , b ] [a,b] [a,b]内的一个数.
线段树优化建边.
线段树分两棵,代表入树和出树.
对于入树,线段树儿子向父亲连有向边.
对于出树,父亲向儿子连边.
我们可以把4中类型划归为第4种: [ x , y ] − > [ a , b ] [x,y]->[a,b] [x,y]−>[a,b].
我们先把他们对应的线段树节点找到,然后对于入,我们把节点连向一个新节点 l e f t left left,对于出,我们用新节点 r i g h t right right连向对应线段树节点.
最后连 ( l e f t , r i g h t , l i m i t ) (left,right,limit) (left,right,limit),表示限制次数.
小细节:对于 [ 1 , n ] [1,n] [1,n]的线段树查区间的话,对应的线段树节点准确来讲是 2 log n 2\log n 2logn的,所以空间要注意一下.
#include<bits/stdc++.h>
#define gc getchar()
using namespace std;
const int N=4e5+10,M=N*8,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,kk,st,ed,d[N],ans;
//Dicnic
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d);
static int q[N],l,r;
q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
//Disc/Queries
struct rec {
int op,lim,x,y,a,b;
} q[N];
int val[N],num;
void disc() {
sort(val+1,val+num+1);
num=unique(val+1,val+num+1)-(val+1);
}
int find(int x) {return lower_bound(val+1,val+num+1,x)-val;}
//Segment Tree
int cnt;
struct Segment_Tree {
int son[2][N],root,id[N];
#define lc son[0][x]
#define rc son[1][x]
void bt(int &x,int l,int r,int op) {
x=++cnt;
if(l==r) {id[l]=x; return ;}
int mid=(l+r)/2;
bt(lc,l,mid,op);
bt(rc,mid+1,r,op);
if(op==1) add(lc,x,inf),add(rc,x,inf);
else add(x,lc,inf),add(x,rc,inf);
}
int sta[44],top;
void ask(int x,int l,int r,int L,int R) {
if(L<=l&&r<=R) {sta[++top]=x; return ;}
int mid=(l+r)>>1;
if(L<=mid) ask(lc,l,mid,L,R);
if(mid< R) ask(rc,mid+1,r,L,R);
}
void Find(int L,int R) {top=0;ask(root,1,num,L,R);}
} in,out;//入树,出树.
int main() {
qr(n); qr(m); qr(kk); if(kk==1) {pr2(n); return 0;}
for(int i=1;i<=m;i++) {
qr(q[i].op); qr(q[i].lim);
switch(q[i].op) {
case 1:qr(q[i].x); qr(q[i].a); break;
case 2:qr(q[i].x); qr(q[i].y); qr(q[i].a); break;
case 3:qr(q[i].x); qr(q[i].a); qr(q[i].b); break;
case 4:qr(q[i].x); qr(q[i].y); qr(q[i].a); qr(q[i].b); break;
}
val[++num]=q[i].x;
val[++num]=q[i].y;
val[++num]=q[i].a;
val[++num]=q[i].b;
}
val[++num]=1; val[++num]=kk; disc();
in.bt(in.root,1,num,1);
out.bt(out.root,1,num,2);
st=in.id[find(1)]; add(out.id[find(kk)],ed=++cnt,n);
for(int i=1;i<=num;i++) add(out.id[i],in.id[i],inf);
for(int i=1;i<=m;i++) {
int op=q[i].op,t=q[i].lim,x=find(q[i].x),y=q[i].y,l=find(q[i].a),r=q[i].b,left,right;
if(op==1) add(in.id[x],out.id[l],t);
else if(op==3) {
out.Find(l,find(r));
right=++cnt;
add(in.id[x],right,t);
for(int j=1;j<=out.top;j++) add(right,out.sta[j],t);
}
else if(op==2) {
in.Find(x,find(y));
left=++cnt;
add(left,out.id[l],t);
for(int j=1;j<=in.top;j++) add(in.sta[j],left,t);
}
else {
left=++cnt; right=++cnt;
in.Find(x,find(y));
out.Find(l,find(r));
for(int j=1;j<=in.top;j++) add(in.sta[j],left,t);
for(int j=1;j<=out.top;j++) add(right,out.sta[j],t);
add(left,right,t);
}
}
pr2(dicnic());
return 0;
}
CF852D Exploration plan
预处理距离,二分长度,跑最大流.
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=810,M=N*N,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,st,ed,d[N],q[N],ans,b[N][N];
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int p[N],cnt,K;
int dicnic(int x) {
len=1; memset(last,0,sizeof last); st=0; ed=n+cnt+1;
for(int i=1;i<=cnt;i++) {
add(st,i,1);//得到点
for(int j=1;j<=n;j++)
if(b[p[i]][j]<=x) add(i,j+cnt,1);
}
for(int i=1;i<=n;i++) add(i+cnt,ed,1);//接纳点
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
int main() {
qr(n); qr(m); qr(cnt); qr(K);
for(int i=1;i<=cnt;i++) qr(p[i]);
memset(b,63,sizeof b);
for(int i=1,x,y,t;i<=m;i++) qr(x),qr(y),qr(t),b[x][y]=b[y][x]=min(b[x][y],t);
for(int i=1;i<=n;i++) b[i][i]=0;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
b[i][j]=min(b[i][j],b[i][k]+b[k][j]);
int l=0,r=1731315,mid;
while(l^r) {
mid=(l+r)>>1;
if(dicnic(mid)>=K) r=mid;
else l=mid+1;
}
if(r>1731311) puts("-1");
else pr2(r);
return 0;
}
CF546E Soldier and Traveling
有一个图,你需要把每个点上的人移动到至多经过1条边的地方,使得最后每个点上的人满足要求.
拆点+用反向边流量构造.
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=210,M=N*N,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,st,ed,d[N],q[N],ans,u[N],v[N],A,B;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
void out() {puts("NO"); exit(0);}
int p[N];
int main() {
qr(n); qr(m); st=0; ed=2*n+1;
for(int i=1;i<=n;i++) qr(u[i]),A+=u[i],add(st,i,u[i]),add(i,i+n,inf);
for(int i=1;i<=n;i++) qr(v[i]),B+=v[i],add(i+n,ed,v[i]);
if(A^B) out();
for(int i=1,x,y;i<=m;i++)
qr(x),qr(y),add(x,y+n,inf),add(y,x+n,inf);
if(dicnic()^A) out();
puts("YES");
for(int i=1;i<=n;i++) {
memset(p+1,0,n<<2);
for(int k=last[i],y;k;k=a[k].next) {
y=a[k].y;
if(y==st) continue;
p[y-n]=a[k^1].c;
}
for(int j=1;j<=n;j++) pr1(p[j]);
puts("");
}
return 0;
}
Luogu P1231 教辅的组成
三分图,注意把书拆点限流.
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=4e4+10,M=N*4,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
int main() {
int u,v,w,x,y; qr(v); qr(u); qr(w); st=0; ed=u+v*2+w+1;
for(int i=1;i<=u;i++) add(st,i,1);
for(int i=u+1;i<=u+v;i++) add(i,i+v,1);//把书拆点
for(int i=u+v*2+1;i<ed;i++) add(i,ed,1);
qr(m); while(m--) {
qr(y); qr(x); y+=u;
add(x,y,1);
}
qr(m); while(m--) {
qr(x); qr(y); x+=u+v; y+=u+v*2;
add(x,y,1);
}
pr2(dicnic());
return 0;
}
Luogu P2825 [HEOI2016/TJOI2016]游戏
发掘1性质.
炸弹连接了竖连通块和横连通块,每个连通块只能连一条边,最大匹配实际上就是最大化炸弹数.
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int C=55,N=C*C*2,M=N*6,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
int row[N][N],col[N][N],s1,s2;
char s[N][N];
int main() {
qr(n); qr(m);
for(int i=1;i<=n;i++) {
scanf("%s",s[i]+1); ++s1;
for(int j=1;j<=m;j++) {
if(s[i][j]=='#') s1++;
else row[i][j]=s1;
}
}
for(int j=1;j<=m;j++) {
++s2;
for(int i=1;i<=n;i++) {
if(s[i][j]=='#') s2++;
else col[i][j]=s2;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='*') add(row[i][j],col[i][j]+s1,1);
st=0; ed=s1+s2+1;
for(int i=1;i<=s1;i++) add(st,i,1);
for(int j=1;j<=s2;j++) add(j+s1,ed,1);
pr2(dicnic());
return 0;
}
P6517 [CEOI2010 day1] alliances
比较难处理的是"人类"的情况.
这个时候我们考虑拆点,把每个点拆成从横竖两种联盟情况.
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int C=77,N=C*C*3,M=N*10,inf=2e9;
template<class o>void qr(o&x) {
char c=gc;int f=1;x=0;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o>void qw(o x) {
if(x/10)qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x);puts("");
}
int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}
bool bfs() {
memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
memcpy(cur,last,sizeof cur);
while(l<=r) {
int x=q[l++];
for(int k=last[x],y;k;k=a[k].next)
if(!d[y=a[k].y]&&a[k].c) {
d[y]=d[x]+1;
q[++r]=y;
}
}
return d[ed];
}
int dfs(int x,int f) {
if(x==ed) return f;
int s=0,t;
for(int &k=cur[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].c;
if(z>0&&d[y]==d[x]+1) {
s+=t=dfs(y,min(f-s,z));
a[k].c-=t; a[k^1].c+=t;
if(s==f) return f;
}
}
if(!s) d[x]=0;
return s;
}
int dicnic() {
int s=0;
while(bfs())
s+=dfs(st,inf);
return s;
}
int b[C][C],pos[C][C],cnt,e[C][C][4];
bool v[C][C][4];
char str[C*3][C*3];
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
void out() {puts("Impossible!"); exit(0);}
int main() {
qr(n); qr(m); st=0; ed=n*m*3+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) qr(b[i][j]),pos[i][j]=++cnt;
int sa=0,sb=0; cnt*=2;
for(int i=1;i<=n;i++)
for(int j=1,id;j<=m;j++)
if(b[i][j]) {
id=pos[i][j]*2;
if(!((i+j)&1)) {
sa+=b[i][j];
if(b[i][j]^2) add(st,++cnt,b[i][j]),add(cnt,id-1,4),add(cnt,id,4);
else add(st,id-1,1),add(st,id,1);
for(int t=0;t<4;t++) {
int x=i+dx[t],y=j+dy[t];
if(x&&y&&x<=n&&y<=m) e[i][j][t]=len+1,add(id-t/2,pos[x][y]*2-t/2,1);
}
}
else {
sb+=b[i][j];
if(b[i][j]^2) ++cnt,add(id-1,cnt,4),add(id,cnt,4),add(cnt,ed,b[i][j]);
else add(id-1,ed,1),add(id,ed,1);
}
}
if((sa^sb)||dicnic()^sa) out();
else {
for(int i=1;i<=n;i++)
for(int j=2-(i&1);j<=m;j+=2)
for(int t=0;t<4;t++)
if(e[i][j][t]&&!a[e[i][j][t]].c)
v[i][j][t]=v[i+dx[t]][j+dy[t]][t^1]=1;
memset(str,'.',sizeof str);
for(int i=1,x=2;i<=n;i++,x+=3)
for(int j=1,y=2;j<=m;j++,y+=3)
if(b[i][j]) {
str[x][y]='O';
for(int t=0;t<4;t++)
if(v[i][j][t])
str[x+dx[t]][y+dy[t]]='X';
}
m=3*m+1;
for(int i=1;i<=3*n;i++)
str[i][m]=0,puts(str[i]+1);
}
return 0;
}