【网络流24题】题解汇总

0. 前言

网络流算是在OI中一个博大精深的问题了。使用它解题的关键就是知道如何建图。网络流24题就是其中建图比较典型的题目了。下面我将按照我刷题的顺序写下每道题的解题报告。

注:部分题目在LOJ上题面和输入格式与洛谷上的有出路,此处以洛谷题面为准。

0.5 一些约定

( u , v , f ) (u,v,f) (u,v,f) 一条从 u u u v v v,流量为 f f f 的边。

( u , v , f , c ) (u,v,f,c) (u,v,f,c) 一条从 u u u v v v,流量为 f f f,费用为 c c c 的边。

1. 飞行员配对问题

题目链接:洛谷P2756 或 LOJ 6000

题意:有 m + n m+n m+n 个飞行员,其中 m m m 个为英国皇家飞行员, n n n 个为外籍飞行员。已知有若干对皇家飞行员和外籍飞行员可以配对。求最多可以配成多少对,并输出方案。 1 ≤ n , m ≤ 100 1 \leq n,m \leq 100 1n,m100

题解:二分图最大匹配模板。但是由于是网络流24题,所以我这里写的是网络流的方法。

超级源点 S S S 1 1 1 m m m 连流量为 1 1 1 的边, m + 1 m+1 m+1 n n n 与超级汇点 T T T 连流量为 1 1 1 的边,中间连题目中所给的边,流量为 + ∞ +\infty +

至于输出方案,只要检查对于中读入的边,流过去的流量是否非零,即反向边的容量非零即可。

代码:

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n,m;
int ecnt=1,head[100005],dep[100005];
struct edge{
	int to,next,cap;
} e[100005];
bool b[100005];
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].next=head[u];head[u]=ecnt;
}
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0; 
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=head[now];i;i=e[i].next){
			int v=e[i].to;
			if(dep[v]==-1&&e[i].cap){
				dep[v]=dep[now]+1;
				q.push(v);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int w,ret=0;
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].to;
		if(dep[v]==dep[x]+1&&e[i].cap){
			w=dfs(v,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	if(!ret)	dep[x]=-1;
	return ret;
}
int dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))
		tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int main(){//源点0,汇点n+1
	scanf("%d%d",&m,&n);
	while(1){
		int x,y;scanf("%d%d",&x,&y);
		if(x==-1&&y==-1)	break;
		addedge(x,y,0x3f3f3f3f);//建中间的边
		addedge(y,x,0);
	}
	for(int i=1;i<=m;i++)	addedge(0,i,1),addedge(i,0,0);//建源点连向1~m的边
	for(int i=m+1;i<=n;i++)	addedge(i,n+1,1),addedge(n+1,i,0);//建m+1~n连向汇点的边
	int tot=dinic(0,n+1);
	if(tot==0){
		puts("No Solution!");
		return 0;
	}
	printf("%d\n",tot);
	for(int i=2;i<=ecnt;i=i+2){
		if(e[i].to!=n+1&&e[i^1].to!=0)
			if(e[i].to!=n+1&&e[i^1].to!=n+1)//判断是否为中间的边
				if(e[i^1].cap!=0){//如果反向边容量不为0
					printf("%d %d\n",e[i^1].to,e[i].to);
				}
	}
	return 0;
}

2. 软件补丁问题

题目链接:洛谷P2761 或 LOJ 6009

题意:有一个公司要应对软件中的 n n n b u g bug bug ,有 m m m 个修复 b u g bug bug 的程序,对于每个程序有四个集合 b i , 1 , b i , 2 , f i , 1 , f i , 2 b_{i,1},b_{i,2},f_{i,1},f_{i,2} bi,1,bi,2,fi,1,fi,2,如果当前 b u g bug bug 的集合包含 b i , 1 b_{i,1} bi,1 中的所有 b u g bug bug 并且不包含 b i , 2 b_{i,2} bi,2 中的所有 b u g bug bug 的时候,使用这个程序可以花费 t i t_i ti 的时间修复 f i , 1 f_{i,1} fi,1 中的所有 b u g bug bug,而又会新增添 f 2 , i f_{2,i} f2,i 中的所有 b u g bug bug。求修复所有 b u g bug bug 需要的最小时间。 1 ≤ n ≤ 20 1 \leq n \leq 20 1n20 1 ≤ m ≤ 100 1 \leq m \leq 100 1m100

题解:这道题不是网络流啊!

状压 d p dp dp d p i dp_i dpi 表示当 b u g bug bug 集合为 i i i 的时候所需要的时间,最短路转移,就可以过了。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n,m,a[105],dp[1<<21],b1[105],b2[105],f1[105],f2[105];
bool legal(int s,int i){//判断状态为s的时候是否可以使用程序i
	if((b1[i]|s)==s&&!(b2[i]&s))	return true;
	return false;
}
void spfa(){
	queue<int> q;
	q.push((1<<n)-1);
	dp[(1<<n)-1]=0;
	while(!q.empty()){
		int s=q.front();
		q.pop();
		for(int i=1;i<=m;i++)
			if(legal(s,i)){
				int t=(s&(~f1[i]))|f2[i];
				if(dp[s]+a[i]<dp[t]){
					dp[t]=dp[s]+a[i];//最短路转移
					q.push(t);
				}
			}
	}
}
int main(){
	memset(dp,1,sizeof(dp));
	cin>>n>>m;
	for(int i=1;i<=m&&cin>>a[i];i++){
		char ch;
		for(int j=1;j<=n;j++){
			char ch;cin>>ch;
			if(ch=='+')
				b1[i]+=1<<j-1;
			else if(ch=='-')
				b2[i]+=1<<(j-1);
		}
		for(int j=1;j<=n;j++){
			char ch;cin>>ch;
			if(ch=='-')
				f1[i]+=1<<(j-1);
			else if(ch=='+')
				f2[i]+=1<<(j-1);
		}
	}
	spfa();
	cout<<(dp[0]==0x01010101?0:dp[0])<<endl;
	return 0;
}

3. 餐巾计划问题

题目链接:洛谷P1251 或 LOJ 6008

题意:有一个餐厅在 n n n 天中第 i i i 天要用 r i r_i ri 块餐巾。餐厅可以购买新的餐巾,每块餐巾的费用为 p p p 分,或者把用过的餐巾送到快洗部,洗一块需 m m m 天,费用为 f f f 分,或者送到慢洗部,洗一块需 l l l 天,费用为 s s s 分。 1 ≤ n ≤ 2000 1 \leq n \leq 2000 1n2000

题解:费用流的经典题,建图较为恶心。

看了洛谷的题解

我们将每一天拆成早上和晚上两个点,每天晚上会受到用过的餐巾,每天早上又会受到干净的餐巾。

按以下方式建图:

  1. 从源点 S S S 向每一天晚上表示的节点 j j j 连一条边 ( S , j , r i , 0 ) (S,j,r_i,0) (S,j,ri,0),表示每天夜晚获得 r i r_i ri 条用过的餐巾。
  2. 从每一天早上表示的节点 j j j 向汇点 T T T 连一条边 ( j , T , r i , 0 ) (j,T,r_i,0) (j,T,ri,0) 表示向汇点提供 r i r_i ri 条干净的餐巾,流满时表示第 i i i 天的餐巾够用。
  3. 从每一天晚上 j j j 向第二天晚上 k k k 连一条边 ( j , k , + ∞ , 0 ) (j,k,+\infty,0) (j,k,+,0),表示每天晚上可以将用过的餐巾留到第二天晚上。
  4. 从每一天晚上 j j j 向这一天过了快洗所用天数 m m m 的那一天早上 k k k 连一条边 ( j , k , + ∞ , f ) (j,k,+\infty,f) (j,k,+,f),表示每天晚上可以将用过的餐巾送去快洗部,在第 i + m i+m i+m 天早上收到干净的餐巾。
  5. 同理,从每一天晚上 j j j 向这一天过了慢洗所用天数 l l l 的那一天早上 k k k 连一条边 ( j , k , + ∞ , s ) (j,k,+\infty,s) (j,k,+,s),意义同上。
  6. 从源点 S S S 向每一天早上 j j j 连一条边 ( S , j , + ∞ , p ) (S,j,+\infty,p) (S,j,+,p),表示每天早上可以购买干净的餐巾。

然后跑最小费用最大流。

这里用到了一个最小(大)费用最大流的一个重要思想,最大流保证构造满足题目条件(如本题中每一天早上向汇点连边,流满表示够用,如果不流满就不合法了),最小(大)费用保证代价最小。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
#define int long long
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int n,m,t1,t2,m1,m2;
signed main(){//源点0,汇点2n+1
	scanf("%lld",&n);
	int st=0,ed=2*n+1;
	for(int i=1;i<=n;i++){
		int x;scanf("%lld",&x);
		addedge(st,i,x,0);addedge(i,st,0,0);//条件1
		addedge(i+n,ed,x,0);addedge(ed,i+n,0,0);//条件2
    }
    scanf("%lld%lld%lld%lld%lld",&m,&t1,&m1,&t2,&m2);
    for(int i=1;i<=n;i++){
		if(i+1<=n)	addedge(i,i+1,1e9,0),addedge(i+1,i,0,0); //条件3
		if(i+t1<=n)	addedge(i,i+n+t1,1e9,m1),addedge(i+n+t1,i,0,-m1);//条件4
		if(i+t2<=n)	addedge(i,i+n+t2,1e9,m2),addedge(i+n+t2,i,0,-m2);//条件5
		addedge(st,i+n,1e9,m);addedge(i+n,st,0,-m);//条件6
	}
	cout<<Dinic(st,ed)<<endl;
	return 0;
}

4. 深海机器人问题

题目链接:洛谷P4012 或 LOJ 6224

题意:有一张网格图,左下角 ( 0 , 0 ) (0,0) (0,0),右上角 ( Q , P ) (Q,P) (Q,P),有 a a a 个出发位置,对于每一个位置给出三个数 k , x , y k,x,y k,x,y,表示有 k k k 个机器人从 ( x , y ) (x,y) (x,y) 出发。有 b b b 个终点,也给出三个数 k , x , y k,x,y k,x,y,表示有 k k k 个机器人要到 ( x , y ) (x,y) (x,y)。每个机器人可以向上或向右走到终点。每条向右或向上的路径上都有一个生物标本,采集一个生物标本可以获得一些价值。一条路上如果生物标本已被采集过就没有了。求总价值的最大值。

题解:费用流的气息很明显。

建出图来,从源点连向每一个起点连一条边 ( S , i , k , 0 ) (S,i,k,0) (S,i,k,0),再从每一个终点连向汇点连一条边 ( i , T , k , 0 ) (i,T,k,0) (i,T,k,0)。对于每一条网格图中的道路 ( x , y , z ) (x,y,z) (x,y,z),连两条边 ( x , y , 1 , z ) (x,y,1,z) (x,y,1,z) ( x , y , ∞ , 0 ) (x,y,\infty,0) (x,y,,0),因为道路可以通过多次,但标本只能收集一次。然后跑最大费用最大流。这里有一个技巧,将每条边的价值都取相反数,这样就变成了一个最小费用最大流,然后答案再取相反数就行了。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
#define int long long//开long long
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
	e[++ecnt].to=u;e[ecnt].cap=0;e[ecnt].cost=-c;e[ecnt].nxt=head[v];head[v]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int a=read(),b=read(),n=read(),m=read();
inline int id(int x,int y){
	return (m+1)*x+y+1;
}
signed main(){//源点1234,汇点5678(不要问我为什么)
	fz(i,0,n){
		fz(j,0,m-1){
			int num=read();
			addedge(id(i,j),id(i,j+1),1,-num);
			addedge(id(i,j),id(i,j+1),0x3f3f3f3f,0);//向每个点东边节点连边
		}
	}
	fz(i,0,m){
		fz(j,0,n-1){
			int num=read();
			addedge(id(j,i),id(j+1,i),1,-num);
			addedge(id(j,i),id(j+1,i),0x3f3f3f3f,0);//向每个点南边节点连边
		}
	}
	fz(i,1,a){
		int k=read(),x=read(),y=read();
		addedge(1234,id(x,y),k,0);//从源点向每个起点连边
	}
	fz(i,1,b){
		int k=read(),x=read(),y=read();
		addedge(id(x,y),5678,k,0);//从每个终点向汇点连边
	}
	cout<<-Dinic(1234,5678)<<endl;
	return 0;
}

5. 方格取数问题

题目链接:洛谷P2774 或 LOJ 6007

题意:有一个 n × m n \times m n×m 的矩阵,你可以从中选取一些元素使得任意两个数都不相邻,求和的最大值。

解法:网络流之最小割

我们考虑先选中所有方格,再想办法删去权值和尽量小的一批方格。

我们可以建一张图,节点表示矩阵中每一个元素,两节点之间有一条边表示这两个点相邻。我们不难发现,当把矩阵进行黑白间隔染色,相邻两点一定是一黑一白,因此我们构造出的矩阵一定是一个二分图。

那么思路就出来了。我们建一个虚拟源点 S S S,和虚拟汇点 T T T,我们对于所有白色格子上的点,连一条从 S S S 到这个点的边,流量为点权,删掉这条边,就表明不选这个点。同理,对于所有黑色格子上的点,连一条从这个点到 T T T 的边,流量为点权,删掉这条边也表明不选这个点。而二分图内部连着互斥的点,边权为 i n f inf inf (即删掉这条边没有意义),那么我们要求的就是这张图的最小割,根据最大流 = = = 最小割可以通过跑一次最大流求出答案。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),m=read();
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
inline int id(int x,int y){
	return (x-1)*m+y;
}
int head[100005];
struct edge{
	int to,nxt,cap;
} e[100005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
int sum=0;
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int main(){//源点0,汇点nm+1
	fz(i,1,n)	fz(j,1,m){
		int val=read();
		sum+=val;
		if((i+j)%2){
			for(int k=0;k<4;k++){
				int x=i+dx[k],y=j+dy[k];
				if(x<1||x>n||y<1||y>m)	continue;
				addedge(id(i,j),id(x,y),0x3f3f3f3f);
				addedge(id(x,y),id(i,j),0);//向周围点连边
			}
			addedge(0,id(i,j),val);//源点向每个点连边
			addedge(id(i,j),0,0);
		}
		else{
			addedge(id(i,j),n*m+1,val);//每个点向汇点连边
			addedge(n*m+1,id(i,j),0);
		}
	}
	cout<<sum-Dinic(0,n*m+1)<<endl;//记得用总数减
	return 0;
}

6. 运输问题

题目链接:洛谷P4015 或 LOJ 6011

题意:有 m m m 个仓库和 n n n 个零售商店。第 i i i 个仓库有 a i a_i ai个单位的货物,第 j j j 个零售商店需要 b j b_j bj 个单位的货物。从第 i i i 个货物运往 第 j j j 个零售店需要 c i , j c_{i,j} ci,j 的代价。求将仓库中所有货物运送到零售商店的代价的最小值和最大值。

题解:算比较简单的费用流的题。

从源点 S S S 向每个仓库 i i i 连一条边 ( S , i , a i , 0 ) (S,i,a_i,0) (S,i,ai,0),从每个零售商店 i i i 向汇点 T T T 连一条边 ( i , T , b i , 0 ) (i,T,b_i,0) (i,T,bi,0)。仓库 i i i 和零售商店 j j j 之间连一条边 ( i , j , + ∞ , c i , j ) (i,j,+\infty,c_{i,j}) (i,j,+,ci,j),然后跑最小(大)费用最大流即可。

这题的思想与【餐巾计划问题】相同,都是最大流保证合法性,最小(大)费用保证代价最小(大)。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int n=read(),m=read(),a[105],b[105],c[105][105];
int main(){//源点0,汇点n+m+1
	fz(i,1,n){
		a[i]=read();
		addedge(0,i,a[i],0);//从源点向每个仓库连边
		addedge(i,0,0,0);
	}
	fz(i,1,m){
		b[i]=read();
		addedge(i+n,n+m+1,b[i],0);//从每个零售商店向汇点连边
		addedge(n+m+1,i+n,0,0);
	}
	fz(i,1,n){
		fz(j,1,m){
			c[i][j]=read();
			addedge(i,j+n,0x3f3f3f3f,c[i][j]);//从每个仓库向每个零售商店连边
			addedge(j+n,i,0,-c[i][j]);
		}
	}
	cout<<Dinic(0,n+m+1)<<endl;
	ecnt=1;//清除整张图
	memset(head,0,sizeof(head));
	fz(i,1,n){
		addedge(0,i,a[i],0);
		addedge(i,0,0,0);
	}
	fz(i,1,m){
		addedge(i+n,n+m+1,b[i],0);
		addedge(n+m+1,i+n,0,0);
	}
	fz(i,1,n){
		fz(j,1,m){
			addedge(i,j+n,0x3f3f3f3f,-c[i][j]);
			addedge(j+n,i,0,c[i][j]);
		}
	}
	cout<<-Dinic(0,n+m+1)<<endl;
	return 0;
}

7. 骑士共存问题

题目链接:洛谷P3355 或 LOJ 6226

题意:在一个 n × n n \times n n×n 个方格的国际象棋棋盘上,骑士可以攻击的棋盘日字形的方格。棋盘上某些方格设置了障碍,骑士不得进入。求最多能在棋盘上放置多少个骑士。

题解:与方格取数问题类似,只不过增添了一个障碍设置环节,特判一下就行了。

P.S:此题需要当前弧优化,而我不会当前弧优化,所以拿我的代码交上去会 T L E TLE TLE
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int head[1000005];
struct edge{
	int to,nxt,cap;
} e[1000005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[1000005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
int sum=0;
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int dx[]={-1,-2,-2,-1,1,2,2,1};
int dy[]={2,1,-1,-2,2,1,-1,-2};
int n=read(),k=read();
bool vis[205][205]; 
inline int id(int i,int j){
	return (i-1)*n+j;
}
int main(){//源点0,汇点n*n+1
	fz(i,1,k){
		int x=read(),y=read();
		vis[x][y]=1;
	}
	fz(i,1,n){
		fz(j,1,n){
			if(vis[i][j])	continue;
			if((i+j)%2){
				for(int s=0;s<8;s++){
					int x=i+dx[s],y=j+dy[s];
					if(x<1||x>n||y<1||y>n)	continue;
					if(vis[x][y])	continue;
					addedge(id(i,j),id(x,y),0x3f3f3f3f);
					addedge(id(x,y),id(i,j),0);
//					cout<
				}
				addedge(0,id(i,j),1);
				addedge(id(i,j),0,0);
			}
			else{
				addedge(id(i,j),n*n+1,1);
				addedge(n*n+1,id(i,j),0);
			}
		}
	}
	cout<<n*n-k-Dinic(0,n*n+1)<<endl;//总数减
	return 0;
}

8. 分配问题

题目链接:洛谷P4014 或 LOJ 6012

题意:有 n n n 件工作要分配给 n n n 个人做。第 i i i 个人做第 j j j 件工作产生的效益为 c i , j c_{i,j} ci,j 。求总效益的最大值。

题解:与运输问题本质上是一致的,只不过把最小值改成了最大值。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read();
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int x[105][105];
int main(){//源点0,汇点2n+1
	fz(i,1,n){
		fz(j,1,n){
			x[i][j]=read();
			addedge(i,j+n,0x3f3f3f3f,x[i][j]);
			addedge(j+n,i,0,-x[i][j]);
		}
		addedge(0,i,1,0);
		addedge(i,0,0,0);
		addedge(i+n,2*n+1,1,0);
		addedge(2*n+1,i+n,0,0);
	}
	cout<<Dinic(0,2*n+1)<<endl;
	ecnt=1;
	memset(head,0,sizeof(head));
	fz(i,1,n){
		fz(j,1,n){
			addedge(i,j+n,0x3f3f3f3f,-x[i][j]);
			addedge(j+n,i,0,x[i][j]);
		}
		addedge(0,i,1,0);
		addedge(i,0,0,0);
		addedge(i+n,2*n+1,1,0);
		addedge(2*n+1,i+n,0,0);
	}
	cout<<-Dinic(0,2*n+1)<<endl;
	return 0;
}

9. 星际转移问题

题目链接:洛谷P2754 或 LOJ 6015

题意:有 k k k 个人要从地球飞到月球。在太空中有 n n n 个太空站, m m m 个宇宙飞船在其中穿梭。这些宇宙飞船都周期性地停靠,即,在时刻 0 0 0 停靠 s 1 s_1 s1,时刻 1 1 1 停靠 s 2 s_2 s2,……,时刻 r − 1 r-1 r1 停靠 s r s_r sr,时刻 r r r 停靠 s 1 s_1 s1,……。( − 1 -1 1 表示月球, 0 0 0 表示地球)。当有宇宙飞船停靠在当前空间站的时候,可以上这个宇宙飞船。每个人可以在任何时候下宇宙飞船。每个人可以在空间站内停留任意时间。求将这 k k k 个人全部转移到月球的最小时间。无解输出 0 0 0

题解:对于每一时刻,我们都建立 n + 2 n+2 n+2 个节点,表示所有的太空站,和地球月球。

之后对于每一时刻,如果此时有一艘太空船正从 x x x y y y 去,那么,我们从上一时刻的 x x x 到这一时刻的 y y y 连一条流量为该太空船的容量的边。对于每一时刻,从前一时刻的每个星球向这一时刻的每个星球连一条边权为 + ∞ +\infty + 的边(人可以停留在空间站嘛)。然后跑最大流。如果发现某一时刻最大流超过 k k k 就输出。对于无解的情况,就循环到一个较大的数,如果还不行就输出 0 0 0

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),m=read(),k=read();
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
int sum=0;
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int s[100005],len[100005];
int cy[105][1005];
inline int id(int x,int y){//第x时刻的空间站y的编号
	return x*(n+2)+y;
}
int main(){//源点50000,汇点50001
	fz(i,1,m){
		s[i]=read();len[i]=read();
		fz(j,0,len[i]-1){
			cy[i][j]=read();
			cy[i][j]+=2;//可能出现负数
		}
	}
	int sum=0;
	fz(i,0,571){//一个小插曲:这里的571是一个非常吉利的质数,拿着个数作hashbase基本不会被卡
		addedge(50000,id(i,2),0x3f3f3f3f);//源点向地球
		addedge(id(i,2),50000,0);
		addedge(id(i,1),50001,0x3f3f3f3f);//月球向汇点
		addedge(50001,id(i,1),0);
		if(i==0)	continue;
		fz(j,1,n+2){
			addedge(id(i-1,j),id(i,j),0x3f3f3f3f);//上一个时刻向这一个时刻
			addedge(id(i,j),id(i-1,j),0);
//			cout<
		}
		fz(j,1,m){
			addedge(id(i-1,cy[j][(i-1)%len[j]]),id(i,cy[j][i%len[j]]),s[j]);//每个宇宙飞船上一个停靠的位置向这一个停靠的位置
			addedge(id(i,cy[j][i%len[j]]),id(i-1,cy[j][(i-1)%len[j]]),0);
//			cout<
		}
		int x=Dinic(50000,50001);
		sum+=x;
		if(sum>=k){//最大流超过k
			cout<<i<<endl;
			return 0;
		}
//		cout<
	}
	puts("0");//无解啦!
	return 0;
}

10. 太空飞行计划问题

题目链接:洛谷P2762 或 LOJ 6001

题意:有 n n n 个仪器,安装第 i i i 个需要花费 w i w_i wi 元。有 n n n 个实验,进行第 i i i 个需要若干个实验仪器,可以获得 p i p_i pi 的利益。求最大利润并输出方案。

题解:我还是太蒻了,一碰到“费用”这种东西就被带偏了,光想着怎么建费用流,虽然思路基本正确,但是本题是无法用费用流解决的。

最大流之最小割

我们考虑建图:

  1. 对于每个实验 i i i,连一条边 ( S , i , p i ) (S,i,p_i) (S,i,pi)
  2. 对于每个实验 i i i,和每个它需要的器材 j j j,连一条边 ( i , j , + ∞ ) (i,j,+\infty) (i,j,+)(割这条边是没有意义的,不能改变实验与器材之间的关系,只能选择不进行某个实验或者不安装某个器材,因此边权设为 ∞ \infty 。)
  3. 对于每个仪器 i i i,连一条边 ( i , T , c i ) (i,T,c_i) (i,T,ci)

然后跑最小割即最大流就是答案。

接下来考虑输出方案:考虑枚举删除器材和汇点 T T T 之间的边,如果删去后的最大流和原来的最大流的差值等于这条边的边权,那么这条边就是必须满流的,也就是这个器材是必要的。再根据需要的器材我们容易知道要做的实验有哪些。

/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
bool ___=1;
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),m=read();
int head[1005];
struct edge{
	int to,nxt,cap;
	inline void clear(){
		to=nxt=cap=0;
	}
} e[30005],ee[30005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
//	cout<<2<
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
//		cout<
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
//			cout<
			if(dep[to]==-1&&e[i].cap>0){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
//	cout<
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
int sum=0;
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
vector<int> v[505];
int c[505],val[505];
bool s1[505],s2[505];
int main(){//源点0,汇点1000
	int sum=0;
	fz(i,1,n){
		val[i]=read();
		sum+=val[i];
		addedge(0,i,val[i]);//源点向每个实验连边
		addedge(i,0,0);
		char tools[10000];
		memset(tools,0,sizeof tools);
		cin.getline(tools,10000);
		int ulen=0,tool;
		while(sscanf(tools+ulen,"%d",&tool)==1){
			v[i].push_back(tool);
			addedge(i,tool+n,0x3f3f3f3f);//每个实验向需要的器材连边
			addedge(tool+n,i,0);
    		if(tool==0)
				ulen++;
			else{
				while(tool){
					tool/=10;
					ulen++;
				}
			}
			ulen++;
		}
	}
	fz(i,1,m){
		c[i]=read();
		addedge(i+n,1000,c[i]);//每个器材向汇点连边
		addedge(1000,i+n,0);
	}
	memcpy(ee,e,sizeof(e));
	int ans=sum-Dinic(0,1000);
	for(int i=head[1000];i;i=e[i].nxt){
		memcpy(e,ee,sizeof(ee));
		edge tmp=e[i^1];
		e[i^1].cap=0;//删边求出安装哪些器材
		int t=sum-Dinic(0,1000);
//		cout<
//		cout<
		if((t-ans-c[e[i].to-n])==0)	s2[e[i].to-n]=1;
		e[i^1]=tmp;
	}
	fz(i,1,n){
		s1[i]=1;
		for(int j=0;j<v[i].size();j++){
			if(!s2[v[i][j]])	s1[i]=0;//根据器材反推实验
		}
	}
	fz(i,1,n)	if(s1[i])	cout<<i<<" ";
	puts("");
	fz(i,1,m)	if(s2[i])	cout<<i<<" ";
	puts("");
	cout<<ans<<endl;
	return 0;
}

11. 圆桌问题

题目链接:洛谷P3254 或 LOJ 6004

题意:有 m m m 个单位聚餐,第 i i i 个单位有 d i d_i di 个人。有 n n n 个圆桌,第 i i i 个可以容纳 c i c_i ci 个人。保证 ∑ d i = ∑ c i \sum{d_i}=\sum{c_i} di=ci。已知一个圆桌只能坐同一个单位的人。给出一个安排座位的方案,或宣布无解。

题解:暴力建图,不需要任何技巧,从源点向每个单位连 d i d_i di 的流量,从每个单位向每张桌子连 1 1 1 单位的流量,再从每张桌子向汇点连 c i c_i ci 的流量。如果(最大流等于所有单位总人数),则有解。输出方案的时候就检查内部拿些边流过去的流量为 1 1 1,即反向边的容量为 1 1 1 就可以了。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int n=read(),m=read(),sum=0;
int main(){
	fz(i,1,n){//源点0,汇点n+m+1
		int x=read();sum+=x;
		addedge(0,i,x);//从源点向每个单位连边
		addedge(i,0,0);
	}
	fz(i,1,m){
		int x=read();
		addedge(i+n,n+m+1,x);//从每个圆桌向汇点连边
		addedge(n+m+1,i+n,0);
	}
	fz(i,1,n){
		fz(j,1,m){
			addedge(i,j+n,1);//从每个单位向每个圆桌连边
			addedge(j+n,i,0);
		}
	}
	int g=Dinic(0,n+m+1);
	if(g==sum){
		cout<<"1\n";
		fz(i,1,n){
			for(int j=head[i];j;j=e[j].nxt){
				if(e[j].to!=0&&e[j].cap==0){//输出方案
					cout<<e[j].to-n<<" ";
				}
			}
			puts("");
		}
	}
	else	puts("0");
	return 0;
}

12. 试题库问题

题目链接:洛谷P2763 或 LOJ 6006

题意:一个试题库中有 n n n 道试题。每道试题都有一些所属类别。现要从题库中抽取 m m m 道题组成试卷。并要求试卷类型 i i i 必须恰好包含 t i t_i ti 题。设计一个满足要求的组卷算法。

题解:不难想到用试题与类型建立一个二分图。对于每一个试题 i i i 和所有它所属于的类型 j j j 之间连一条边 ( i , j , 1 ) (i,j,1) (i,j,1),对于每一个试题 i i i,连一条边 ( S , i , 1 ) (S,i,1) (S,i,1),对于每一个类型 j j j,连一条边 ( j , T , t i ) (j,T,t_i) (j,T,ti),然后跑最大流就是答案。输出方案和前面类似,这里就不再赘述了。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int k=read(),n=read(),sum=0;
vector<int> ans[22];
int main(){//源点0,汇点n+k+1
	fz(i,1,k){
		int t=read();
		addedge(i+n,n+k+1,t);
		addedge(n+k+1,i+n,0);
		sum+=t;
	}
	fz(i,1,n){
		addedge(0,i,1);
		addedge(i,0,0);
		int p=read();
		fz(j,1,p){
			int x=read();
			addedge(i,x+n,1);
			addedge(x+n,i,1);
		}
	}
	int num=Dinic(0,n+k+1);
	if(num!=sum){
		return puts("No Solution!"),0;
	}
	fz(i,1,n){
		for(int j=head[i];j;j=e[j].nxt){
			if(e[j].to!=0&&e[j].cap==0){
				ans[e[j].to-n].push_back(i);
			}
		}
	}
	fz(i,1,k){
		cout<<i<<": ";
		for(int j=0;j<ans[i].size();j++){
			cout<<ans[i][j]<<" ";
		}
		puts("");
	}
	return 0;
}

13. 最小路径覆盖问题

题目链接:洛谷P2764 或 LOJ 6002

题意:给出一张 D A G DAG DAG,求出其最小路径覆盖(即最少需要多少条有向路径才能覆盖这张图里所有顶点)。并输出方案。

题解:我们先假设所有点都单独成一个路径,这显然不是最优解,因此我们要想着合并两条路径。

每个节点只能有一条出边,一条入边。如果我们将每个点拆成一个入点和一个出点,那么:

  1. 入点只能连向出点

  2. 每个入点只能连向一个出点

  3. 每个出点只能被一个入点连

这……不是二分图匹配吗?这样我们就回到了【飞行员配对问题】。于是我们就可以愉快的AC了。

/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int n=read(),m=read();
struct DSU{
	int fa[100005];
	inline void init(){
		fz(i,1,n)	fa[i]=i;
	}
	inline int find(int x){
		return (fa[x]==x)?x:fa[x]=find(fa[x]);
	}
	inline void merge(int x,int y){
		int a=find(x),b=find(y);
		if(a==b)	return;
		fa[a]=b;
	}
} dsu;
vector<int> ans[155];
int main(){
	fz(i,1,m){
		int x=read(),y=read();
		addedge(x*2,y*2-1,1);
		addedge(y*2-1,x*2,0);
	}
	fz(i,1,n){
		addedge(0,i*2,1);
		addedge(i*2,0,0);
		addedge(i*2-1,2*n+1,1);
		addedge(2*n+1,i*2-1,0);
	}
	int anss=n-Dinic(0,2*n+1);
	dsu.init();
	fz(i,1,n){
		for(int j=head[i*2];j;j=e[j].nxt){
			if(e[j].to>0&&e[j].cap==0){
				dsu.merge(i,(e[j].to+1)/2);
			}
		}
	}
	fz(i,1,n){
		ans[dsu.find(i)].push_back(i);
	}
	fz(i,1,n){
		if(dsu.find(i)==i){
			for(int j=0;j<ans[i].size();j++)	cout<<ans[i][j]<<" ";
			puts("");
		}
	}
	cout<<anss<<endl;
	return 0;
}

14. 魔术球问题

题目链接:洛谷P2765 或 LOJ 6003

题意:假设有 n n n 根柱子,现要按下述规则在这n根柱子中依次放入编号为 1 , 2 , 3 … 1,2,3\dots 1,2,3的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。

题意:如果我们在和为平方数的点之间连边,那么“能够放在 n n n 根柱子上”,就转化为了“能够用 n n n 条有向路径覆盖”,这样就和上一道题相同了

/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read();
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
struct DSU{
	int fa[100005];
	inline void init(int t){
		fz(i,1,t)	fa[i]=i;
	}
	inline int find(int x){
		return (fa[x]==x)?x:fa[x]=find(fa[x]);
	}
	inline void merge(int x,int y){
		int a=find(x),b=find(y);
		if(a==b)	return;
		fa[a]=b;
	}
} dsu;
vector<int> ansv[155];
int main(){
	int ans=0,sum=0;
	while(1){
		ans++;
		for(int i=1;i<=100;i++){
			if(i*i-ans>=1&&i*i-ans<ans){
//				cout<
				addedge((i*i-ans)*2,ans*2-1,1);
				addedge(ans*2-1,(i*i-ans)*2,0);
			}
		}
		addedge(0,ans*2,1);
		addedge(ans*2,0,0);
		addedge(ans*2-1,50000,1);
		addedge(50000,ans*2-1,0);
		sum+=Dinic(0,50000);
//		cout<
		if(ans-sum>n){
			ans--;
			break;
		}
	}
	ecnt=1;
	memset(head,0,sizeof(head));
	fz(i,1,ans){
		fz(j,1,100){
			if(j*j-i>=1&&j*j-i<i){
//				cout<
				addedge((j*j-i)*2,i*2-1,1);
				addedge(i*2-1,(j*j-i)*2,0);
			}
		}
		addedge(0,i*2,1);
		addedge(i*2,0,0);
		addedge(i*2-1,50000,1);
		addedge(50000,i*2-1,0);
	}
	Dinic(0,50000);
	cout<<ans<<endl;
	dsu.init(ans);
	fz(i,1,ans){
		for(int j=head[i*2];j;j=e[j].nxt){
			if(e[j].to>0&&e[j].cap==0){
				dsu.merge(i,(e[j].to+1)/2);
			}
		}
	}
	fz(i,1,ans){
		ansv[dsu.find(i)].push_back(i);
	}
	fz(i,1,ans){
		if(dsu.find(i)==i){
			for(int j=0;j<ansv[i].size();j++)	cout<<ansv[i][j]<<" ";
			puts("");
		}
	}
	return 0;
}

15. 最长不下降子序列问题

题目链接:洛谷P2766 或 LOJ 6005

题意:给出长度为 n n n 的数组 a a a,求出:

  1. 最长不下降子序列的长度 l l l
  2. 有多少个长度为 l l l 的最长不下降子序列(元素不能同时属于超过 2 2 2 个最长不下降子序列)
  3. 有多少个长度为 l l l 的最长不下降子序列(除 a 1 , a n a_1,a_n a1,an 之外每个元素不能同时属于超过 2 2 2 个最长不下降子序列)

题解:首先动态规划求出第一问。

考虑建图:

  1. 把序列每位i拆成两个点,入点和出点,对于每个点,连一条从入点到出点,容量为 1 1 1 的有向边。
  2. 如果以 i i i 为开头的最长不下降子序列的长度为 l l l,那么建一条从这个点的出点向汇点 T T T,容量为 1 1 1 的边。
  3. 如果以 i i i 为开头的最长不下降子序列的长度为 1 1 1,那么建一条从源点向这个点的入点的边,容量为 1 1 1
  4. 如果 j ≥ i j \geq i ji a j ≥ a i a_j \geq a_i ajai f j = f i + 1 f_j=f_i+1 fj=fi+1,那么连一条从 i i i 的出点到 j j j 的入点的边,容量为 1 1 1

跑出来的最大流就是第而问的答案。

对于第三问,只需将源点到 1 , n 1,n 1,n 的入点, 1 , n 1,n 1,n 的出点到汇点, 1 , n 1,n 1,n 之间的入点和出点之间的边的容量改为 ∞ \infty ,然后跑最大流就是答案。

注意特判 l = 1 l=1 l=1

/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),a[505],dp[505],mx;
int head[100005];
struct edge{
	int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
	queue<int> q;
	memset(dep,-1,sizeof(dep));
	q.push(s);dep[s]=0;
	while(!q.empty()){
		int cur=q.front();q.pop();
		for(int i=head[cur];i;i=e[i].nxt){
			int to=e[i].to;
			if(dep[to]==-1&&e[i].cap){
				dep[to]=dep[cur]+1;
				q.push(to);
			}
		}
	}
	if(dep[t]!=-1)	return 1;
	return 0;
}
inline int dfs(int x,int t,int f){
	if(x==t)	return f;
	int ret=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(dep[y]==dep[x]+1&&e[i].cap){
			int w=dfs(y,t,min(f-ret,e[i].cap));
			e[i].cap-=w;
			e[i^1].cap+=w;
			ret+=w;
			if(ret==f)	return f;
		}
	}
	return ret;
}
inline int Dinic(int s,int t){
	int tot=0;
	while(bfs(s,t))	tot+=dfs(s,t,0x3f3f3f3f);
	return tot;
}
int main(){
	fz(i,1,n)	a[i]=read();
	fz(i,1,n){
		dp[i]=1;
		for(int j=1;j<i;j++){
			if(a[j]<=a[i]){
				dp[i]=max(dp[i],dp[j]+1);
			}
		}
		mx=max(mx,dp[i]);
	}
	cout<<mx<<endl;
	if(mx==1){
		cout<<n<<endl<<n<<endl;return 0;
	}
	fz(i,1,n){
		if(dp[i]==1){
			addedge(0,i*2-1,1);
			addedge(i*2-1,0,0);
		}
		if(dp[i]==mx){
			addedge(i*2,2*n+1,1);
			addedge(2*n+1,i*2,0);
		}
		for(int j=i+1;j<=n;j++){
			if(a[j]>=a[i]&&dp[j]==dp[i]+1){
				addedge(i*2,j*2-1,1);
				addedge(j*2-1,i*2,0);
			}
		}
		addedge(i*2-1,i*2,1);
		addedge(i*2,i*2-1,0);
	}
	cout<<Dinic(0,2*n+1)<<endl;
	ecnt=1;memset(head,0,sizeof(head));
	fz(i,1,n-1){
		if(dp[i]==1){
			addedge(0,i*2-1,1);
			addedge(i*2-1,0,0);
		}
		if(dp[i]==mx){
			addedge(i*2,2*n+1,1);
			addedge(2*n+1,i*2,0);
		}
		for(int j=i+1;j<=n;j++){
			if(a[j]>=a[i]&&dp[j]==dp[i]+1){
				addedge(i*2,j*2-1,1);
				addedge(j*2-1,i*2,0);
			}
		}
		addedge(i*2-1,i*2,1);
		addedge(i*2,i*2-1,0);
	}
	addedge(0,1,INT_MAX);
	addedge(1,0,0);
	addedge(1,2,INT_MAX);
	addedge(2,1,0);
	if(dp[n]==mx){
		addedge(2*n-1,2*n,INT_MAX);
		addedge(2*n,2*n-1,0);
		addedge(2*n,2*n+1,INT_MAX);
		addedge(2*n+1,2*n,0);
	}
	cout<<Dinic(0,2*n+1)<<endl;
	return 0;
}

16. 最长 k k k 可重区间集问题

题目链接:洛谷P3358 或 LOJ 6014

题意:有 n n n 个开区间 [ l i , r i l_i,r_i li,ri],你要选出一些开区间,使得任意一个点被覆盖的次数不超过 k k k。定义一个开区间 [ l , r l,r l,r] 的价值为 r − l r-l rl,求出取出开区间价值的最大值。

题解:

设所有开区间中右端点最大值为 m x mx mx

我们考虑建图。

  1. 对于每个位置 i i i ,连一条边 ( i , i + 1 , k , 0 ) (i,i+1,k,0) (i,i+1,k,0)
  2. 连边 ( S , 1 , k , 0 ) (S,1,k,0) (S,1,k,0) ( m x , T , k , 0 ) (mx,T,k,0) (mx,T,k,0)
  3. 对于每一条从 l l l r r r ,长度为 l e n len len 的线段,连一条边 ( l , r , 1 , l e n ) (l,r,1,len) (l,r,1,len)

然后跑最大费用最大流就行了,注意离散化。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[6010];
int dist[6010],flow[6010],pre[6010],pos[6010];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int n=read(),k=read(),l[1005],r[1005],key[1005],cnt=0,hs[1005],cnt2=0;
int main(){
	fz(i,1,n)	l[i]=read(),r[i]=read(),key[++cnt]=l[i],key[++cnt]=r[i];
	sort(key+1,key+cnt+1);
	fz(i,1,cnt){
		if(key[i]!=key[i-1]){
			hs[++cnt2]=key[i];
		}
	}
	fz(i,1,n){
		int len=r[i]-l[i];
		l[i]=lower_bound(hs+1,hs+cnt2+1,l[i])-hs;
		r[i]=lower_bound(hs+1,hs+cnt2+1,r[i])-hs;
//		cout<
		addedge(l[i],r[i],1,-len);
		addedge(r[i],l[i],0,len);
	}
	fz(i,1,cnt2-1){
		addedge(i,i+1,0x3f3f3f3f,0);
		addedge(i+1,i,0,0);
	}
	addedge(cnt2+1,1,k,0);
	addedge(1,cnt2+1,0,0);
	addedge(cnt2,cnt2+2,k,0);
	addedge(cnt2+2,cnt2,0,0);
	cout<<-Dinic(cnt2+1,cnt2+2)<<endl;
	return 0;
}

17. 最长 k k k 可重线段集问题

题目链接:洛谷P3357 或 LOJ 6227

题意:有 n n n 条开线段 ( x 1 , y 1 ) (x1,y1) (x1,y1) ( x 2 , y 2 ) (x2,y2) (x2,y2)。你需要选择一些线段,使得对于任意 p p p,直线 x = p x=p x=p 与这些线段交点个数的总和不超过 k k k。定义一条线段的价值为这条线段的长度,求出选出线段价值总和的最大值。

题解:与上一题基本一致,只不过需要特判 x 1 = x 2 x1=x2 x1=x2 的情况。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
#define int long long
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[100005];
struct edge{
	int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[10010];
int dist[10010],flow[10010],pre[10010],pos[10010];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int n=read(),k=read(),l[10010],r[10010],len[10010],key[10010],cnt=0,hs[1005],cnt2=0;
signed main(){
	fz(i,1,n){
		int x=read(),y=read(),xx=read(),yy=read();
		len[i]=((int)(sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy))));
		x*=2;xx*=2;
		if(x==xx)	l[i]=x,r[i]=x+1;
		else		l[i]=min(x,xx)+1,r[i]=max(x,xx);
	}
	key[0]=-0x3f3f3f3f;
	fz(i,1,n)	key[++cnt]=l[i],key[++cnt]=r[i];
	sort(key+1,key+cnt+1);
	fz(i,1,cnt){
		if(key[i]!=key[i-1]){
			hs[++cnt2]=key[i];
		}
	}
	fz(i,1,n){
		l[i]=lower_bound(hs+1,hs+cnt2+1,l[i])-hs;
		r[i]=lower_bound(hs+1,hs+cnt2+1,r[i])-hs;
//		cout<
		addedge(l[i],r[i],1,-len[i]);
		addedge(r[i],l[i],0,len[i]);
	}
	fz(i,1,cnt2-1){
		addedge(i,i+1,0x3f3f3f3f,0);
		addedge(i+1,i,0,0);
	}
	addedge(cnt2+1,1,k,0);
	addedge(1,cnt2+1,0,0);
	addedge(cnt2,cnt2+2,k,0);
	addedge(cnt2+2,cnt2,0,0);
	cout<<-Dinic(cnt2+1,cnt2+2)<<endl;
	return 0;
}

18. 汽车加油行驶问题

题目链接:洛谷P4009 或 LOJ 6223

题意:有一个 n × m n\times m n×m 的网格图,有些格子上有加油站,汽车初始位置在 ( 0 , 0 ) (0,0) (0,0)(左上角),要走到 ( n , m ) (n,m) (n,m)(右下角),每行驶一个需要消耗一格油,加满油 后行驶 k k k 格后就没油了。汽车向右或向下形式不需要代价,向上或向左需要 B B B 的代价。如果汽车行驶到一个加油站,那么 必须 花费 A A A 的代价加满油。否则可以花费 C C C 的代价建立加油站并加满油。求行驶到 ( n , m ) (n,m) (n,m) 的最小代价。

我们就可以按照剩余流量,分层建图。

令第 0 0 0 层为满油层,第 K K K 层为空油层。规定坐标 [ z , x , y ] [z,x,y] [z,x,y] 的意义为:第 z z z 层的 ( x , y ) (x,y) (x,y) 位置。

首先,对于一个加油站的位置 ( x , y ) (x,y) (x,y)
如果有 z ≠ 0 z \neq 0 z=0 ,连一条边 ( [ z , x , y ] , [ 0 , x , y ] , ∞ , A ) ([z,x,y],[0,x,y],\infty,A) ([z,x,y],[0,x,y],,A)

否则,即 z = 0 z=0 z=0 ,向下一层的邻居节点连边。

这时候就有人问了,到加油站不是强制加油吗,为什么第 0 0 0 层时却不用加油?

因为第 0 0 0 层的状态只有在刚加满油的时候才会出现。其它时候,当你从其他地方开进一个加油站时,一定不会在第 0 0 0 层。

然后,对于一个非加油站:

默认可以建油站,连一条边 ( [ z , x , y ] , [ 0 , x , y ] , I N F , A + C ) ([z,x,y],[0,x,y],INF,A+C) ([z,x,y],[0,x,y],INF,A+C)

那又有问题了,同一个节点,油站建一次就行了凭什么再来时还要再建?

因为我们的路径必然无环。有环的局面必然是向上或向左绕路去加油的,但已经修了加油站,就不会再想着去绕路了。

同时,如果 z ≠ K z \neq K z=K ,可以向下一层的邻居节点连边。

关于源点和汇点,初始状态必然只有 ( S , [ 0 , 0 , 0 ] , 1 , 0 ) (S,[0,0,0],1,0) (S,[0,0,0],1,0) 一种。

但是对于所有的 z ∈ [ 0 , K ] z\in [0,K] z[0,K] ,都可以有 ( [ z , n − 1 , n − 1 ] , T , 1 , 0 ) ([z,n-1,n-1],T,1,0) ([z,n1,n1],T,1,0)

所以图就建完了。答案即为最小费用最大流。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
#define int long long
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[1000005];
struct edge{
	int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int g[105][105];
int n=read(),k=read(),a=read(),b=read(),c=read();
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
const int cst[]={0,0,b,b};
inline int id(int x,int y,int lv){
	return n*n*lv+((x-1)*n+y-1);
}
signed main(){
	fz(i,1,n)	fz(j,1,n)	cin>>g[i][j];
	fz(i,1,n){
		fz(j,1,n){
			fz(l,0,k){
				if(g[i][j]){
					if(l!=0){
						addedge(id(i,j,l),id(i,j,0),1,a);
						addedge(id(i,j,0),id(i,j,l),0,-a);
					}
				}
				else{
					if(l!=0){
						addedge(id(i,j,l),id(i,j,0),1,a+c);
						addedge(id(i,j,0),id(i,j,l),0,-a-c);
					}
				}
				if((!g[i][j]&&l!=k)||(g[i][j]&&l==0)){
					for(int o=0;o<4;o++){
						if(i+dx[o]<1||i+dx[o]>n||j+dy[o]<1||j+dy[o]>n)	continue;
						addedge(id(i,j,l),id(i+dx[o],j+dy[o],l+1),1,cst[o]);
						addedge(id(i+dx[o],j+dy[o],l+1),id(i,j,l),0,-cst[o]);
					}
				}
			}
		}
	}
	addedge(300000,id(1,1,0),1,0);
	addedge(id(1,1,0),300000,0,0);
	fz(l,0,k){
		addedge(id(n,n,l),300001,1,0);
		addedge(300001,id(n,n,l),0,0);
	}
//	puts("a");
	cout<<Dinic(300000,300001)<<endl;
	return 0;
}

19. 数字梯形问题

题目链接:洛谷P4013 或 LOJ 6010

题意:给定一个由 n n n行数字组成的数字梯形如下图所示。梯形的第一行有 m m m个数字。从梯形的顶部的 m m m个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。

规则 1 1 1:从梯形的顶至底的 m m m条路径互不相交。

规则 2 2 2:从梯形的顶至底的 m m m条路径仅在数字结点处相交。

规则 3 3 3:从梯形的顶至底的 m m m条路径允许在数字结点相交或边相交。

求出在三种规则下所有路径经过的数的和的最大值

题解:拆点。将每个点的入点与出点之间连一条流量为 1 1 1,费用为这个点上的数字的边。在每个点的出点与它下面两个点的入点间连一条流量为 1 1 1,费用为 0 0 0 的边。对于上底的每个点,连一条从源点流向这个点的入点的边,流量为 1 1 1,费用为 0 0 0,对于下底的每个点,连一条从这个点的出点到汇点的边。跑最大流,就是第一问的答案。

将连向汇点的所有边,以及每个点入点和出点之间的边,流量都改为 ∞ \infty ,跑最大流就是第二问的答案。

将所有除了源点连向的边的流量都改为 ∞ \infty ,跑最大流就是第三问的答案。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int l=read(),r=read(),a[45][45];
int ecnt=1,head[1000005];
struct edge{
	int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
inline int id(int i,int j){
	int sum=0;
	fz(t,1,i-1){
		sum+=l+t-1;
	}
	return sum+j;
}
signed main(){
	r+=l-1;
	fz(i,1,r-l+1){
		fz(j,1,l+i-1){
			cin>>a[i][j];
		}
	}
	fz(i,1,r-l+1){
		fz(j,1,l+i-1){
			addedge(2*id(i,j)-1,2*id(i,j),1,-a[i][j]);
			addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
//			cout<
			if(i==r-l+1)	continue;
			addedge(2*id(i,j),2*id(i+1,j)-1,1,0);
			addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
			addedge(2*id(i,j),2*id(i+1,j+1)-1,1,0);
			addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
		}
//		puts("");
	}
	fz(i,1,l)	addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
	fz(i,1,r)	addedge(id(r-l+1,i)*2,30001,1,0),addedge(30001,id(r-l+1,i)*2,0,0);
	cout<<-Dinic(30000,30001)<<endl;
	ecnt=1;
	memset(head,0,sizeof(head));
	fz(i,1,r-l+1){
		fz(j,1,l+i-1){
			addedge(2*id(i,j)-1,2*id(i,j),0x3f3f3f3f,-a[i][j]);
			addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
//			cout<
			if(i==r-l+1)	continue;
			addedge(2*id(i,j),2*id(i+1,j)-1,1,0);
			addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
			addedge(2*id(i,j),2*id(i+1,j+1)-1,1,0);
			addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
		}
//		puts("");
	}
	fz(i,1,l)	addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
	fz(i,1,r)	addedge(id(r-l+1,i)*2,30001,0x3f3f3f3f,0),addedge(30001,id(r-l+1,i)*2,0,0);
	cout<<-Dinic(30000,30001)<<endl;
	ecnt=1;
	memset(head,0,sizeof(head));
	fz(i,1,r-l+1){
		fz(j,1,l+i-1){
			addedge(2*id(i,j)-1,2*id(i,j),0x3f3f3f3f,-a[i][j]);
			addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
//			cout<
			if(i==r-l+1)	continue;
			addedge(2*id(i,j),2*id(i+1,j)-1,0x3f3f3f3f,0);
			addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
			addedge(2*id(i,j),2*id(i+1,j+1)-1,0x3f3f3f3f,0);
			addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
		}
//		puts("");
	}
	fz(i,1,l)	addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
	fz(i,1,r)	addedge(id(r-l+1,i)*2,30001,0x3f3f3f3f,0),addedge(30001,id(r-l+1,i)*2,0,0);
	cout<<-Dinic(30000,30001)<<endl;
	return 0;
}

小技巧:拆点可以限制某一个点的出入次数,适用于对出入次数有要求的题目。

20. 火星探险问题

题目链接:洛谷P3356 或 LOJ 6225

题意:有 n n n 火星探测器位于 ( 0 , 0 ) (0,0) (0,0),要走到 ( n , m ) (n,m) (n,m)。有一些点上有障碍物,还有一些有岩石标本。已知每块岩石标本只能被采集一次。岩石标本被采集后,其他探测车可以从原来岩石标本所在处通过。探测车不能通过有障碍的地面。本题限定探测车只能从登陆处沿着向南或向东的方向朝传送器移动,而且多个探测车可以在同一时间占据同一位置。如果某个探测车在到达传送器以前不能继续前进,则该车所采集的岩石标本将全部损失。求获得岩石标本个数的最大值

题解:拆点,然后老套路,在拆出的两个点之间连一条流量为 ∞ \infty ,费用为 0 0 0 的边。如果这个点是一块石头,再连一条边权为 1 1 1,费用为 1 1 1 的边。然后跑最大费用最大流。输出方案直接暴搜就行了

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int ecnt=1,head[1000005];
struct edge{
	int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int n=read(),p=read(),q=read(),a[40][40];
int dx[]={1,0};
int dy[]={0,1};
int id(int i,int j){
	return (i-1)*q+j;
}
char ans[100005];
int len=0,occ[1000005];
inline void dfs(int x,int y){
//	cout<
	for(int i=head[id(x,y)*2];i;i=e[i].nxt){
		int z=e[i].to;
		if(occ[i]>=e[i^1].cap)	continue;
		if(z==(id(x+1,y)*2-1)&&x+1<=p){
			occ[i]++;
			ans[++len]='0';
//			cout<<0;
			dfs(x+1,y);
			return;
		}
		if(z==(id(x,y+1)*2-1)&&y+1<=q){
			occ[i]++;
//			cout<<1;
			ans[++len]='1';
			dfs(x,y+1);
			return;
		}
	}
}
int main(){
	swap(p,q);
	fz(i,1,p)	fz(j,1,q)	a[i][j]=read();
	fz(i,1,p){
		fz(j,1,q){
			if(a[i][j]==1)	continue;
			addedge(id(i,j)*2-1,id(i,j)*2,0x3f3f3f3f,0);
			addedge(id(i,j)*2,id(i,j)*2-1,0,0);
//			cout<
			if(a[i][j]==2){
				addedge(id(i,j)*2-1,id(i,j)*2,1,-1);
				addedge(id(i,j)*2,id(i,j)*2-1,0,1);
			}
		}
	}
	fz(i,1,p){
		fz(j,1,q){
			if(a[i][j]==1)	continue;
//			cout<<"i="<
			for(int l=0;l<2;l++){
				int x=i+dx[l],y=j+dy[l];
				if(x<1||x>p||y<1||y>q)	continue;
				if(a[x][y]==1)	continue;
//				cout<
				addedge(id(i,j)*2,id(x,y)*2-1,0x3f3f3f3f,0);
				addedge(id(x,y)*2-1,id(i,j)*2,0,0);
			}
		}
	}
	addedge(0,id(1,1)*2-1,n,0);
	addedge(id(1,1)*2-1,0,0,0);
	addedge(id(p,q)*2,2*p*q+1,n,0);
	addedge(2*p*q+1,id(p,q)*2,0,0);
	Dinic(0,2*p*q+1);
	fz(i,1,n){
		len=0;
		dfs(1,1);
		fz(j,1,len)	cout<<i<<" "<<ans[j]<<endl;
	}
	return 0;
}

21. 负载平衡问题

题目链接:洛谷P4016 或 LOJ 6013

题意:有 n n n 个仓库形成一个环,第 i i i 个有 a i a_i ai 个库存。每次可以在相邻仓库之间搬运库存。现在要将每个仓库里的库存数量相同。求最少搬运量。

题解:我们不难发现,不管怎么搬来搬去,这 n n n 个仓库的库存总量一定是定值,也就是说,最后每个仓库的库存一定是 a a a 数组的平均值,假设为 g g g

对于所有的 a i a_i ai,我们可以算出 a i − g a_i-g aig 的值。如果这个值为正,那么意味着这个仓库需要运一些到其他仓库,就连一条边 ( S , i , a i − g , 0 ) (S,i,a_i-g,0) (S,i,aig,0),否则,意味着这个仓库需要从别的仓库中获得一些库存,就连一条边 ( i , T , g − a i , 0 ) (i,T,g-a_i,0) (i,T,gai,0)。对于相邻两个仓库 i , j i,j i,j,由于它们可以无限制地运送库存,连一条边 ( i , j , ∞ , 1 ) (i,j,\infty,1) (i,j,,1)

然后跑最小费用最大流就可以了。

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),a[105];
int ecnt=1,head[1000005];
struct edge{
	int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	memset(dist,63,sizeof(dist));
	queue<int> q;
	vis[s]=0;
	dist[s]=0;
	flow[s]=0x3f3f3f3f;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
//		cout<
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
//			cout<
			if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
				dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
				flow[y]=min(flow[x],e[i].cap);
//				cout<
				if(vis[y]){
					q.push(y);
					vis[y]=false;
				}
			}
		}
		q.pop();
	}
//	cout<
	return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
	int sum=0;
	while(spfa(s,t)){
		sum+=flow[t]*dist[t];
		for(int i=t;i!=s;i=pos[i]){
			e[pre[i]].cap-=flow[t];
			e[pre[i]^1].cap+=flow[t];
		}
	}
	return sum;
}
int main(){
	int sum=0;
	fz(i,1,n)	a[i]=read(),sum+=a[i];
	sum/=n;
	fz(i,1,n){
		if(a[i]<sum)	addedge(0,i,sum-a[i],0),addedge(i,0,0,0);
		else			addedge(i,n+1,a[i]-sum,0),addedge(n+1,i,0,0);
	}
	fz(i,1,n-1){
		addedge(i,i+1,0x3f3f3f3f,1);
		addedge(i+1,i,0,-1);
		addedge(i+1,i,0x3f3f3f3f,1);
		addedge(i,i+1,0,-1);
	}
	addedge(1,n,0x3f3f3f3f,1);
	addedge(n,1,0,-1);
	addedge(n,1,0x3f3f3f3f,1);
	addedge(1,n,0,-1);
	cout<<Dinic(0,n+1)<<endl;
	return 0;
}

22. 航空路线问题

题目链接:洛谷P2770 或 LOJ 6122

题意:有 n n n 个城市,从西向东依次是 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,,sn。有 m m m 个航班连接这 n n n 个城市。有一个人要从最西端的城市旅行到最东端的城市,然后再回到最西端的城市。已知除了西端的城市被访问两次,其他城市要么没有被访问,要么只被访问一次。求最多可以访问多少个城市,并输出路径。无解输出 N o   S o l u t i o n ! \mathrm{No\ Solution!} No Solution!

题解:看到城市只能访问一次,你第一个想到的应该是……

拆点大法好哇!

我们将每个点拆成两个点——入点和出点(老套路)。然后在除了第一个城市和最后一个城市,每个城市入点和出点之间连一条边,容量为 1 1 1,费用为 1 1 1,在第一个和最后一个城市的入点和出点之间连一条边,容量为 2 2 2,费用为 1 1 1。如果两点之间有一条边,那么在第一个点的出点和第二个点的入点间连一条边,容量为 1 1 1,费用为 0 0 0。连一条从源点到第一个城市的入点,容量为 2 2 2,费用为 0 0 0 的边,连一条从第 n n n 个点的出点,流量为 2 2 2 ,费用为 0 0 0 的边。跑最小费用最大流,最小费用 − 2 -2 2 就是答案(第一个城市和最后一个城市被算了两次,故减 2 2 2)。无解的情况就是最大流 ≠ 2 \neq 2 =2

然后是输出方案,我们只用找到两条从 1 1 1 n n n 的路径,并且没有交点,然后将第一条正序输出,第二条倒序输出。注意 n n n 会输出两次。

然后你美滋滋地交上去,以为可以 A C \mathrm{AC} AC,然鹅

91 91 91?WA #2?

然后你会发现可以构造出一组毒瘤数据把你的程序卡得半死:

2 1
ycx
tzc
ycx tzc

如果你的程序输出无解,那么您太强了,您完美地掉进了这道题的坑里。我们不难发现,如果最大流为 1 1 1,并且 1 1 1 n n n 之间有直接的边,那么也是有解的,因此需要加一个小小的特判。(我竟然能在今天中午吃火锅的时候想到这个我也是服了我了)

所以说这题看似简单实则不容易一遍 A C AC AC

/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include 
using namespace std;
#define fi			first
#define se			second
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define put(x)		putchar(x)
#define eoln        put('\n')
#define space		put(' ')
inline int read(){
	int x=0,neg=1;char c=getchar();
	while(!isdigit(c)){
		if(c=='-')	neg=-1;
		c=getchar();
	}
	while(isdigit(c))	x=x*10+c-'0',c=getchar();
	return x*neg;
}
inline void print(int x){
	if(x<0){
		putchar('-');
		print(abs(x));
		return;
	}
	if(x<=9)	putchar(x+'0');
	else{
		print(x/10);
		putchar(x%10+'0');
	}
}
int n=read(),m=read(),fl=0;
map<string,int> mp;
int ecnt=1,head[1000005];
struct edge{
	int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
	e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
	memset(vis,1,sizeof(vis));
	

你可能感兴趣的:(题目)