网络流学习笔记——简单题

因为写过的网络流博客太多,一天发不完,所以就把简单题合在一起发。

O.约定

\(S\):源点

\(\mathbb{S}\):源点集合(在网络流跑完后与\(S\)连通的点集)

\(T\):汇点

\(\mathbb{T}\):源点集合(在网络流跑完后与\(T\)连通的点集)

\((p,q)\):一条从\(p\)\(q\)的有向边(包括反边)

\((x,y,z)\):一条从\(x\)\(y\),边权为\(z\)的边(包括反边)

\((u,v,w,c)\):一条从\(u\)\(v\),边权为\(w\),单位流量费用为\(c\)的边(包括反边)

\((i,j,[k,l])\):一条从\(i\)\(j\),边权限制为闭区间\([k,l]\)的边。

\((a,b,[c,d],e)\),一条从\(a\)\(b\),限制为\([c,d]\),费用为\(e\)的边。

\(flow\):最大流

\(cut\):最小割(两者虽然值相同,意义却不同)

\(cost\):最小/大费用

\(\color{Thistle}\colorbox{CadetBlue}{Let's GO!!!}\)

I.最小路径覆盖问题

刚好是第200道AC的紫黑题~~~

一眼看去不会做。但是题目好心地已经把解法写上去了。很明显,就算看了解法,我还是不理解。看了题解,就明白了。

首先,我们可以初始成每条路径只包括单一节点。然后,我们每次尝试合并两条路径。

每个节点只能有一条出边,一条入边。如果我们将每个点拆成一个入点和一个出点(即题面上的\(x_i\)\(y_i\)),那么:

1.入点只能连向出点

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

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

想到了什么?

二分图匹配!

当然,作为网络流的24题,当然要用网络流水它了

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[350],cnt,S,T,dis[350],cur[350],res,to[350];
bool ok[350];
struct node{
	int to,next,val;
}edge[30100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
queue<int>q;
bool bfs(){
	memset(dis,0,sizeof(dis)),dis[S]=1,q.push(S);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dis[edge[i].to])dis[edge[i].to]=dis[x]+1,q.push(edge[i].to);
	}
	return dis[T]!=0;
}
bool reach;
int dfs(int x,int flow){
	if(x==T){
		reach=true;
		res+=flow;
		return flow;
	}
	int used=0;
	for(int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dis[edge[i].to]!=dis[x]+1)continue;
		int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)ae(S,i,1),ae(i,S,0),ae(i+n,T,1),ae(T,i+n,0);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),ae(x,y+n,1),ae(y+n,x,0);
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}
	for(int i=1;i<=n;i++)for(int j=head[i];j!=-1;j=edge[j].next)if(!edge[j].val&&edge[j].to>n&&edge[j].to<=2*n)to[i]=edge[j].to-n,ok[edge[j].to-n]=true;
	for(int i=1;i<=n;i++){
		if(ok[i])continue;
		int j=i;
		while(j)printf("%d ",j),j=to[j];puts("");
	}
	printf("%d\n",n-res);
	return 0;
} 

II.魔术球问题

一开始没有思路,就仿照上一题,枚举每一对球,如果它们编号和为完全平方数就连边。然后就是前一道题的路径覆盖了。

我们枚举一个\(n\),表示放多少个球。之后就用前面的算法暴力验证。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[350],cnt,S,T,dis[350],cur[350],res,to[350];
bool ok[350];
struct node{
	int to,next,val;
}edge[30100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
queue<int>q;
bool bfs(){
	memset(dis,0,sizeof(dis)),dis[S]=1,q.push(S);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dis[edge[i].to])dis[edge[i].to]=dis[x]+1,q.push(edge[i].to);
	}
	return dis[T]!=0;
}
bool reach;
int dfs(int x,int flow){
	if(x==T){
		reach=true;
		res+=flow;
		return flow;
	}
	int used=0;
	for(int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dis[edge[i].to]!=dis[x]+1)continue;
		int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)ae(S,i,1),ae(i,S,0),ae(i+n,T,1),ae(T,i+n,0);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),ae(x,y+n,1),ae(y+n,x,0);
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}
	for(int i=1;i<=n;i++)for(int j=head[i];j!=-1;j=edge[j].next)if(!edge[j].val&&edge[j].to>n&&edge[j].to<=2*n)to[i]=edge[j].to-n,ok[edge[j].to-n]=true;
	for(int i=1;i<=n;i++){
		if(ok[i])continue;
		int j=i;
		while(j)printf("%d ",j),j=to[j];puts("");
	}
	printf("%d\n",n-res);
	return 0;
} 

但是,这个就算吸了臭氧,还是会T三个点。

看了题解之后,发现每次我们实际上不用重新全跑,只要加入点\(n\)和所有与它相关的边。这个时候,剩下的图仍可以看作一个比较奇怪的残量网络。暴力\(++n\)直到\(n-flow > N\)。然后,此时的\(n-1\)即为正确答案。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int HF=5000;
int N,n,head[10010],cnt,S,T,dep[10010],cur[10010],res,to[10010];
struct node{
	int to,next,val;
}edge[301000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
bool ok[10010];
int main(){
	scanf("%d",&N);
	memset(head,-1,sizeof(head)),S=HF*2+1,T=HF*2+2;
	while(++n){
		ae(S,n,1),ae(n,S,0),ae(HF+n,T,1),ae(T,HF+n,0);
		for(int i=1;i<n;i++){
			int sr=int(sqrt(i+n));
			if(sr*sr==i+n)ae(i,HF+n,1),ae(HF+n,i,0);
		}
		Dinic();
		if(n-res>N)break;
	}
	n--;
	printf("%d\n",n);
	for(register int i=1;i<=n;i++)for(register int j=head[i];j!=-1;j=edge[j].next)if(!edge[j].val&&edge[j].to>HF&&edge[j].to<=HF*2)to[i]=edge[j].to-HF;
	for(register int i=1;i<=n;i++){
		if(ok[i])continue;
		for(int j=i;j;j=to[j])printf("%d ",j),ok[j]=true;puts("");
	}
	return 0;
}

III.试题库问题

第一道完全自己做出来的网络流题祭

我们对于每种类型\(i\),建立一个节点\(x_i\),并从源点\(S\)连来(这种类型需要的题数)单位的流量。

对于每道题目\(i\),建立节点\(y_i\),并向汇点连去\(1\)单位流量。

如果题目\(i\)是一道类型\(j\)的题,那么从\(x_j\)\(y_i\)\(1\)单位流量。

最后跑最大流就行了。

为什么?

实际上就是一道二分图多重匹配模板。因为各个类型之间不会连边,各道题目直接也不会连边。而每个类型必须连到多个题目,但每个题目只能作为一个类型被选中。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[2010],cnt,S,T,dep[2010],res,sum,cur[2010];
struct node{
	int to,next,val;
}edge[301000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
int main(){
	scanf("%d%d",&m,&n),memset(head,-1,sizeof(head)),S=n+m+1,T=n+m+2;
	for(int i=1,x;i<=m;i++)scanf("%d",&x),ae(S,i,x),ae(i,S,0),sum+=x;
	for(int i=1;i<=n;i++)ae(i+m,T,1),ae(T,i+m,0);
	for(int i=1,t1,t2;i<=n;i++){
		scanf("%d",&t1);
		while(t1--)scanf("%d",&t2),ae(t2,m+i,1),ae(m+i,t2,0);
	}
	Dinic();
	if(res!=sum){puts("No Solution!");return 0;}
	for(int i=1;i<=m;i++){
		printf("%d:",i);
		for(int j=head[i];j!=-1;j=edge[j].next)if(!edge[j].val&&edge[j].to>m&&edge[j].to<=n+m)printf(" %d",edge[j].to-m);
		puts("");
	}
	return 0;
}

V.方格取数问题

第二道自己AC的网络流祭

本题介绍一种经典的建图方法:奇偶建图法

首先,暴力建图方法肯定是相邻两个格子之间连边,之后跑最小割。但是,这样肯定会出现一些问题。

设某个格子的坐标为\((x,y)\),观察得,\((x+y)\)为奇的点仅与\((x+y)\)为偶的点相连,奇点与偶点之间都不会连边。它满足二分图性质。

处理二分图时,我们很自然地从源点向每个奇点连一条值为该奇点权值的边,然后从每个奇点向相邻的偶点连一条无穷权值的边(防止割断),再从每个偶点向汇点连一条值为该偶点权值的边。之后跑最小割。答案即为(整个方格的和-最小割)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,head[10100],cnt,S,T,num[110][110],cur[10100],dep[10100],res,sum;
struct node{
	int to,next,val;
}edge[301000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
signed main(){
	scanf("%lld%lld",&n,&m),memset(head,-1,sizeof(head)),S=n*m+1,T=n*m+2;
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)scanf("%lld",&num[i][j]),sum+=num[i][j];
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		if(!((i+j)&1))ae(i*m+j+1,T,num[i][j]);
		else{
			ae(S,i*m+j+1,num[i][j]);
			if(j+1<m)ae(i*m+j+1,i*m+j+2,0x3f3f3f3f);
			if(j-1>=0)ae(i*m+j+1,i*m+j,0x3f3f3f3f);
			if(i+1<n)ae(i*m+j+1,(i+1)*m+j+1,0x3f3f3f3f);
			if(i-1>=0)ae(i*m+j+1,(i-1)*m+j+1,0x3f3f3f3f);
		}
	}
	Dinic();
	printf("%lld\n",sum-res);
	return 0;
}

VIII.负载平衡问题

费用流第一题~~~

一看到题目有些发懵,似乎用最大流并不能解决问题。

看了题解。

我们首先可以把每个节点最终状态求出来(即\(average=\Sigma num_i /n\))。

然后,对于每个\(num_i>average\),连边\((S,i,num_i-average,0)\),表示该节点初始有\(num_i-average\)单位的流量可供调出,同时调出这些流量的费用为\(0\)

对于每个\(num_i<average\),连边\((i,T,average-num_i,0)\),表示该节点需要接受\(average-num_i\)单位的流量,并且接受的费用为\(0\)

之后,对于两两相邻的点对\((x,x\pm 1)\),连边\((x,x\pm 1,INF,1)\),表示可以花单位流量代价为\(1\)的费用在两个节点之间传递任意大流量。

然后跑最小费用最大流即可。最大流保证了一定是合法的转移,最小费用保证答案最优。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,num[110],head[110],dis[110],fr[110],id[110],cnt,average,S,T,cost;
struct node{
	int to,next,val,cost;
}edge[10100];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[110];
bool SPFA(){
	memset(dis,0x3f3f3f3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]>dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x3f3f3f3f)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	scanf("%d",&n),S=n,T=n+1,memset(head,-1,sizeof(head));
	for(int i=0;i<n;i++)scanf("%d",&num[i]),average+=num[i];
	average/=n;
	for(int i=0;i<n;i++){
		if(num[i]>average)ae(S,i,num[i]-average,0);
		if(num[i]<average)ae(i,T,average-num[i],0);
		ae(i,(i+1)%n,0x3f3f3f3f,1);
		ae(i,(i-1+n)%n,0x3f3f3f3f,1);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

IX.圆桌问题

第三道自己AC的网络流题祭

暴力建图,不需要任何技巧,从源点向每个单位连(人数)单位的流量,从每个单位向每张桌子连\(1\)单位的流量,再从每张桌子向汇点连(人数)单位的流量。如果(最大流=所有单位总人数),则有解。

太暴力了以至于根本不需要过多思考

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[510],cnt,cur[510],dep[510],S,T,sum,res;
struct node{
	int to,next,val;
}edge[400100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
int main(){
	scanf("%d%d",&m,&n),memset(head,-1,sizeof(head)),S=n+m+1,T=n+m+2;
	for(int i=1,x;i<=m;i++){
		scanf("%d",&x),ae(S,i,x),sum+=x;
		for(int j=1;j<=n;j++)ae(i,m+j,1);
	}
	for(int i=1,x;i<=n;i++)scanf("%d",&x),ae(m+i,T,x);
	Dinic();
	if(res!=sum){puts("0");return 0;}
	puts("1");
	for(int i=1;i<=m;i++){for(int j=head[i];j!=-1;j=edge[j].next)if(!edge[j].val&&edge[j].to>m&&edge[j].to<=n+m)printf("%d ",edge[j].to-m);puts("");}
	return 0;
}

XI.骑士共存问题

第四道自己AC的网络流题祭

本题还是奇偶建图法。

观察到任意一对可以互相攻击的骑士对,它们的横纵坐标和肯定是一奇一偶。

因此我们可以仿效方格取数问题,还是暴力建图,将所有的奇点连到\(T\),将\(S\)连上所有的偶点,这两个都是边权为\(1\)。然后对于所有的骑士对,从偶点向奇点连一条边权为\(INF\)的边(防止割断)。之后跑最小割,然后答案即为\(n^2-m-cut\)(割掉了\(cut\)个点不选,还有\(m\)个不能选的格子)。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[40100],cnt,S,T,cur[40100],dep[40100],dx[8]={-1,1,2,2,1,-1,-2,-2},dy[8]={2,2,1,-1,-2,-2,-1,1},sum,res;
bool ok[210][210];
struct node{
	int to,next,val;
}edge[400100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
bool chk(int x,int y){
	return x<n&&x>=0&&y<n&&y>=0&&!ok[x][y];
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*n,T=n*n+1;
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),ok[x-1][y-1]=true;
	for(int i=0;i<n;i++)for(int j=0;j<n;j++){
		if(!chk(i,j))continue;
		sum++;
		if((i+j)&1){ae(i*n+j,T,1);continue;}
		ae(S,i*n+j,1);
		for(int k=0;k<8;k++)if(chk(i+dx[k],j+dy[k]))ae(i*n+j,(i+dx[k])*n+(j+dy[k]),0x3f3f3f3f);
	}
	Dinic();
	printf("%d\n",sum-res);
	return 0;
} 

XIII.[CTSC1999]家园

出题人用脚造数据,假算法都能拿90分

一看就看出浓浓的网络流气息。

对于每一时刻,我们都建立\(n\)个节点,表示所有的太空站。

之后对于每一时刻,如果此时有一艘太空船正从\(x\)\(y\)去,那么,我们从上一时刻的\(x\)到这一时刻的\(y\)连一条流量为该太空船的容量的边(地球为\(S\),月球为\(T\))。

当然,还有一些注意事项,例如:

1.某太空船前一时刻与这一时刻的星球如果相同的话,这条边不能连。

2.某太空船前一时刻在\(-1\)的话,这条边不能连。

3.某太空船这一时刻在\(1\)的话,这条边不能连。

以及

\(\color{red}\colorbox{white}{4.对于每一时刻,都要从前一时刻的每个星球向这一时刻的每个星球连一条边权为无穷的边}\)

(没写这个还拿了90分QaQ)

代码:

#include<bits/stdc++.h>
using namespace std;
#define LAS (i-1)*n+v[j][(i-1)%cycle[j]]
#define NOW i*n+v[j][i%cycle[j]]
int n,m,k,S,T,head[15100],cur[15100],dep[15100],cnt,sz[30],cycle[30],res;
vector<int>v[30];
struct node{
	int to,next,val;
}edge[400100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
int main(){
	scanf("%d%d%d",&n,&m,&k),memset(head,-1,sizeof(head)),S=15001,T=15002;
	for(int i=0;i<m;i++){
		scanf("%d%d",&sz[i],&cycle[i]);
		for(int j=0,x;j<cycle[i];j++)scanf("%d",&x),v[i].push_back(x);
	}
	for(int i=1;i<=(n+2)*m*k;i++){
		for(int j=1;j<=n;j++)ae((i-1)*n+j,i*n+j,0x3f3f3f3f);
		for(int j=0;j<m;j++){
			if(v[j][(i-1)%cycle[j]]==v[j][i%cycle[j]])continue;
			if(v[j][i%cycle[j]]==-1){
				if(v[j][(i-1)%cycle[j]]==0)ae(S,T,sz[j]);
				else ae(LAS,T,sz[j]);
			}
			else if(v[j][(i-1)%cycle[j]]!=-1&&v[j][i%cycle[j]]!=0){
				if(v[j][(i-1)%cycle[j]]==0)ae(S,NOW,sz[j]);
				else ae(LAS,NOW,sz[j]);
			}
		}
		Dinic();
		if(res>=k){printf("%d\n",i);return 0;}
	}
	puts("0");
	return 0;
}

XIV.传纸条

为什么一道绿题会用到网络流呢?它不是一道暴力DP吗?

这里我们介绍一种拆点的做法。

把每个点拆成两个点:入点\(in\)和出点\(out\)

首先,连两条边\((S,out_{1,1},2,0)\)\((in_{n,m},T,2,0)\)表示要求两条路径自\((1,1)\)开始,到\((n,m)\)结束。

然后,对于每个点\((i,j)\),连一条边\((in_{i,j},out_{i,j},1,num_{i,j})\),表示只能有一条路径经过这个节点,并且经过这个节点的费用是\(num_{i,j}\)

同时,连两条边\((out_{i,j},in_{i+1,j},1,0)\)\((out_{i,j},in_{i,j+1},1,0)\),是从\((i,j)\)的两个转移目标。

答案即为最大费用最大流

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

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,num[110][110],head[21000],dis[21000],fr[21000],id[21000],cn,S,T,cnt,cost;
struct node{
	int to,next,val,cost;
}edge[401000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[21000];
bool SPFA(){
	memset(dis,-1,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==-1)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=2*n*m+1,T=2*n*m+2,ae(S,n*m,2,0),ae(n*m-1,T,2,0);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)scanf("%d",&num[i][j]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		ae(i*m+j,i*m+j+n*m,1,num[i][j]);
		if(i+1<n)ae(i*m+j+n*m,(i+1)*m+j,1,0);
		if(j+1<m)ae(i*m+j+n*m,i*m+j+1,1,0);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

XV.数字梯形问题

之前讲那道绿题就是为了这道题做铺垫的。

很显然,这道题就DP不了了吧~

这时候,我们就可以仿照上一题建图了。

第一问是一样的套路,一样的过程。

第二问只需要把连接每个点内部的边\((in_x,out_x)\)的边权赋为\(INF\)即可。

第三问更暴力,除了进入第一行每个点的边的边权仍为\(1\)以外,其他所有边的边权都要赋成\(INF\)

但是,这题有两个坑点QaQ:

1.矩阵中可能有负数(但题面中并未给出),因此跑最小费用最大流时初始值不能赋成\(-1\),而必须赋成\(-INF\)

2.矩阵记得开成\(20\times 40\)的,因为第一行有\(20\)个数,最后一行就有\(39\)个数。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,ord[500][500],num[500][500],head[210000],dis[210000],fr[210000],id[210000],S,T,cnt,cost,lim;
struct node{
	int to,next,val,cost;
}edge[401000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[210000];
bool SPFA(){
	memset(dis,0xf0,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%lld\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0xf0f0f0f0f0f0f0f0)return false;
	int x=T,mn=0x3f3f3f3f3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
signed main(){
	scanf("%lld%lld",&m,&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&num[i][1]),ord[i][1]=ord[i-1][m+i-2]+1;
		for(int j=2;j<=m+i-1;j++)scanf("%lld",&num[i][j]),ord[i][j]=ord[i][j-1]+1;
	}
	if(n==1){
		for(int i=1;i<=m;i++)cost+=num[1][i];
		printf("%lld\n%lld\n%lld\n",cost,cost,cost);
		return 0;
	}
//	for(int i=1;i<=n;i++){for(int j=1;j<=m+i-1;j++)printf("%d ",ord[i][j]);puts("");}
	lim=ord[n][m+n-1];
	S=2*lim+1,T=S+1;
	memset(head,-1,sizeof(head)),cost=cnt=0;
	for(int i=1;i<=m;i++)ae(S,ord[1][i]+lim,1,num[1][i]);
	for(int i=1;i<=m+n-1;i++)ae(ord[n][i],T,1,num[n][i]);
	for(int i=1;i<n;i++)for(int j=1;j<=m+i-1;j++)ae(ord[i][j],ord[i][j]+lim,1,num[i][j]),ae(ord[i][j]+lim,ord[i+1][j],1,0),ae(ord[i][j]+lim,ord[i+1][j+1],1,0);
	while(SPFA());
	printf("%lld\n",cost);
	memset(head,-1,sizeof(head)),cost=cnt=0;
	for(int i=1;i<=m;i++)ae(S,ord[1][i]+lim,1,num[1][i]);
	for(int i=1;i<=m+n-1;i++)ae(ord[n][i],T,0x3f3f3f3f,num[n][i]);
	for(int i=1;i<n;i++)for(int j=1;j<=m+i-1;j++)ae(ord[i][j],ord[i][j]+lim,0x3f3f3f3f,num[i][j]),ae(ord[i][j]+lim,ord[i+1][j],1,0),ae(ord[i][j]+lim,ord[i+1][j+1],1,0);
	while(SPFA());
	printf("%lld\n",cost);
	memset(head,-1,sizeof(head)),cost=cnt=0;
	for(int i=1;i<=m;i++)ae(S,ord[1][i]+lim,1,num[1][i]);
	for(int i=1;i<=m+n-1;i++)ae(ord[n][i],T,0x3f3f3f3f,num[n][i]);
	for(int i=1;i<n;i++)for(int j=1;j<=m+i-1;j++)ae(ord[i][j],ord[i][j]+lim,0x3f3f3f3f,num[i][j]),ae(ord[i][j]+lim,ord[i+1][j],0x3f3f3f3f,0),ae(ord[i][j]+lim,ord[i+1][j+1],0x3f3f3f3f,0);
	while(SPFA());
	printf("%lld\n",cost);
	return 0;
}

XVI.[USACO5.4]周游加拿大Canada Tour

本题提供两种解法。

法一:DP

#include<bits/stdc++.h>
using namespace std;
int n,m,head[101],cnt,f[101][101],mx,t1,t2,g[101][101];
map<string,int>mp;
string s1,s2;
int main(){
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)cin>>s1,mp[s1]=i;
	for(int i=1;i<=m;i++)cin>>s1>>s2,t1=mp[s1],t2=mp[s2],g[t1][t2]=g[t2][t1]=true;
	f[1][1]=1;
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)for(int k=1;k<j;k++)if(g[j][k]&&f[i][k])f[i][j]=f[j][i]=max(f[i][k]+1,f[i][j]);
	for(int i=1;i<=n;i++)if(g[i][n])mx=max(mx,f[i][n]);
	printf("%d\n",!mx?1:mx);
	return 0;
}

不要问我怎么DP的,一年前写的代码都忘光了QaQ

法二:最大费用最大流

思想是可以借鉴的,就是把一条从\(1\)号城市到\(n\)号城市再返回\(1\)号城市的路径拆成两条从\(1\)号城市到\(n\)号城市的路径。

因为这两条路径不能相交,所以就可以直接借鉴传纸条了。

注意最终最大费用是要减去\(2\)再输出的,因为\(1\)号节点出现两次,\(n\)号节点出现两次。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dis[20100],id[20100],fr[20100],head[20100],cnt,S,T,flow,cost;
map<string,int>mp;
struct node{
	int to,next,val,cost;
}edge[401000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[21000];
bool SPFA(){
	memset(dis,-1,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==-1)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T,flow+=mn;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	cin>>n>>m,memset(head,-1,sizeof(head)),S=n*2+1,T=n*2+2,ae(S,n+1,2,1),ae(n,T,2,1);
	for(int i=1;i<=n;i++){
		string s;
		cin>>s;
		mp[s]=i;
		ae(i,i+n,1,1);
	}
	for(int i=1,x,y;i<=m;i++){
		string s1,s2;
		cin>>s1>>s2;
		x=mp[s1],y=mp[s2];
		if(x>y)swap(x,y);
		ae(x+n,y,1,0);
	}
	while(SPFA());
	if(flow!=2){puts("1");return 0;}
	printf("%d\n",cost-2);
	return 0;
}

XVII.航空路线问题

题意与上一题完全一致连样例都一模一样

唯一的不同是,这道题要求输出方案。

于是,我便用了一种超级暴力的方式输出答案:

for(int i=head[n+1];i!=-1;i=edge[i].next)if(edge[i].to>=2&&edge[i].to<=n&&!edge[i].val)v.push_back(edge[i].to);
for(int i=0;i<v.size();i++){
	int x=v[i];
	while(x!=n){
		vv[i].push_back(x);
		for(int j=head[x+n];j!=-1;j=edge[j].next)if(edge[i].to>=x+1&&edge[j].to<=n&&!edge[j].val){x=edge[j].to;break;}
	}
}
cout<<s[1]<<endl;
for(int i=0;i<vv[0].size();i++)cout<<s[vv[0][i]]<<endl;
cout<<s[n]<<endl;
reverse(vv[1].begin(),vv[1].end());
for(int i=0;i<vv[1].size();i++)cout<<s[vv[1][i]]<<endl;
cout<<s[1]<<endl;

可以看到,这就是暴力找出两条转移路径,让后输出。

但是,这会在某种情况下出锅:

2 1
AAA
BBB
AAA BBB

假如你的程序跑出来此组数据无解,恭喜你,中招了。

这组数据如果跑的话,只能找出一条路径。

但是,仍然可以找出一条符合要求的路径,就是\(AAA \rightarrow BBB \rightarrow AAA\)

这是因为两条路径重合了。

因此我们要特判一下:

if(flow!=2){
	if(flow==1&&cost==2){
		puts("2");
		cout<<s[1]<<endl<<s[n]<<endl<<s[1]<<endl;
	}
	else puts("No Solution!");
	return 0;
}

然后就过了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dis[20100],id[20100],fr[20100],head[20100],cnt,S,T,flow,cost;
map<string,int>mp;
struct node{
	int to,next,val,cost;
}edge[401000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[21000];
bool SPFA(){
	memset(dis,-1,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==-1)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T,flow+=mn;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
vector<int>v,vv[2];
string s[110];
int main(){
	cin>>n>>m,memset(head,-1,sizeof(head)),S=n*2+1,T=n*2+2,ae(S,n+1,2,1),ae(n,T,2,1);
	for(int i=1;i<=n;i++)cin>>s[i],mp[s[i]]=i,ae(i,i+n,1,1);
	for(int i=1,x,y;i<=m;i++){
		string s1,s2;
		cin>>s1>>s2;
		x=mp[s1],y=mp[s2];
		if(x>y)swap(x,y);
		ae(x+n,y,1,0);
	}
	while(SPFA());
	if(flow!=2){
		if(flow==1&&cost==2){
			puts("2");
			cout<<s[1]<<endl<<s[n]<<endl<<s[1]<<endl;
		}
		else puts("No Solution!");
		return 0;
	}
	cout<<cost-2<<endl;
	for(int i=head[n+1];i!=-1;i=edge[i].next)if(edge[i].to>=2&&edge[i].to<=n&&!edge[i].val)v.push_back(edge[i].to);
	for(int i=0;i<v.size();i++){
		int x=v[i];
		while(x!=n){
			vv[i].push_back(x);
			for(int j=head[x+n];j!=-1;j=edge[j].next)if(edge[i].to>=x+1&&edge[j].to<=n&&!edge[j].val){x=edge[j].to;break;}
		}
	}
//	for(int i=0;i<v.size();i++)for(int j=0;j<vv[i].size();j++)printf("%d ",vv[i][j]);puts("");
	cout<<s[1]<<endl;
	for(int i=0;i<vv[0].size();i++)cout<<s[vv[0][i]]<<endl;
	cout<<s[n]<<endl;
	reverse(vv[1].begin(),vv[1].end());
	for(int i=0;i<vv[1].size();i++)cout<<s[vv[1][i]]<<endl;
	cout<<s[1]<<endl;
	return 0;
}

XVIII.深海机器人问题

调这道题心态都要炸了……莫名其妙WA#7,8,9,最后发现可能是生物标本价值中有负数,将最大费用最大流的初始值从\(-1\)赋成\(-INF\)就过了……

费用流的气息很明显。建出图来,从源点连向每一个起点,再从每一个终点连向汇点。对于每一条网格图中的道路\((x,y,z)\),连两条边\((x,y,1,z)\)\((x,y,INF,0)\),因为道路可以通过多次,但标本只能收集一次。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,S,T,a,b,head[5010],fr[5010],cnt,id[5010],dis[5010],cost;
struct node{
	int to,next,val,cost;
}edge[101000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[5010];
bool SPFA(){
	memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]>dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x3f3f3f3f3f3f3f3f)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
signed main(){
	scanf("%lld%lld",&a,&b);
	scanf("%lld%lld",&n,&m),S=5000,T=5001,memset(head,-1,sizeof(head)),n++,m++;
	for(int i=0;i<n;i++)for(int j=0,x;j+1<m;j++)scanf("%lld",&x),ae(i*m+j,i*m+(j+1),1,-x),ae(i*m+j,i*m+(j+1),0x3f3f3f3f,0);
	for(int j=0,x;j<m;j++)for(int i=0;i+1<n;i++)scanf("%lld",&x),ae(i*m+j,(i+1)*m+j,1,-x),ae(i*m+j,(i+1)*m+j,0x3f3f3f3f,0);
	for(int i=1,x,y,z;i<=a;i++)scanf("%lld%lld%lld",&z,&x,&y),ae(S,x*m+y,z,0);
	for(int i=1,x,y,z;i<=b;i++)scanf("%lld%lld%lld",&z,&x,&y),ae(x*m+y,T,z,0);
	while(SPFA());
	printf("%lld\n",-cost);
	return 0;
} 

XIX.运输问题

你永远也不知道为什么运输货物的费用会是负的233

简直模板一般,暴力建图,暴力连边,网络流做多了自然会了

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,S,T,head[510],cnt,id[510],fr[510],dis[510],cost,IN[510],OUT[510],g[510][510];
struct node{
	int to,next,val,cost;
}edge[101000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[5010];
bool SPFA1(){
	memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]>dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x3f3f3f3f)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
bool SPFA2(){
	memset(dis,0x80,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x80808080)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,T=n+m+2;
	memset(head,-1,sizeof(head)),cnt=0;
	for(int i=1;i<=n;i++)scanf("%d",&IN[i]),ae(S,i,IN[i],0);
	for(int i=1;i<=m;i++)scanf("%d",&OUT[i]),ae(i+n,T,OUT[i],0);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&g[i][j]),ae(i,j+n,0x3f3f3f3f,g[i][j]);
	cost=0;while(SPFA1());printf("%d\n",cost);
	memset(head,-1,sizeof(head)),cnt=0;
	for(int i=1;i<=n;i++)ae(S,i,IN[i],0);
	for(int i=1;i<=m;i++)ae(i+n,T,OUT[i],0);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)ae(i,j+n,0x3f3f3f3f,g[i][j]);
	cost=0;while(SPFA2());printf("%d\n",cost);
	return 0;
}

XX.分配问题

大水题,一眼AC类型。

如果您这都不能一眼AC,那说明您太巨了,从头学起吧QaQ

代码:

#include<bits/stdc++.h>
using namespace std;
int n,g[110][110],S,T,head[210],dis[210],id[210],fr[210],cost,cnt;
struct node{
	int to,next,val,cost;
}edge[101000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[5010];
bool SPFA1(){
	memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]>dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x3f3f3f3f)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
bool SPFA2(){
	memset(dis,0x80,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x80808080)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	scanf("%d",&n),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&g[i][j]);
	memset(head,-1,sizeof(head)),cnt=cost=0;
	for(int i=1;i<=n;i++)ae(S,i,1,0);
	for(int i=1;i<=n;i++)ae(i+n,T,1,0);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)ae(i,j+n,1,g[i][j]);
	while(SPFA1());printf("%d\n",cost);
	memset(head,-1,sizeof(head)),cnt=cost=0;
	for(int i=1;i<=n;i++)ae(S,i,1,0);
	for(int i=1;i<=n;i++)ae(i+n,T,1,0);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)ae(i,j+n,1,g[i][j]);
	while(SPFA2());printf("%d\n",cost);
	return 0;
}

XXI.火星探险问题

建图十分简单,关键是输出方案比较恶心。

首先,我们可以bfs一下,求出所有能到的方格。之后建图时,就只考虑被bfs到的格子。

然后,就开始建图。拆点,然后老套路,在拆出的两个点之间连一条边权为\(INF\),费用为\(0\)的边。如果这个点是一块石头,再连一条边权为\(1\),费用为\(1\)的边。然后跑最大费用最大流。

然后需要输出方案。枚举每一个点,查看它入点和出点间边的剩余流量。则这个点被访问了(总流量-剩余流量)次。

然后,从起点开始dfs,dfs\(n\)次,每次找出一条访问次数都为正的路径,然后把路径上所有点的访问次数减去\(1\)

方案代码:

void dfs(int x,int y,int ord){
	occ[x][y]--;
	if(x==n-1&&y==m-1)return;
	if(occ[x+1][y]){printf("%d 0\n",ord),dfs(x+1,y,ord);return;}
	if(occ[x][y+1]){printf("%d 1\n",ord),dfs(x,y+1,ord);return;}
}





for(int i=0;i<n;i++)for(int j=0;j<m;j++)for(int l=head[i*m+j];l!=-1;l=edge[l].next){
	if(edge[l].to!=(i*m+j+n*m))continue;
	if(edge[l].cost==0)occ[i][j]+=0x3f3f3f3f-edge[l].val;
	if(edge[l].cost==1)occ[i][j]+=1-edge[l].val;
	}
occ[0][0]=occ[n-1][m-1]=k;
for(int i=1;i<=k;i++)dfs(0,0,i);

总代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,k,g[110][110],head[11000],cnt,fr[11000],id[11000],dis[11000],S,T,occ[110][110];
bool vis[110][110];
bool bfs(){
	queue<pair<int,int> >q;
	q.push(make_pair(0,0));
	while(!q.empty()){
		pair<int,int>x=q.front();q.pop();
		vis[x.first][x.second]=true;
		if(x.first+1<n&&g[x.first+1][x.second]!=1)q.push(make_pair(x.first+1,x.second));
		if(x.second+1<m&&g[x.first][x.second+1]!=1)q.push(make_pair(x.first,x.second+1));
	}
	return vis[n-1][m-1];
}
struct node{
	int to,next,val,cost;
}edge[101000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[11000];
bool SPFA(){
	memset(dis,-1,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==-1)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
void dfs(int x,int y,int ord){
	occ[x][y]--;
	if(x==n-1&&y==m-1)return;
	if(occ[x+1][y]){printf("%d 0\n",ord),dfs(x+1,y,ord);return;}
	if(occ[x][y+1]){printf("%d 1\n",ord),dfs(x,y+1,ord);return;}
}
int main(){
	scanf("%d%d%d",&k,&m,&n),memset(head,-1,sizeof(head)),S=2*n*m+1,T=2*n*m+2,ae(S,n*m,k,0),ae(n*m-1,T,k,0);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)scanf("%d",&g[i][j]);
	if(!bfs())return 0;
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		if(!vis[i][j])continue;
		ae(i*m+j,i*m+j+n*m,0x3f3f3f3f,0);
		if(g[i][j]==2)ae(i*m+j,i*m+j+n*m,1,1);
		if(i+1<n&&vis[i+1][j])ae(i*m+j+n*m,(i+1)*m+j,0x3f3f3f3f,0);
		if(j+1<m&&vis[i][j+1])ae(i*m+j+n*m,i*m+(j+1),0x3f3f3f3f,0);
	}
	while(SPFA());
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)for(int l=head[i*m+j];l!=-1;l=edge[l].next){
		if(edge[l].to!=(i*m+j+n*m))continue;
		if(edge[l].cost==0)occ[i][j]+=0x3f3f3f3f-edge[l].val;
		if(edge[l].cost==1)occ[i][j]+=1-edge[l].val;
	}
	occ[0][0]=occ[n-1][m-1]=k;
//	for(int i=0;i<n;i++){for(int j=0;j<m;j++)printf("%d ",occ[i][j]);puts("");}
	for(int i=1;i<=k;i++)dfs(0,0,i);
	return 0;
}

XXVI.[SDOI2015]星际战争

省选题正式开始~~~

关于这道题,我们可以二分最终时间。虽然精度要求较低可以采取暴力\(\times 1000\)的做法,但是我还是采取了实数域二分的做法。

当我们二分出一个时间后,一台发射器在规定时间内所能输出的攻击也就确定了。这个时候,我们只需要从源点向每台发射器连(攻击)单位的流量,再从发射器向所有它能攻击到的士兵连(攻击)单位的流量,最后从每个士兵向汇点连(血量)单位的流量。如果(最大流=血量之和),那么这个时间合法。

注意网络流中所有流量都是\(double\)类型!

代码:

#include<bits/stdc++.h>
using namespace std;
const double EPS=1e-6;
int n,m,amour[110],dam[110],head[110],cur[110],dep[110],cnt,S,T,sum;
double res;
struct node{
	int to,next;
	double val;
}edge[400100];
void ae(int u,int v,double w){
//	printf("%d %d %lf\n",u,v,w);
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val>EPS&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline double dfs(int x,double flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	double used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(edge[i].val<EPS||dep[edge[i].to]!=dep[x]+1)continue;
		register double ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	res=0.0;
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
bool ok[100][100];
double l=0,r=5e6;
bool che(double ip){
	memset(head,-1,sizeof(head)),cnt=0;
	for(int i=1;i<=m;i++)ae(S,i,ip*dam[i]);
	for(int i=1;i<=n;i++)ae(i+m,T,amour[i]);
	for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)if(ok[i][j])ae(i,j+m,ip*dam[i]);
	Dinic();
//	printf("%lf\n",res);
	return abs(sum-res)<EPS;
}
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,T=n+m+2;
	for(int i=1;i<=n;i++)scanf("%d",&amour[i]),sum+=amour[i];
	for(int i=1;i<=m;i++)scanf("%d",&dam[i]);
	for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)scanf("%d",&ok[i][j]);
	while(r-l>EPS){
		double mid=(l+r)/2;
//		printf("%lf %lf:\n",l,r);
		if(che(mid))r=mid;
		else l=mid;
	}
	printf("%lf\n",l);
	return 0;
}

XXVII.[SDOI2009]晨跑

大水题,随便建建就出来了。只需要拆点就能满足“每个十字路口经过一次”的限制。然后跑最小费用最大流。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[410],cnt,id[410],fr[410],dis[410],cost,flow,S,T;
struct node{
	int to,next,val,cost;
}edge[5010000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[410];
bool SPFA(){
	memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]>dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==0x3f3f3f3f)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,flow+=mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*2+1,T=n*2+2;
	for(int i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		if(x==1)x=S;
		else if(x==n)x=T;
		else x=2*x;
		if(y==1)y=S;
		else if(y==n)y=T;
		else y=2*y-1;
		ae(x,y,1,z);
	}
	for(int i=2;i<n;i++)ae(2*i-1,2*i,1,0);
	while(SPFA());
	printf("%d %d\n",flow,cost);
	return 0;
}

XXIX.[SDOI2017]新生舞会

\(0/1\)分数规划强行套到网络流里?orzorz。

一看那个鬼畜般的\(C\)的式子,立马就应该条件反射\(0/1\)分数规划。对于二分出来的值,我们判断它的最大费用最大流是否为正。

代码:

#include<bits/stdc++.h>
using namespace std;
const double EPS=1e-8;
int n,a[110][110],b[110][110],head[210],cnt,S,T,fr[210],id[210];
double cost,dis[210];
struct node{
	int to,next,val;
	double cost;
}edge[501000];
void ae(int u,int v,int w,double c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[210];
bool SPFA(){
	for(int i=1;i<=T;i++)dis[i]=-1e9;
	dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(edge[i].val<EPS)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(abs(dis[T]+1e9)<EPS)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}
bool che(double ip){
	memset(head,-1,sizeof(head)),cnt=0,cost=0;
	for(int i=1;i<=n;i++)ae(S,i,1,0);
	for(int i=1;i<=n;i++)ae(i+n,T,1,0);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)ae(i,j+n,1,(double)a[i][j]-ip*b[i][j]);
	while(SPFA());
//	printf("%lf\n",cost);
	return cost>0;
}
double l,r;
int main(){
	scanf("%d",&n),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]),r+=a[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&b[i][j]);
	while(r-l>EPS){
		double mid=(l+r)/2;
//		printf("%lf,%lf:%lf\n",l,r,mid);
		if(che(mid))l=mid;
		else r=mid;
	}
	printf("%lf\n",l);
	return 0;
}

XXXIV.[ZJOI2010]网络扩容

这题思想不难,但是因为同时要跑一遍最大流和费用流,所以不得不第一次用了\(namespace\)……

首先第一问就是最大流模板……

第二问就是对于原图中的每一条边\((u,v,W,C)\),连边\((u,v,W,0)\)\((u,v,INF,C)\)。同时,建立一个伪汇点,令伪汇点为\(t\),然后连边\((t,T,flow+k,0)\),这样就限制住了流量。

一开始想的是玄学二分QaQ……

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
namespace MaxFlow{
	int head[1010],cur[1010],dep[1010],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[40100];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}	
	}	
}
namespace MCMF{
	int head[1010],cnt,dis[1010],fr[1010],id[1010],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[40100];
	void ae(int u,int v,int w,int c){
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[1010];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[0])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}	
}
int main(){
	scanf("%d%d%d",&n,&m,&k),memset(MaxFlow::head,-1,sizeof(MaxFlow::head)),memset(MCMF::head,-1,sizeof(MCMF::head));
	MaxFlow::S=MCMF::S=1,MaxFlow::T=n,MCMF::T=n+1;
	for(int i=1,x,y,z,w;i<=m;i++){
		scanf("%d%d%d%d",&x,&y,&z,&w);
		MaxFlow::ae(x,y,z);
		MCMF::ae(x,y,0x3f3f3f3f,w);
		MCMF::ae(x,y,z,0);
	}
	MaxFlow::Dinic();
	printf("%d ",MaxFlow::res);
	MCMF::ae(n,n+1,MaxFlow::res+k,0);
	while(MCMF::SPFA());
	printf("%d\n",MCMF::cost);
	return 0;
} 

XXXV.[JSOI2016]飞机调度

我为了这题专门写了篇题解,这里打个广告,就不再赘述了。

XXXVII.[CQOI2015]网络吞吐量

我要吐槽……渣题面根本没有可读性QaQ……

翻译成人话:给你一张无向图,求共可以找出多少条从\(1\)\(n\)的最短路(可以相同),使得没有一个点\(i\)被访问了超过\(a_i\)次(点\(1\)和点\(n\)除外)。

我就是因为没看懂题面一直不会做

首先我们可以随便跑个最短路出来。然后,如果对于一条边\((u,v,w)\),有\((dis_v=dis_u+w)\),则这条边是可选的。

因为这是对点的限制而非对边的限制,我们立马就能想到拆点。对于一条合法边\((u,v)\),我们连边\((out_u,in_v,INF)\);同时,对于所有的\(x\),连边\((in_x,out_x,a_x)\)

则显然,答案即为最大流。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
struct edge{
	int u,v,w;
}e[100100];
namespace MaxFlow{
	int head[1010],cur[1010],dep[1010],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[1001000];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f3f3f3f3f);
		}	
	}	
}
namespace ShortestPath{
	int head[510],cnt,dis[510];
	bool vis[510];
	struct node{
		int to,next,val;
	}edge[201000];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	priority_queue<pair<int,int> >q;
	void Dijkstra(){
		memset(dis,0x3f,sizeof(dis)),memset(vis,false,sizeof(vis)),dis[1]=0,q.push(make_pair(0,1));
		while(!q.empty()){
			int x=q.top().second;q.pop();
			if(vis[x])continue;vis[x]=true;
			for(int i=head[x];i!=-1;i=edge[i].next)if(dis[edge[i].to]>dis[x]+edge[i].val)dis[edge[i].to]=dis[x]+edge[i].val,q.push(make_pair(-dis[edge[i].to],edge[i].to));
		}
	}
}
signed main(){
	scanf("%lld%lld",&n,&m),memset(ShortestPath::head,-1,sizeof(ShortestPath::head)),memset(MaxFlow::head,-1,sizeof(MaxFlow::head)),MaxFlow::S=n+1,MaxFlow::T=n;
	for(int i=1;i<=m;i++)scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w),ShortestPath::ae(e[i].u,e[i].v,e[i].w);
	ShortestPath::Dijkstra();
	for(int i=1;i<=m;i++){
		if(ShortestPath::dis[e[i].v]==ShortestPath::dis[e[i].u]+e[i].w)MaxFlow::ae(e[i].u+n,e[i].v,0x3f3f3f3f3f3f3f3f);
		if(ShortestPath::dis[e[i].u]==ShortestPath::dis[e[i].v]+e[i].w)MaxFlow::ae(e[i].v+n,e[i].u,0x3f3f3f3f3f3f3f3f);
	}
	for(int i=1,x;i<=n;i++)scanf("%lld",&x),MaxFlow::ae(i,i+n,x);
	MaxFlow::Dinic();
	printf("%lld\n",MaxFlow::res);
	return 0;
}

XXXVIII.方格取数加强版

一般的题目。也是拆点,在入点和出点间连两条边,一条流量为\(1\),费用为\(A_{i,j}\);一条流量为\(INF\),费用为\(0\)。答案即为最大费用最大流。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,head[10100],cnt,dis[10100],fr[10100],id[10100],S,T,cost;
struct node{
	int to,next,val,cost;
}edge[401000];
void ae(int u,int v,int w,int c){
	edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
bool in[10100];
bool SPFA(){
	memset(dis,-1,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
	while(!q.empty()){
		int x=q.front();q.pop(),in[x]=false;
//		printf("%d\n",x);
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(!edge[i].val)continue;
			if(dis[edge[i].to]<dis[x]+edge[i].cost){
				dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
				if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
			}
		}
	}
	if(dis[T]==-1)return false;
	int x=T,mn=0x3f3f3f3f;
	while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
	cost+=dis[T]*mn,x=T;
	while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
	return true;
}	
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=0,T=2*n*n-1;
	for(int i=0;i<n;i++)for(int j=0,x;j<n;j++){
		scanf("%d",&x),ae(i*n+j,i*n+j+n*n,1,x),ae(i*n+j,i*n+j+n*n,m-1,0);
		if(i+1<n)ae(i*n+j+n*n,(i+1)*n+j,m,0);
		if(j+1<n)ae(i*n+j+n*n,i*n+(j+1),m,0);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

XL.[NOI2006]最大获利

这是我做的最迷惑的题……

首先一眼看出这题简直和太空飞行计划问题一模一样。但是,看到这\(n\leq 5000,m\leq 50000\),我还真不敢贸然直接用网络流。

想了会链式建图没想出来,然后直接敲了个暴力网络流。方法同XII.太空飞行计划问题完全一致。

然后就A了?????

QaQ?

网络流的复杂度真的玄学。

并且题解都是这个算法。

代码(\(namespace\)真有意思):

#include<bits/stdc++.h>
using namespace std;
int n,m,sum;
namespace MaxFlow{
	const int N=60000,M=400000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,T=n+m+2,memset(head,-1,sizeof(head));
	for(int i=1,x;i<=n;i++)scanf("%d",&x),ae(S,i,x);
	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),ae(x,i+n,0x3f3f3f3f),ae(y,i+n,0x3f3f3f3f),ae(i+n,T,z),sum+=z;
	Dinic();
	printf("%d\n",sum-res);
	return 0;
} 

XLII.[ICPC-Beijing 2006]狼抓兔子

众所周知,\(n^2\)可以过百万……

这题一眼就能看出来是最小割,但是这\(10^6\)个点让人有些发慌呀QaQ……

但是除了最小割我也没有其它好办法,看了题解,发现真是暴力最小割……

我实在不能理解,为什么\(n^2m\)的网络流能够跑过\(10^6\)……

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
namespace MaxFlow{
	const int N=1000100,M=6000100;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=0,T=n*m-1;
	for(int i=0;i<n;i++)for(int j=0,x;j<m-1;j++)scanf("%d",&x),ae(i*m+j,i*m+(j+1),x);
	for(int i=0;i<n-1;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),ae(i*m+j,(i+1)*m+j,x);
	for(int i=0;i<n-1;i++)for(int j=0,x;j<m-1;j++)scanf("%d",&x),ae(i*m+j,(i+1)*m+(j+1),x);
	Dinic();
	printf("%d\n",res);
	return 0;
} 

XLVII.[SCOI2007]蜥蜴

嗯,首先要说明一下,就是题面中的这个“平面距离”指的是欧氏距离,不是曼哈顿或切比雪夫。

这题做法比较显然,还是拆点以限制通过次数,所有有蜥蜴的点从源点连来一点流量,所有离边界距离不超过\(D\)的点向汇点连去\(INF\)点流量,之后对于所有的格子,入出点之间连(高度)的流量。对于所有互相能达到的点对,入点和出点之间连边。答案即为(蜥蜴数-最大流)。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,d,ans;
char mp[30][30],liz[30][30];
namespace MaxFlow{
	const int N=10000,M=200000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d%d",&n,&m,&d),memset(head,-1,sizeof(head)),S=n*m*2,T=n*m*2+1;
	for(int i=0;i<n;i++)scanf("%s",mp[i]);
	for(int i=0;i<n;i++)scanf("%s",liz[i]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		ae(i*m+j,i*m+j+n*m,mp[i][j]-'0');
		if(i+1<=d||n-i<=d||j+1<=d||m-j<=d)ae(i*m+j+n*m,T,0x3f3f3f3f);
		if(liz[i][j]=='L')ae(S,i*m+j,1),ans++;
		for(int k=0;k<n;k++)for(int l=0;l<m;l++)if((k-i)*(k-i)+(l-j)*(l-j)<=d*d)ae(i*m+j+n*m,k*m+l,0x3f3f3f3f);
	}
	Dinic();
	printf("%d\n",ans-res);
	return 0;
} 

IL.小M的作物

同样,这也是一道对偶建图的题,与上一题基本相同(那为什么上一题紫这题蓝)。

只要你上一道题会了这道题就没问题。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[1010],b[1010],ans;
namespace MaxFlow{
	const int N=5000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),ans+=a[i];
	for(int i=1;i<=n;i++)scanf("%d",&b[i]),ans+=b[i];
	scanf("%d",&m),S=n+m*2+1,T=n+m*2+2;
	for(int i=1;i<=n;i++)ae(S,i,a[i]),ae(i,T,b[i]);
	for(int i=1,t1,t2,t3,t4;i<=m;i++){
		scanf("%d%d%d",&t1,&t2,&t3),ae(S,n+i,t2),ae(n+m+i,T,t3),ans+=t2+t3;
		while(t1--)scanf("%d",&t4),ae(n+i,t4,0x3f3f3f3f),ae(t4,n+m+i,0x3f3f3f3f);
	}
	Dinic();
	printf("%d\n",ans-res);
	return 0;
}

L.[SHOI2007]善意的投票

网络流第五十题祭~~~~

这题也是对偶建图题。

之前的几道题都有添加新点,结果我就被带偏了,一直想着加新点,忘记了最原始的不加新点的做法。

首先,对于每个小朋友,他是\(1\)就连到\(S\),是\(0\)就连到\(T\)。同时,对于每对好朋友,连一条无向边。所有的边权都为\(1\)。答案即为最小割。

非常神奇,对不对?但是只要稍微一想,就会发现这是正确的。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
namespace MaxFlow{
	const int N=10000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n+1,T=n+2;
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		if(x)ae(S,i,1);
		else ae(i,T,1);
	}
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),AE(x,y,1),AE(y,x,1);
	Dinic();
	printf("%d\n",res);
	return 0;
}

附一份调参失败,50分的模拟退火代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
int n,mn,m,cnt,Now,head[310];
struct node{
	int to,next,val;
}edge[200100];
void ae(int u,int v){
	edge[cnt].next=head[u],edge[cnt].to=v,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,head[v]=cnt++;
}
bool now[310],ans[310],STD[310];
const double delta=0.9995;
void SA(){
	double T=1000;
	memcpy(now,ans,sizeof(ans)),Now=mn;
	while(T>1e-10){
		int pos=rand()%n;
		now[pos]^=1;
		int bef=Now;
		if(now[pos]==STD[pos])Now--;
		else Now++;
		for(int i=head[pos];i!=-1;i=edge[i].next)if(now[pos]==now[edge[i].to])Now--;else Now++;
		int Delta=Now-mn;
		if(Delta<0)memcpy(ans,now,sizeof(now)),mn=Now;
		else if(exp(-Delta/T)*RAND_MAX<rand())now[pos]^=1,Now=bef;
		T*=delta; 
	}
}
void solve(){
	memcpy(ans,STD,sizeof(STD));
	SA(),SA(),SA();
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),srand(19260817);
	for(int i=0;i<n;i++)scanf("%d",&STD[i]);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),x--,y--,ae(x,y),mn+=(STD[x]!=STD[y]);
	solve();
	printf("%d\n",mn);
	return 0;
}

LI.[ZJOI2009]假期的宿舍

这题又是近似于我们的第一题最小路径覆盖问题的题目。建图简单,也是拆点,对于互相认识的两个人由入点连向出点。

但是我因为总是忘了一些细节然后WA了一堆……

代码:

#include<bits/stdc++.h>
using namespace std;
int T_T,n,is[60],out[60],know[60][60],sum;
namespace MaxFlow{
	const int N=1000,M=200000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d",&T_T);
	while(T_T--){
		scanf("%d",&n),memset(head,-1,sizeof(head)),cnt=res=sum=0,S=n*2+1,T=n*2+2;
		for(int i=1;i<=n;i++){
			scanf("%d",&is[i]);
			if(!is[i])ae(S,i,1),sum++;
			else ae(i+n,T,1);
		}
		for(int i=1;i<=n;i++){
			scanf("%d",&out[i]);
			if(!out[i]&&is[i])ae(S,i,1),sum++;
		}
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
			scanf("%d",&know[i][j]);
			if(i==j)know[i][j]=true;
			if(!is[i]&&!is[j])continue;
			if(know[i][j])ae(i,j+n,1);
		}
		Dinic();
		puts(res==sum?"^_^":"T_T");
	}
	return 0;
}

LIII.CF628F Bear and Fair Set

同理,题解

LIV.[ZJOI2009]狼和羊的故事

一看到这道题:哇,这什么神仙题呀!

然后就没有想出来。

题解也很神仙:

1.从源点向每头羊连边权为\(INF\)的边

2.从每头狼向汇点连边权为\(INF\)的边

3.对于每个点,向相邻的\(4\)个点连边权为\(1\)的边。

然后就完了。答案即为最小割。

\(What?!?!?\)

好像是对的。\(1\)\(2\)中的边防止割断,只能去割\(3\)中边。割完后,羊集合与狼集合就不连通了。这就相当于修了栅栏。至于最小割,当然是修最少的栅栏了!

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
namespace MaxFlow{
	const int N=10100,M=500000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*m,T=n*m+1;
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++){
		scanf("%d",&x);
		for(int k=0;k<4;k++)if((i+dx[k])>=0&&(i+dx[k])<n&&(j+dy[k])>=0&&(j+dy[k])<m)ae(i*m+j,(i+dx[k])*m+(j+dy[k]),1);
		if(!x)continue;
		if(x==1)ae(S,i*m+j,0x3f3f3f3f);
		else ae(i*m+j,T,0x3f3f3f3f);
	}
	Dinic();
	printf("%d\n",res);
	return 0;
}

LV.[CQOI2009]跳舞

这道题我的建图是正确的,但是因为没有想到二分,就没能做出来。

首先,显然可以二分舞曲数量,设答案为\(ans\),则所有舞曲数量\(\leq ans\)的方案显然都合法,所有舞曲数量\(> ans\)的方案显然都不合法。

然后想一下在具体的二分数量\(mid\)下应该如何\(check\)

为了限制每个人最多只能匹配\(mid\)次,就从源点向每个♂连\(mid\)的流量,从每个♀向汇点连\(mid\)的流量。为了满足\(k\)的限制,我们开虚拟节点\(B\)\(G\)。从每个♂向对应的\(B\)\(k\)的流量,从每个\(G\)向对应的♀连\(k\)的流量。然后,对于所有相互喜欢的对,在♂和♀之间连一条流量为\(1\)的边;对于所有不相互喜欢的对,在\(B\)\(G\)间连一条流量为\(1\)的边。

如果\(mid\times n=flow\),则这个\(mid\)合法。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
char s[60][60];
namespace MaxFlow{
	const int N=100000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
bool che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int i=0;i<n;i++)ae(S,i,ip),ae(i,i+2*n,m),ae(i+n,T,ip),ae(i+3*n,i+n,m);
	for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(s[i][j]=='Y')ae(i,j+n,1);else ae(i+2*n,j+3*n,1);
	Dinic();
	return ip*n==res;
}
int main(){
	scanf("%d%d",&n,&m),S=4*n+1,T=4*n+2;
	for(int i=0;i<n;i++)scanf("%s",s[i]);
	int l=0,r=n;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(che(mid))l=mid;
		else r=mid-1;
	}
	printf("%d\n",l);
	return 0;
}

LVI.[国家集训队]happiness

又是对偶建图的题甚至连题面都跟文理分科类似,就不再赘述了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define O(x,y) (x)*m+(y)
#define A(x,y) (x)*m+(y)+n*m
#define B(x,y) (x)*m+(y)+n*m*2
#define C(x,y) (x)*m+(y)+n*m*3
#define D(x,y) (x)*m+(y)+n*m*4
int n,m,sum;
namespace MaxFlow{
	const int N=100000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*m*5,T=n*m*5+1;
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),sum+=x,ae(S,O(i,j),x);
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),sum+=x,ae(O(i,j),T,x);
	for(int i=0;i+1<n;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),sum+=x,ae(S,A(i,j),x),ae(A(i,j),O(i,j),0x3f3f3f3f),ae(A(i,j),O(i+1,j),0x3f3f3f3f);
	for(int i=0;i+1<n;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),sum+=x,ae(B(i,j),T,x),ae(O(i,j),B(i,j),0x3f3f3f3f),ae(O(i+1,j),B(i,j),0x3f3f3f3f);
	for(int i=0;i<n;i++)for(int j=0,x;j+1<m;j++)scanf("%d",&x),sum+=x,ae(S,C(i,j),x),ae(C(i,j),O(i,j),0x3f3f3f3f),ae(C(i,j),O(i,j+1),0x3f3f3f3f);
	for(int i=0;i<n;i++)for(int j=0,x;j+1<m;j++)scanf("%d",&x),sum+=x,ae(D(i,j),T,x),ae(O(i,j),D(i,j),0x3f3f3f3f),ae(O(i,j+1),D(i,j),0x3f3f3f3f);
	Dinic();
	printf("%d\n",sum-res);
	return 0;
}

LVIII.[SDOI2013]费用流

首先,\(B\)给安排费用时,肯定是挑流量最大的那条边,然后把所有的费用全塞给它。因此,\(A\)在保证最大流的前提下,肯定要让流量最大的边最小。

很容易想到二分,将所有边的容量都限制在二分值内。如果仍然保证最大流,则当前二分的值合法。(别忘了,流量是可以为实数的)

另外,为了防止卡精度,我第一问用了整数网络流,第二问用的是实数网络流。

代码:

#include<bits/stdc++.h>
using namespace std;
const double EPS=1e-6;
int n,m,p,ans;
double L,R;
namespace MF{
	const int N=1000,M=20000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
namespace DD{
	const int N=1000,M=20000;
	int head[N],cur[N],dep[N],cnt,S,T;
	double res;
	struct node{
		int to,next;
		double val;
	}edge[M];
	void ae(int u,int v,double w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val>EPS&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline double dfs(int x,double flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		double used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(edge[i].val<EPS||dep[edge[i].to]!=dep[x]+1)continue;
			register double ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,1e9);
		}
	}
}
struct EDGE{
	int u,v,w;
}e[1010];
bool che(double ip){
	memset(DD::head,-1,sizeof(DD::head)),DD::cnt=0,DD::res=0;
	for(int i=1;i<=m;i++)DD::ae(e[i].u,e[i].v,min(1.0*e[i].w,ip));
	DD::Dinic();
	return abs(DD::res-ans)<EPS;
}
int main(){
	scanf("%d%d%d",&n,&m,&p),MF::S=DD::S=1,MF::T=DD::T=n;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),ans=max(ans,e[i].w);
	R=ans;
	memset(MF::head,-1,sizeof(MF::head));
	for(int i=1;i<=m;i++)MF::ae(e[i].u,e[i].v,e[i].w);
	MF::Dinic();
	printf("%d\n",ans=MF::res);
	while(R-L>EPS){
		double mid=(L+R)/2;
		if(che(mid))R=mid;
		else L=mid;
	}
	printf("%lf\n",L*p);
	return 0;
}

LXII.[USACO09MAR]地震损失2Earthquake Damage 2

这题属于一上来就秒会的题,拆个点跑最小割即可。

但是!!!点\(1\)要强制没被毁坏。

反正随随便便调调就能A。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p;
namespace MaxFlow{
	const int N=10000,M=200000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
bool ok[10000];
int main(){
	scanf("%d%d%d",&n,&m,&p),memset(head,-1,sizeof(head)),S=2*n+1,T=2*n+2,ae(S,1,0x3f3f3f3f),ae(1,1+n,0x3f3f3f3f);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),ae(x+n,y,0x3f3f3f3f),ae(y+n,x,0x3f3f3f3f);
	for(int i=1,x;i<=p;i++)scanf("%d",&x),ok[x]=true;
	ok[1]=false;
	for(int i=1;i<=n;i++)if(ok[i])ae(i,i+n,0x3f3f3f3f),ae(i+n,T,0x3f3f3f3f);else ae(i,i+n,1);
	Dinic();
	printf("%d\n",res);
	return 0;
}

LXIII.Four Melodies

消减边数的好题,写了题解

LXIV.[国家集训队]圈地计划

也是一道好题,将奇偶建图对偶建图巧妙地结合在了一起。

我一开始想要把每个点拆成\(9\)个点你知道吗

如果是相同加收益的话,这就是L.[SHOI2007]善意的投票,直接在相邻的两个点直接连一条无向边即可。

但是现在是不同加收益,怎么办?

没关系,依照奇偶建图,我们可以在奇点上,农业连\(S\),工业连\(T\);在偶点上,农业连\(T\),工业连\(S\)。这样子,就可以依然保证“同侧加收益”。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1},sum;
namespace MaxFlow{
	const int N=20000,M=800000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*m+1,T=n*m+2;
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++){
		scanf("%d",&x),sum+=x;
		if((i+j)&1)ae(S,i*m+j,x);
		else ae(i*m+j,T,x);
	}
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++){
		scanf("%d",&x),sum+=x;
		if((i+j)&1)ae(i*m+j,T,x);
		else ae(S,i*m+j,x);
	}
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++){
		scanf("%d",&x);
		for(int k=0;k<4;k++){
			if(i+dx[k]<0||i+dx[k]>=n||j+dy[k]<0||j+dy[k]>=m)continue;
			sum+=x;
			AE(i*m+j,(i+dx[k])*m+(j+dy[k]),x);
		}
	}
	Dinic();
	printf("%d\n",sum-res);
	return 0;
}

LXV.[HNOI2007]紧急疏散EVACUATE

嗯,一道还不错的题。

一开始写了个假算法还拿了70分这数据得有多水

首先,这个时间明显可以二分。考虑如何在判断在限定时间内能否全部疏散。设时间为\(mid\),则显然,每扇门最多可以疏散\(mid\)个人;而同时,每个人都只能到达距离不超过\(mid\)的门。因此,我们从源点向每个空地连容量为\(1\)的边,从每个空地向每扇到得了的门连容量为\(1\)的边,再从门向汇点连容量为\(mid\)的边。如果最大流=空地数,则当前\(mid\)合法。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,L,R,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1},sum,dis[30][30][30][30];
char s[110][110];
namespace MaxFlow{
	const int N=5000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
bool che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=sum=0;
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		if(s[i][j]=='X')continue;
		if(s[i][j]=='.'){
			ae(S,i*m+j,1),sum++;
			for(int k=0;k<n;k++)for(int l=0;l<m;l++)if(s[k][l]=='D'&&dis[i][j][k][l]<=ip)ae(i*m+j,k*m+l,1);
		}else ae(i*m+j,T,ip);
	}
	Dinic();
	return res==sum;
}
queue<pair<int,int> >Q;
void DIS(int u,int v){
	if(s[u][v]!='.')return;
	dis[u][v][u][v]=0;
	Q.push(make_pair(u,v));
	while(!Q.empty()){
		pair<int,int> p=Q.front();Q.pop();
		for(int i=0;i<4;i++){
			int nx=p.first+dx[i],ny=p.second+dy[i];
			if(nx<0||nx>=n||ny<0||ny>=m||s[nx][ny]=='X'||dis[u][v][nx][ny]!=0x3f3f3f3f)continue;
			dis[u][v][nx][ny]=dis[u][v][p.first][p.second]+1;
			Q.push(make_pair(nx,ny));
		}
	}
}
int main(){
	scanf("%d%d",&n,&m),memset(dis,0x3f3f3f3f,sizeof(dis)),L=1,R=n*m,S=n*m+1,T=n*m+2;
	for(int i=0;i<n;i++)scanf("%s",s[i]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)DIS(i,j);
	while(L<R){
		int mid=(L+R)>>1;
//		printf("%d %d\n",L,R);
		if(che(mid))R=mid;
		else L=mid+1;
	}
	if(!che(R))puts("impossible");
	else printf("%d\n",R);
	return 0;
} 

LXVI.[NOI2010]海拔

这题考试如果碰到了会写暴力最小割就行了,对偶图什么的毒瘤玩意管都不要管

首先,这道题所有算法的第一步,就是要证明海拔要么\(0\)要么\(1\),不存在介于两者之间的海拔

感性理解一下,因为反正最后都要上升到\(1\),那么一段一段地上升,每段都会有代价,结果一定不会比一升到顶要优。(当初在想到这一点时我也是费了很大劲才说服我自己,理解万岁)。

知道了这一点,就好办多了。因此,我们把所有点分成两个集合,即\(1\)集合与\(0\)集合,只有两个集合间的边有贡献。

这不就是最小割吗?

因此我非常快乐地码了个最小割上去。

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
int n;
namespace MaxFlow{
	const int N=1000000,M=5000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	inline void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		register int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head)),n++,S=0,T=n*n-1;
	for(register int i=0;i<n;i++)for(register int j=0,x;j+1<n;j++)scanf("%d",&x),ae(i*n+j,i*n+(j+1),x);
	for(register int i=0;i+1<n;i++)for(register int j=0,x;j<n;j++)scanf("%d",&x),ae(i*n+j,(i+1)*n+j,x);
	for(register int i=0;i<n;i++)for(register int j=1,x;j<n;j++)scanf("%d",&x),ae(i*n+j,i*n+(j-1),x);
	for(register int i=1;i<n;i++)for(register int j=0,x;j<n;j++)scanf("%d",&x),ae(i*n+j,(i-1)*n+j,x);
	Dinic();
	printf("%d\n",res);
	return 0;
}

但是,就算开了O3,慢腾腾的网络流还是只能拿到90分,二号点始终跑不过去。

对偶图这种玩意,我是没明白,也压根没打算明白(反正考了也写不出来),代码放这儿,想学的自己看题解吧。

#include<bits/stdc++.h>
using namespace std;
int n,head[1000100],cnt,dis[1000100],S,T;
struct node{
	int to,next,val;
}edge[10001000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
bool vis[1000100];
priority_queue<pair<int,int> >q;
int Dijkstra(){
	memset(dis,0x3f3f3f3f,sizeof(dis)),dis[S]=0,q.push(make_pair(0,S));
	while(!q.empty()){
		int x=q.top().second;q.pop();
		if(vis[x])continue;vis[x]=true;
		for(int i=head[x];i!=-1;i=edge[i].next)if(dis[edge[i].to]>dis[x]+edge[i].val)dis[edge[i].to]=dis[x]+edge[i].val,q.push(make_pair(-dis[edge[i].to],edge[i].to));
	}
	return dis[T];
}
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head)),S=n*n+1,T=n*n+2;
	for(int i=0;i<=n;i++)for(int j=1,x;j<=n;j++){
		scanf("%d",&x);
		if(!i)ae(j,T,x);
		else if(i==n)ae(S,(i-1)*n+j,x);
		else ae(i*n+j,(i-1)*n+j,x);
	}
	for(int i=1;i<=n;i++)for(int j=0,x;j<=n;j++){
		scanf("%d",&x);
		if(!j)ae(S,(i-1)*n+1,x);
		else if(j==n)ae(i*n,T,x);
		else ae((i-1)*n+j,(i-1)*n+(j+1),x);
	}
	for(int i=0;i<=n;i++)for(int j=1,x;j<=n;j++){
		scanf("%d",&x);
		if(!i)ae(T,j,x);
		else if(i==n)ae((i-1)*n+j,S,x);
		else ae((i-1)*n+j,i*n+j,x);
	}
	for(int i=1;i<=n;i++)for(int j=0,x;j<=n;j++){
		scanf("%d",&x);
		if(!j)ae((i-1)*n+1,S,x);
		else if(j==n)ae(T,i*n,x);
		else ae((i-1)*n+(j+1),(i-1)*n+j,x);
	}
	printf("%d\n",Dijkstra());
	return 0;
} 

LXVIII.CF1082G Petya and Graph

虽然题意有很大不同,但实际上算法同XL.[NOI2006]最大获利几乎完全一致。反正跑个最小割就A了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,sum;
namespace MaxFlow{
	const int N=3000,M=200000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
signed main(){
	scanf("%lld%lld",&n,&m),memset(head,-1,sizeof(head)),S=n+m+1,T=n+m+2;
	for(int i=1,x;i<=n;i++)scanf("%lld",&x),ae(S,i,x);
	for(int i=1,x,y,z;i<=m;i++)scanf("%lld%lld%lld",&x,&y,&z),ae(n+i,T,z),sum+=z,ae(x,n+i,0x3f3f3f3f3f3f3f3f),ae(y,n+i,0x3f3f3f3f3f3f3f3f);
	Dinic();
	printf("%lld\n",sum-res);
	return 0;
}

LXXIII.清理雪道

这题有两种方法:

1.建立虚拟源点\(s\)和虚拟汇点\(t\),所有的点从源点连流量为\(1\)的边,并向汇点连流量为\(1\)的边。除了源点的边费用为\(1\)以外,其他边费用都为\(0\)。原图中的边具有\(1\)的下界和\(INF\)的上界。这就转化为有源汇有上下界最小费用流。则答案为(最小费用)。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,degree[1000],s,t;
namespace MCMF{
	const int N=1000,M=2000000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[0])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head)),s=n+1,t=n+2,S=n+3,T=n+4;
	for(int i=1,t1,t2;i<=n;i++){
		scanf("%d",&t1),ae(s,i,0x3f3f3f3f,1),ae(i,t,0x3f3f3f3f,0);
		while(t1--)scanf("%d",&t2),degree[i]--,degree[t2]++,ae(i,t2,0x3f3f3f3f,0);
	}
	ae(t,s,0x3f3f3f3f,0);
	for(int i=1;i<=n;i++)if(degree[i]>0)ae(S,i,degree[i],0);else ae(i,T,-degree[i],0);
	while(SPFA());
	printf("%d\n",cost);
	return 0;
} 

2.直接抛弃费用,跑最小流。
代码:

#include<bits/stdc++.h>
using namespace std;
int n,degree[1000],s,t;
namespace MaxFlow{
	const int N=1000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head)),s=n+1,t=n+2,S=n+3,T=n+4;
	for(int i=1,t1,t2;i<=n;i++){
		scanf("%d",&t1),ae(s,i,0x3f3f3f3f),ae(i,t,0x3f3f3f3f);
		while(t1--)scanf("%d",&t2),degree[i]--,degree[t2]++,ae(i,t2,0x3f3f3f3f);
	}
	for(int i=1;i<=n;i++)if(degree[i]>0)ae(S,i,degree[i]);else ae(i,T,-degree[i]);
	Dinic(); 
	ae(t,s,0x3f3f3f3f);
	Dinic();
	for(int i=head[t];i!=-1;i=edge[i].next)if(edge[i].to==s)res=edge[i^1].val;
	printf("%d\n",res);
	return 0;
} 

LXXV.CF852D Exploration plan

明显时间具有单调性,因此可以二分。

首先,可以\(floyd\)预处理出任意两点间距离。

然后,我们拆点,对于一个二分出来的时间\(ip\),如果两个点\(i,j\)有距离\(\leq ip\),就连边\((in_i,out_j,INF)\)

对于每支团队,连边\((S,x_i,1)\)

对于每个点,连边\((i,T,1)\)

只要判断最终的答案是否符合要求即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p,r,dis[610][610],occ[610];
namespace MaxFlow{
	const int N=10000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int i=1;i<=n;i++)ae(S,i,occ[i]),ae(i+n,T,1);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(dis[i][j]<=ip)ae(i,j+n,0x3f3f3f3f);
	Dinic();
	return res>=r;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&p,&r),memset(dis,0x3f3f3f3f,sizeof(dis)),S=n*2+1,T=n*2+2;
	for(int i=1;i<=n;i++)dis[i][i]=0;
	for(int i=1,x;i<=p;i++)scanf("%d",&x),occ[x]++;
	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),dis[x][y]=dis[y][x]=min(dis[x][y],z);
	for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	int L=0,R=1731311;
	while(L<R){
		int mid=(L+R)>>1;
		if(che(mid))R=mid;
		else L=mid+1;
	}
	if(!che(R))puts("-1");
	else printf("%d\n",R);
	return 0;
}

LXXIX.士兵占领

这题有多种方法,可以看题解,但是为了锻炼有上下界网络流的水平,我果断写了有上下界的网络流。

对于每一行\(row_i\),连边\((S,row_i,[L_i,INF))\)

对于每一列\(col_i\),连边\((col_i,T,[C_i,INF))\)

对于每一个可以放置士兵的点\((i,j)\),连边\((row_i,col_j,[0,1])\)

则答案为最小流。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,degree[210],p,s,t,sum;
namespace MaxFlow{
	const int N=1000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
bool ok[110][110];
int main(){
	scanf("%d%d%d",&n,&m,&p),memset(head,-1,sizeof(head)),s=n+m+1,t=n+m+2,S=n+m+3,T=n+m+4;
	for(int i=1,x;i<=n;i++)scanf("%d",&x),degree[s]-=x,degree[i]+=x,ae(s,i,0x3f3f3f3f);
	for(int i=1,x;i<=m;i++)scanf("%d",&x),degree[i+n]-=x,degree[t]+=x,ae(i+n,t,0x3f3f3f3f);
	for(int i=1,x,y;i<=p;i++)scanf("%d%d",&x,&y),ok[x][y]=true;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!ok[i][j])ae(i,j+n,1);
	for(int i=1;i<=t;i++)if(degree[i]>0)ae(S,i,degree[i]),sum+=degree[i];else ae(i,T,-degree[i]);
	Dinic();
	ae(t,s,0x3f3f3f3f);
	Dinic();
	if(sum!=res){puts("JIONG!");return 0;}
	for(int i=head[s];i!=-1;i=edge[i].next)if(edge[i].to==t)printf("%d\n",edge[i].val);
	return 0;
}

LXXX.酒店之王

之前一开始脑残了,死活想不出来,然后发现就是将每个人拆点以限制每个人只能匹配一次。

然后就是非常模板的最大流了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p;
namespace MaxFlow{
	const int N=1000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d%d",&n,&m,&p),memset(head,-1,sizeof(head)),S=m+n*2+p+1,T=m+n*2+p+2;
	for(int i=1,x;i<=n;i++)for(int j=1;j<=m;j++){
		scanf("%d",&x);
		if(x)ae(j,i+m,1);
	}
	for(int i=1,x;i<=n;i++)for(int j=1;j<=p;j++){
		scanf("%d",&x);
		if(x)ae(i+m+n,j+m+n*2,1);
	}
	for(int i=1;i<=n;i++)ae(i+m,i+m+n,1);
	for(int i=1;i<=m;i++)ae(S,i,1);
	for(int i=1;i<=p;i++)ae(i+m+n*2,T,1);
	Dinic();
	printf("%d\n",res);
	return 0;
}

LXXXI.80人环游世界

同XLIII.[SDOI2010]星际竞速类似,也是I.最小路径覆盖问题的奇妙变种。

老套路拆点连边。

为了处理\(m\)个人这个限制,我们仿照XLIII.[SDOI2010]星际竞速,在出点处给他补上\(v_i\)个流量。但是,这所有补上的流量之和,加起来不能超过\(m\)

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s;
namespace MCMF{
	const int N=1000,M=2000000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[0])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=2*n+1,T=2*n+2,s=2*n+3,ae(S,s,m,0);
	for(int i=1,x;i<=n;i++)scanf("%d",&x),ae(S,i+n,x,0),ae(i,T,x,0),ae(s,i,x,0);
	for(int i=1;i<=n;i++)for(int j=i+1,x;j<=n;j++){
		scanf("%d",&x);
		if(x!=-1)ae(i+n,j,0x3f3f3f3f,x);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
} 

LXXXII.CF237E Build String

一开始,我没想到这稀奇古怪的题是网络流。但是,因为有这么个限制(每个字符串只能删掉\(a_i\)个;每个字符串每删一个费用为\(i\);每个字符串中每个字符最多只能删掉的数量都有限制;最终的\(t\)串中每个字符删掉的数量还是有限制),常规的方法似乎不太好整。不如就遇事不决网络流,直接最小费用最大流一下,OK。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,SS,TT,val[110],tot[110][26],occ[26];
char s[110];
namespace MCMF{
	const int N=10000,M=2000000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost,flow;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
//		if(w)printf("%d %d %d %d\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T,flow+=mn;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%s",s),TT=strlen(s),memset(head,-1,sizeof(head));
	for(int i=0;i<TT;i++)occ[s[i]-'a']++;
	scanf("%d",&n),S=n+26,T=n+26+1;
	for(int i=0;i<n;i++){
		scanf("%s%d",s,&val[i]),SS=strlen(s),ae(S,i,val[i],i+1);
		for(int j=0;j<SS;j++)tot[i][s[j]-'a']++;
		for(int j=0;j<26;j++)ae(i,n+j,tot[i][j],0);
	}
	for(int i=0;i<26;i++)ae(n+i,T,occ[i],0);
	while(SPFA());
//	printf("%d\n",flow);
	if(flow==TT)printf("%d\n",cost);
	else puts("-1");
	return 0;
}

LXXXIII.[BJOI2012]连连看

很明显是费用流。但是它是二分图吗?

根据暴力搜,是的,只是我证不出来

于是我就写了暴力二分图分部的代码:

#include<bits/stdc++.h>
using namespace std;
int a,b;
namespace MCMF{
	const int N=10000,M=2000000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost,flow;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
//		printf("%d %d %d %d\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x80,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
//			printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]<dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==0x80808080)return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T,flow+=mn;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
namespace BG{
	const int N=10000,M=2000000;
	int head[N],cnt;
	bool col[N],vis[N];
	struct node{
		int to,next;
	}edge[M];
	void ae(int u,int v){
		edge[cnt].next=head[u],edge[cnt].to=v,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,head[v]=cnt++;
	}
	void dfs(int x){
		for(int i=head[x];i!=-1;i=edge[i].next){
			if(vis[edge[i].to]){if(col[edge[i].to]==col[x])puts("QWQ");continue;}
			vis[edge[i].to]=true,col[edge[i].to]=!col[x];
			if(col[x])MCMF::ae(x,edge[i].to,1,x+edge[i].to);
			else MCMF::ae(edge[i].to,x,1,x+edge[i].to);
			dfs(edge[i].to);
		}
	}
}
int main(){
	scanf("%d%d",&a,&b),memset(MCMF::head,-1,sizeof(MCMF::head)),MCMF::S=b+1,MCMF::T=b+2,memset(BG::head,-1,sizeof(BG::head));
	for(int i=a;i<=b;i++)for(int j=i+1;j<=b;j++){
		int k=(int)sqrt(j*j-i*i);
		if(k*k+i*i!=j*j)continue;
		if(__gcd(i,k)!=1)continue;
//		printf("%d %d\n",i,j);
		BG::ae(i,j);
	}
	for(int i=a;i<=b;i++){
		if(!BG::vis[i])BG::vis[i]=true,BG::dfs(i);
		if(BG::col[i])MCMF::ae(MCMF::S,i,1,0);
		else MCMF::ae(i,MCMF::T,1,0);
	}
	while(MCMF::SPFA());
	printf("%d %d\n",MCMF::flow,MCMF::cost);
	return 0;
}

后来呢,我把它交上去了,只有70分,WA了三个点,至今原因不明。

讲一下正解吧。是拆点,将每个点拆成入点和出点,每有一对合法的对,就在入点和出点间分别相互连边。然后源点连入点,出点连汇点。这样,每个点实际上会被匹配两次:入点匹配一次,出点匹配一次,因此无论是流量还是费用,都要除以\(2\)

代码:

#include<bits/stdc++.h>
using namespace std;
int a,b;
namespace MCMF{
	const int N=10000,M=2000000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost,flow;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
//		printf("%d %d %d %d\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x80,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
//			printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]<dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==0x80808080)return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T,flow+=mn;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d",&a,&b),memset(head,-1,sizeof(head)),S=b+1,T=b+2;
	for(int i=a;i<=b;i++)for(int j=i+1;j<=b;j++){
		int k=(int)sqrt(j*j-i*i);
		if(k*k+i*i!=j*j)continue;
		if(__gcd(i,k)!=1)continue;
		ae(i,j+b,1,i+j);
		ae(j,i+b,1,i+j);
	}
	for(int i=a;i<=b;i++)ae(S,i,1,0),ae(i+b,T,1,0);
	while(SPFA());
	printf("%d %d\n",flow>>1,cost>>1);
	return 0;
}

LXXXIV.[JSOI2010]冷冻波

这里我这个一点解析几何也没有学过的蒟蒻就来爆算一下公式吧。

首先,点到直线距离公式,我从网上搜到了:

\(d=\left|\dfrac{Ax_0+By_0+C}{\sqrt{A^2+B^2}}\right|\)

其中\(Ax+By+C\)是直线方程,\((x_0,y_0)\)是点坐标。

学信息的,都应该尽量避免\(double\)的出现。尝试在\(int\)范围内把它搞出来。

在判断是否视线被木头阻拦时,我们要判断是否有\(d>r\)

\(d>r\)

\(\Leftrightarrow\left|\dfrac{Ax_0+By_0+C}{\sqrt{A^2+B^2}}\right|>r\)

\(\Leftrightarrow\dfrac{(Ax_0+By_0+C)^2}{A^2+B^2}>r^2\)

\(\Leftrightarrow(Ax_0+By_0+C)^2>r^2(A^2+B^2)\)

这时候,我们就可以在\(int\)上处理这个问题。

我们尝试解出\(A,B,C\)。设巫妖位于\((x_1,y_1)\),小精灵位于\((x_2,y_2)\)。则有:

\(\begin{cases}Ax_1+By_1+C=0\\Ax_2+By_2+C=0\end{cases}\)

一番处理之后,我们发现,\(A=y_1-y_2,B=x_2-x_1\)是一组合法解。

则有\(C=-(Ax_1+By_1)\)

但是,线段和直线还是有区别的。有可能这棵树与直线的距离很小,但是它离线段很远。

我们想一想,因为巫妖和精灵肯定都在树外面,那么如果在\(\triangle\text{巫妖、树、精灵}\)中,\(\angle\text{巫妖、精灵、树}\)或者\(\angle\text{精灵、巫妖、树}\)为钝角或直角的话,那肯定符合上述“这棵树与直线的距离很小,但是它离线段很远”的描述。

我们有\(\vec{a}\cdot\vec{b}=|\vec{a}||\vec{b}|\cos\theta=(\vec{a}_x\vec{b}_x)+(\vec{a}_y\vec{b}_y)\)

\(\theta\geq\dfrac{\pi}{2}\)时,有\(\cos\theta\leq 0\),即\(\vec{a}\cdot\vec{b}\leq 0\)

我们只需要这么点乘判断一下即可。

我们已经成功地可以在整数域内找出所有可以互相攻击到的对了。接下来只需要二分一个时间,用网络流判定即可。

就算这样,我的程序还是只有70分,原因不明。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,u,tot;
struct witch{
	int x,y,r,t;
}w[210];
struct spirit{
	int x,y;
}s[210];
struct tree{
	int x,y,r;
}t[210];
pair<int,int>p[100100];
namespace MaxFlow{
	const int N=410,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
bool che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int i=1;i<=n;i++)ae(S,i,ip/w[i].t+1);
	for(int i=1;i<=m;i++)ae(i+n,T,1);
	for(int i=1;i<=tot;i++)ae(p[i].first,p[i].second,1);
	Dinic();
	return res==m;
}
double dis1(int xx1,int yy1,int xx2,int yy2){
	return sqrt((xx1-xx2)*(xx1-xx2)+(yy1-yy2)*(yy1-yy2));
}
double dis2(int xx1,int yy1,int xx2,int yy2,int xx3,int yy3){
	double A=1.0*(yy1-yy2)/(xx1-xx2),B=-1,C=0.0+yy2-A*xx2;
	return fabs(A*xx3+B*yy3+C)/sqrt(A*A+B*B);
}
bool okk[210];
signed main(){
	scanf("%lld%lld%lld",&n,&m,&u),S=n+m+1,T=n+m+2;
	for(int i=1;i<=n;i++)scanf("%lld%lld%lld%lld",&w[i].x,&w[i].y,&w[i].r,&w[i].t);
	for(int i=1;i<=m;i++)scanf("%lld%lld",&s[i].x,&s[i].y);
	for(int i=1;i<=u;i++)scanf("%lld%lld",&t[i].x,&t[i].y,&t[i].r);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		double d=dis1(w[i].x,w[i].y,s[j].x,s[j].y);
//		printf("%lf\n",d);
		if(d>w[i].r)continue;
		bool ok=true;
		for(int k=1;k<=u;k++){
			double d1=dis2(w[i].x,w[i].y,s[j].x,s[j].y,t[k].x,t[k].y);
			double d2=dis1(w[i].x,w[i].y,t[k].x,t[k].y);
			double d3=dis1(s[j].x,s[j].y,t[k].x,t[k].y);
			if(d2<d3)swap(d2,d3);
			double d4=sqrt(d2*d2-d1*d1);
			double d5=(d4<d?d1:d3);
//			printf("%lf %lf %lf %lf %lf\n",d1,d2,d3,d4,d5);
			ok&=(d5>t[k].r);
		}
		if(ok)p[++tot]=make_pair(i,j+n),okk[j]=true;
	}
	for(int i=1;i<=m;i++)if(!okk[i]){puts("-1");return 0;}
	int l=0,r=4000000;
	while(l<r){
		int mid=(l+r)>>1;
		if(che(mid))r=mid;
		else l=mid+1;
	}
	printf("%lld\n",r);
	return 0;
}
/*
1 1 1
0 0 2 1
0 2
0 -100 99
*/

LXXXV.CF863F Almost Permutation

没什么好说的,直接大力差分建图。那种奇奇怪怪的限制直接暴力跑出来每个位置可以填的东西的上下界即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,up[100],lw[100];
namespace MCMF{
	const int N=1000,M=20000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[0])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)up[i]=n,lw[i]=1;
	for(int i=1,t1,t2,t3,t4;i<=m;i++){
		scanf("%d%d%d%d",&t1,&t2,&t3,&t4);
		if(t1==1)for(int j=t2;j<=t3;j++)lw[j]=max(lw[j],t4);
		else for(int j=t2;j<=t3;j++)up[j]=min(up[j],t4);
	}
	for(int i=1;i<=n;i++)if(up[i]<lw[i]){puts("-1");return 0;}
	for(int i=1;i<=n;i++)for(int j=lw[i];j<=up[i];j++)ae(j,i+n,1,0);
	for(int i=1;i<=n;i++){
		ae(i+n,T,1,0);
		for(int j=1;j<=n;j++)ae(S,i,1,2*j-1);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

LXXXVIII.[JSOI2015]圈地

非常水的题,仿照XLVIII.文理分科对偶建图跑最小割,LIV.[ZJOI2009]狼和羊的故事
建图,然后就OK了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,sum;
namespace MaxFlow{
	const int N=50000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*m,T=n*m+1;
	for(int i=0;i<n;i++)for(int j=0,x;j<m;j++){
		scanf("%d",&x),sum+=abs(x);
		if(x>0)ae(S,i*m+j,x);
		if(x<0)ae(i*m+j,T,-x);
	}
	for(int i=0;i<n-1;i++)for(int j=0,x;j<m;j++)scanf("%d",&x),AE(i*m+j,(i+1)*m+j,x);
	for(int i=0;i<n;i++)for(int j=0,x;j<m-1;j++)scanf("%d",&x),AE(i*m+j,i*m+(j+1),x);
	Dinic();
	printf("%d\n",sum-res);
	return 0;
}

LXXXIX.AT3672 [ARC085C] MUL

我一直认为89的写法应该是XCIX或是其它什么东西的……

这题最小权闭合子图的模型应该非常明显,因为你选择打碎所有编号为\(x\)的倍数的水晶这个操作是强制的。

我一开始想的是对“打碎所有编号为\(x\)的倍数的水晶”这一操作单独建点,但这是不正确的,因为这就要求你不能单独选“水晶节点”,只能选“操作节点”。

我们考虑将“操作节点\(x\)”同“水晶节点\(x\)”合并,即从节点\(x\)向每个\(x\)的倍数节点连边。这样就不会出现“不能选的节点”,就可以套最小权闭合子图的模型了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,num[110],sum;
namespace MaxFlow{
	const int N=1000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
signed main(){
	scanf("%lld",&n),S=n+1,T=n+2,memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++){
		scanf("%lld",&num[i]);
		if(num[i]>0)ae(i,T,num[i]),sum+=num[i];
		else ae(S,i,-num[i]);
	}
	for(int i=1;i<=n;i++)for(int j=i+i;j<=n;j+=i)ae(i,j,0x3f3f3f3f);
	Dinic();
	printf("%lld\n",sum-res);
	return 0;
}

XC.[BJOI2016]水晶

我佛了……负数模完\(3\)居然模出来的还是负数……害得我整整debug了一下午……

首先,我们发现这个坐标是三元坐标,但是二维平面上的点只需要两个坐标就能表示。因此我们尝试削减一维坐标。

我们将一个点表示成向量的形式,即\(\vec{v}=a\vec{x}+b\vec{y}+c\vec{z}\)

发现\(\vec{x}+\vec{y}+\vec{z}=\vec{0}\),则有\(\vec{z}=-\vec{x}-\vec{y}\),得到\(\vec{v}=(a-c)\vec{x}+(b-c)\vec{y}\)。也就是说,原本意义下的点\((x,y,z)\)可以被转为\((x-z,y-z)\),成功削减一维坐标。

对于这个模型,我们考虑使用最小割解决它。

我们发现,无论是\(a\)共振还是\(b\)共振,总是模\(3\)\(0\)、余\(1\)、余\(2\)的点各有一个。这样,我们就分层建图,所有余\(0\)的为一层,余\(1\)的为一层,余\(2\)的为一层。

然后拆点,保证每个节点只需要被割一次就会解除所有与它有关的共振。之后,对于每组共振,总是余\(0\)连余\(1\),余\(1\)连余\(2\)

对于这个\(10\%\)的要求,直接将所有节点的权值\(\times 10\)即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,lim,sum,dx[6]={1,1,0,-1,-1,0},dy[6]={0,1,1,0,-1,-1};
map<pair<int,int>,int>mp,id;
namespace MaxFlow{
	const int N=200000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
//		printf("%d %d %d\n",u,v,w);
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head));
	for(int i=1,x,y,z,w;i<=n;i++){
		scanf("%d%d%d%d",&x,&y,&z,&w);
		x-=z,y-=z;
		if(!(((x+y)%3+3)%3))w*=11;
		else w*=10;
		sum+=w;
		if(mp.find(make_pair(x,y))==mp.end())id[make_pair(x,y)]=++lim;
		mp[make_pair(x,y)]+=w;
	}
	S=2*lim+1,T=2*lim+2;
	for(map<pair<int,int>,int>::iterator it=mp.begin();it!=mp.end();it++){
		int x=it->first.first,y=it->first.second,z=id[it->first];
		ae(z,z+lim,it->second);
		if(((x+y)%3+3)%3==1)ae(S,z,0x3f3f3f3f);
		if(((x+y)%3+3)%3==2)ae(z+lim,T,0x3f3f3f3f);
		if(((x+y)%3+3)%3)continue;
		int qwq[6];
		for(int i=0;i<6;i++){
			if(mp.find(make_pair(x+dx[i],y+dy[i]))==mp.end())qwq[i]=-1;
			else qwq[i]=id[make_pair(x+dx[i],y+dy[i])];
		}
		for(int i=0;i<6;i++){
			if(qwq[i]==-1||qwq[(i+1)%6]==-1)continue;
//			printf("%d:%d %d %d\n",i,z,qwq[i],qwq[(i+1)%6]);
			if(i&1)ae(qwq[(i+1)%6]+lim,z,0x3f3f3f3f),ae(z+lim,qwq[i],0x3f3f3f3f);
			else ae(z+lim,qwq[(i+1)%6],0x3f3f3f3f),ae(qwq[i]+lim,z,0x3f3f3f3f);
		}
		for(int i=0;i<3;i++){
			if(qwq[i]==-1||qwq[i+3]==-1)continue;
			if(i&1)ae(qwq[i+3]+lim,z,0x3f3f3f3f),ae(z+lim,qwq[i],0x3f3f3f3f);
			else ae(z+lim,qwq[i+3],0x3f3f3f3f),ae(qwq[i]+lim,z,0x3f3f3f3f);
		}
	}
	Dinic();
	sum-=res;
	printf("%d.%d",sum/10,sum%10);
	return 0;
}

XCI.拍照

是XII.太空飞行计划问题的弱化版,把那题的程序照搬过来稍微改改就过了。

代码:

#include<bits/stdc++.h>
using namespace std;
int m,n,head[210],cnt,S,T,cur[210],dep[210],res,sum;
struct node{
	int to,next,val;
}edge[400100];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
}
queue<int>q;
inline bool bfs(){
	memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
	while(!q.empty()){
		register int x=q.front();q.pop();
		for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
	}
	return dep[T]>0;
}
bool reach;
inline int dfs(int x,int flow){
	if(x==T){
		res+=flow;
		reach=true;
		return flow;
	}
	int used=0;
	for(register int &i=cur[x];i!=-1;i=edge[i].next){
		if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
		register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
		if(ff){
			edge[i].val-=ff;
			edge[i^1].val+=ff;
			used+=ff;
			if(used==flow)break;
		}
	}
	return used;
}
inline void Dinic(){
	while(bfs()){
		reach=true;
		while(reach)reach=false,dfs(S,0x3f3f3f3f);
	}	
}
int main(){
	scanf("%d%d",&m,&n),memset(head,-1,sizeof(head)),S=n+m+1,T=n+m+2;
	for(int i=1,x,y;i<=m;i++){
		scanf("%d",&x),sum+=x;
		ae(i+n,T,x);
		scanf("%d",&y);
		while(y)ae(y,i+n,0x3f3f3f3f),scanf("%d",&y);
	}
	for(int i=1,x;i<=n;i++)scanf("%d",&x),ae(S,i,x);
	Dinic();
//	for(int i=n+1;i<=n+m;i++)if(!dep[i])printf("%d ",i-n);puts("");
//	for(int i=1;i<=n;i++)if(!dep[i])printf("%d ",i);puts("");
	printf("%d\n",sum-res);
	return 0;
}

XCII.[清华集训2012]最小生成树

这题数据到底多水呀……那个\(L\)没有读进来还有\(70\%\)……

这题主要是get一种判断边是否在MST中的一种方法:当所有比当前边小的边全都连上以后仍然不能使这条边的两个端点连通,则这条边就一定可以在MST上。

然后就是近似模板了……

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,lim,a[200100],b[200100],c[200100];
namespace MaxFlow{
	const int N=201000,M=2001000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=1,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]);
	scanf("%d%d%d",&S,&T,&lim);
	memset(head,-1,sizeof(head)),cnt=0;
	for(int i=1;i<=m;i++)if(c[i]<lim)ae(a[i],b[i]),ae(b[i],a[i]);
	Dinic();
	memset(head,-1,sizeof(head)),cnt=0;
	for(int i=1;i<=m;i++)if(c[i]>lim)ae(a[i],b[i]),ae(b[i],a[i]);
	Dinic();
	printf("%d\n",res);
	return 0;
}

XCIII.[ZOJ3229]Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流

我也不知道这名字为什么这么长……

确实很模板,随便建建就行。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,sum;
namespace MaxFlow{
	const int N=2000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res,degree[N];
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int l,int r){
		degree[v]+=l,degree[u]-=l;
//		printf("%d %d (%d,%d)\n",u,v,l,r);
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=r-l,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		memset(head,-1,sizeof(head)),memset(degree,0,sizeof(degree)),cnt=sum=res=0,s=n+m,t=n+m+1,S=n+m+2,T=n+m+3;
		for(int i=0,x;i<m;i++)scanf("%d",&x),ae(n+i,t,x,0x3f3f3f3f);
		for(int i=0,C,D,I,L,R;i<n;i++){
			scanf("%d%d",&C,&D),ae(s,i,0,D);
			while(C--)scanf("%d%d%d",&I,&L,&R),ae(i,n+I,L,R);
		}
		ae(t,s,0,0x3f3f3f3f);
		for(int i=0;i<=t;i++){
			if(degree[i]>0)ae(S,i,0,degree[i]),sum+=degree[i];
			if(degree[i]<0)ae(i,T,0,-degree[i]);
		}
		Dinic();
		if(res!=sum){puts("-1");puts("");continue;}
		for(int i=head[s];i!=-1;i=edge[i].next)if(edge[i].to==t)res=edge[i].val,edge[i].val=edge[i^1].val=0;
		for(int i=head[S];i!=-1;i=edge[i].next)edge[i].val=edge[i^1].val=0;
		for(int i=head[T];i!=-1;i=edge[i].next)edge[i].val=edge[i^1].val=0;
		S=s,T=t;
		Dinic();
		printf("%d\n",res);
		puts("");
	}
	return 0;
}

XCVI.AT696 グラフ

翻译:给定一张有向图,我们在图上找出两条路径\(P_1=\{V_1,E_1\},P_2=\{V_2,E_2\}\),路径可以有重复的点或边。求
\(\max(|V_1\cup V_2|)\)

题解

XCVII.[JSOI2008]Blue Mary的旅行

大水题啊。

这个\(n,t\leq 50\)就很暗示了,然后因为一个人一天只能坐一趟航班所以考虑分层按时间建图。

最坏的情况,一天走一个人,并且每个地方都走,最多\(n+t\)天。可以二分,但是二分还不如直接在残量网络上加边来得快。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p,tot;
pair<pair<int,int>,int>e[5000];
namespace MaxFlow{
	const int N=100000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline int Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
		return res;
	}
}
using namespace MaxFlow;
void day(int x){
	for(int i=1;i<=m;i++)ae(e[i].first.first+(x-1)*n,e[i].first.second+x*n,e[i].second);
	for(int i=1;i<=n;i++)ae(i+(x-1)*n,i+x*n,p);
	ae((x+1)*n,T,p);
}
int main(){
	scanf("%d%d%d",&n,&m,&p),memset(head,-1,sizeof(head)),S=(n+p)*n+1,T=(n+p)*n+2;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].first.first,&e[i].first.second,&e[i].second);
	ae(S,1,p);
	day(tot=1);
	while(Dinic()<p)day(++tot);
	printf("%d\n",tot);
	return 0;
}

IC.[POJ3469]Dual Core CPU

笔记的最后两题,放点水题罢。

对偶建图裸题,直接上就行了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int n,m;
namespace MaxFlow{
	const int N=20100;
	const int M=2001000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}	
	}	
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),S=n+1,T=n+2,memset(head,-1,sizeof(head));
	for(int i=1,x,y;i<=n;i++)scanf("%d%d",&x,&y),ae(S,i,x),ae(i,T,y);
	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),AE(x,y,z);
	Dinic();
	printf("%d\n",res);
	return 0;
}

CI.[国家集训队]部落战争

第一题,挑道比较板子的题罢。

首先很明显可以抽象出一张从一个位置走到另一个的有向无环图出来(因为只能从上往下打)

然后就是最小路径覆盖问题的板子。很遗憾的是,我忘记了最小路径覆盖问题的解法,于是使用了XLIII.[SDOI2010]星际竞速中的拆点+最小费用最大流的做法,一样能过,就是被卡掉一个点,不得不吸氧。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,a,b;
char s[60][60];
namespace MCMF{
	const int N=5010,M=200000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
	//	printf("%d %d %d %d\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d%d%d",&n,&m,&a,&b),S=2*n*m,T=S+1,memset(head,-1,sizeof(head));
	int dx[4]={a,b,b,a},dy[4]={-b,-a,a,b};
	for(int i=0;i<n;i++)scanf("%s",s[i]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		if(s[i][j]=='x')continue;
		ae(S,i*m+j,1,0);
		ae(i*m+j,T,1,1);
		ae(n*m+i*m+j,T,1,0);
		for(int k=0;k<4;k++){
			int ii=i+dx[k],jj=j+dy[k];
			if(ii<0||ii>=n||jj<0||jj>=m||s[ii][jj]=='x')continue;
	//		printf("(%d,%d):(%d,%d)\n",i,j,ii,jj);
			ae(i*m+j,n*m+ii*m+jj,1,0);
		}
	}
	while(SPFA());
//	for(int i=0;i<cnt;i++)if(edge[i^1].val&&edge[i^1].to<n*m)printf("%d->%d\n",edge[i^1].to,edge[i].to);
	printf("%d\n",cost);
	return 0;
} 

CII.[NOI2015]小园丁与老司机

首先,老司机部分考虑DP:明显 \(y\) 值不同的状态间转移是有阶段性的,但是 \(y\) 值相同时则不然;于是为了凸显阶段性,我们设 \(f_x\) 表示从位置 \(x\) 进入某个 \(y\) 值的最大收益,\(g_x\) 表示从位置 \(x\) 离开某个 \(y\) 值的最大收益。

\(g\rightarrow f\) 的转移(即为不同 \(y\) 间的转移)就直接从左上、上、右上三个方向转移即可,这部分是简单的;关键是 \(f\rightarrow g\),即同一 \(y\) 间的转移。因为保证每个 \(y\) 值的树不会太多,所以可以直接 \(n^2\) 地枚举所有转移对进行转移即可。

记录路径,我们就完成了老司机部分。

然后是小园丁部分。为了找到所有最优路径的并,我们需要建出图来,每个 \(f_x,g_x\) 各独立作一个节点(因此,总节点数应为 \(2n+2\) 个),在可能出现的位置连边,并且从每个最优的终局节点反向推出所有合法的边。这样,我们便可以建出一张所有需要经过的边所构成的DAG。

此DAG上,每条边被经过的下限是 \(1\),上限是 \(\infty\)(实际应用中取 \(3n\) 即可,因为最多只会有不超过 \(3n\) 条边)。于是我们建图跑上下界最小流即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=50100;
namespace MaxFlow{
    const int M=2000000;
    int head[N],cur[N],dep[N],cnt,S,T,s,t,ans,deg[N];
    struct node{
        int to,next,val;
    }edge[M];
    void ae(int u,int v,int w){
        edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
        edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
    void AE(int u,int v,int l,int r){
//    	printf("%d %d [%d,%d]\n",u,v,l,r);
    	deg[v]+=l,deg[u]-=l;
        edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=r-l,head[u]=cnt++;
        edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
    }
    queue<int>q;
    inline bool bfs(){
        memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
        while(!q.empty()){
            register int x=q.front();q.pop();
            for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
        }
        return dep[T]>0;
    }
    bool reach;
    inline int dfs(int x,int flow){
        if(x==T){
            ans+=flow;
            reach=true;
            return flow;
        }
        int used=0;
        for(register int &i=cur[x];i!=-1;i=edge[i].next){
            if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
            register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
            if(ff){
                edge[i].val-=ff;
                edge[i^1].val+=ff;
                used+=ff;
                if(used==flow)break;
            }
        }
        return used;
    }
    inline void Dinic(){
        while(bfs()){
            reach=true;
            while(reach)reach=false,dfs(S,0x3f3f3f3f);
        }
    }
}
using namespace MaxFlow;
int n,x[N],y[N],nw[N],ne[N],f[N],g[N],F[N],G[N],res;//f:maximal when arriving at i  g:maximal when leaving from i
vector<int>v[N],u[N],a;
void discrete(int *arr){
	a.clear();
	for(int i=0;i<=n;i++)a.push_back(arr[i]);
	sort(a.begin(),a.end()),a.resize(unique(a.begin(),a.end())-a.begin());
	for(int i=0;i<=n;i++)u[arr[i]=lower_bound(a.begin(),a.end(),arr[i])-a.begin()].push_back(i);
}
void buildgraph(){
	for(int i=0;i<a.size();i++){
		sort(u[i].begin(),u[i].end(),[](int X,int Y){return y[X]<y[Y];});
		for(int j=1;j<u[i].size();j++)v[u[i][j-1]].push_back(u[i][j]);
		u[i].clear();
	}
}
bool cmp(int X,int Y){return x[X]<x[Y];}
stack<int>st;
bool lim[N<<1],vis[N<<1];//if position i is able to reach maximum
vector<int>w[N<<1];
bool dfscheck(int x){
	if(vis[x])return lim[x];vis[x]=true;
	for(auto y:w[x])if(dfscheck(y)){
		lim[x]=true;
		if(x>n)AE(x-n-1,y,1,3*n);
	}
	return lim[x];
}
int main(){
	scanf("%d",&n),memset(f,-1,sizeof(f)),memset(g,-1,sizeof(g)),memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]),nw[i]=x[i]+y[i],ne[i]=x[i]-y[i];
	discrete(nw),buildgraph();
	discrete(ne),buildgraph();
	discrete(x),buildgraph();
//	for(int i=0;i<=n;i++){for(auto j:v[i])printf("%d ",j);puts("");}
	discrete(y),f[0]=0;
	for(int i=0;i<a.size();i++){
		sort(u[i].begin(),u[i].end(),cmp); 
		for(int j=0;j<u[i].size();j++)if(f[u[i][j]]!=-1)for(int k=0;k<u[i].size();k++){
			int now=f[u[i][j]];
			if(j<k)now+=k;
			if(j>k)now+=u[i].size()-k-1;
			if(g[u[i][k]]<=now)g[u[i][k]]=now,G[u[i][k]]=u[i][j];
		}
		for(auto j:u[i])if(g[j]!=-1)for(auto k:v[j])if(f[k]<=g[j]+1)f[k]=g[j]+1,F[k]=j;
	}
	for(int i=0;i<=n;i++)res=max(res,g[i]);
	printf("%d\n",res);
	for(int i=0;i<=n;i++){
		if(g[i]!=res)continue;
		while(i){
			st.push(i);
			int P=y[i];
			int I=lower_bound(u[P].begin(),u[P].end(),i,cmp)-u[P].begin(),J=lower_bound(u[P].begin(),u[P].end(),G[i],cmp)-u[P].begin();
			if(J<I){
				for(int k=I-1;k>J;k--)st.push(u[P][k]);
				for(int k=0;k<=J;k++)st.push(u[P][k]);
			}
			if(J>I){
				for(int k=I+1;k<J;k++)st.push(u[P][k]);
				for(int k=u[P].size()-1;k>=J;k--)st.push(u[P][k]);
			}
			i=F[G[i]];
		}
		break;
	}
	while(!st.empty())printf("%d ",st.top()),st.pop();puts("");
	s=n+1,t=n+2,S=n+3,T=n+4;
	for(int i=0;i<a.size();i++){
		for(int j=0;j<u[i].size();j++)if(f[u[i][j]]!=-1)for(int k=0;k<u[i].size();k++){
			int now=f[u[i][j]];
			if(j<k)now+=k;
			if(j>k)now+=u[i].size()-k-1;
			if(g[u[i][k]]==now)w[u[i][j]].push_back(u[i][k]+n+1);
		}
		for(auto j:u[i])if(g[j]!=-1)for(auto k:v[j])if(f[k]==g[j]+1)w[j+n+1].push_back(k);
	}
	for(int i=0;i<=n;i++)if(g[i]==res)lim[i+n+1]=true;
	dfscheck(0);
	for(int i=0;i<=n;i++)ae(s,i,3*n),ae(i,t,3*n);
	for(int i=0;i<=n;i++)if(deg[i]>0)ae(S,i,deg[i]);else ae(i,T,-deg[i]);
	Dinic();
	ae(t,s,3*n);
	Dinic();
	printf("%d\n",edge[cnt-1].val);
	return 0;
}

CIII.[SCOI2014]方伯伯运椰子

首先,对于题目中的一条边 \((u,v,a,b,c,d)\),若其容量增大 \(1\),则相当于有 \(b+d\) 的费用;减小 \(1\),则相当于有 \(a-d\) 的费用。因为一开始所有点都是收支平衡的(除了源点和汇点;但是源点因为保证无论如何流出的流量都不会变,汇点同理,所以也可以把它们看作收支平衡),故经历修改后它们仍然收支平衡。于是,上述增大或减小的容量,就相当于增大或减小的流量,仍然要使所有点平衡。

于是我们连边 \((u,v,\infty,b+d)\) 代表增大的容量,\((v,u,c,a-d)\) 代表减小的容量,则所有东西叠加起来的效果,应该仍相当于一个无源汇可行流。

现在我们看向我们的答案,会发现它应是一个0/1分数规划的形式;故我们二分答案为 \(T\),然后check时将所有边权都加上 \(T\)。假如跑出来最小费用可行流的结果为负,则二分结果合法,否则不合法。

但是,最小费用可行流的求法是比较非主流的——因为有负环。所谓的消圈算法非常罕见,更好的方式是对于负边,强制将其满流并让其反向(使用上下界网络流的科技)。

代码:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-3;
const double inf=0x3f3f3f3f;
namespace MCMF{
	const int N=5100,M=2000000;
	int head[N],cnt,fr[N],id[N],S,T,deg[N],flow;
	double dis[N],cost;
	struct node{
		int to,next,val;
		double cost;
	}edge[M];
	void ae(int u,int v,int w){
//		printf("ae:%d %d %d\n",u,v,w);
		edge[cnt].cost=0,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=0,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w,double c){
//		printf("AE:%d %d %d %lf\n",u,v,w,c);
		if(c<0)cost+=c*w,deg[v]+=w,deg[u]-=w,swap(u,v),c=-c;
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		for(int i=1;i<=T;i++)dis[i]=inf;dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
//			printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
//				printf("(%d,%d,%d,%lf)\n",x,edge[i].to,edge[i].val,edge[i].cost);
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==inf)return false;
//		printf("DEST:%d\n",dis[T]);
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,flow+=mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int n,m,lim;
struct EDGE{
	int u,v,a,b,c,d;
	void read(){scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&c,&d);}
}e[3010];
bool che(double now){
	memset(head,-1,sizeof(head)),cnt=flow=0,cost=0;
//	printf("%lf:\n",now);
	for(int i=1;i<=m;i++){
		AE(e[i].u,e[i].v,lim,e[i].b+e[i].d+now);
		AE(e[i].v,e[i].u,e[i].c,e[i].a-e[i].d+now);
	}
	int sum=0;
	for(int i=1;i<=n;i++){
//		printf("%d\n",deg[i]);
		if(deg[i]>0)ae(S,i,deg[i]),sum+=deg[i];
		if(deg[i]<0)ae(i,T,-deg[i]);
		deg[i]=0;
	}
	while(SPFA());
	return sum==flow&&cost<0;
}
int main(){
	scanf("%d%d",&n,&m),n++,S=n+1,T=n+2;
	for(int i=1;i<=m;i++){
		e[i].read();
		if(e[i].u==n||e[i].v==n){
			if(e[i].u==n)lim=e[i].c;
			i--,m--;continue;
		}
		if(e[i].u==n+1)e[i].u=n;
		if(e[i].v==n+1)e[i].v=n;
	}
	double l=0,r=2000;
	while(r-l>eps){
		double mid=(l+r)/2;
		if(che(mid))l=mid;
		else r=mid;
	}
	printf("%.2lf\n",l);
	return 0;
}

CIV.[POI2005]KOS-Dicing

“最大值最小”条件反射二分,于是就从源点连向每个人(二分的值)点流量,然后对于每场比赛,从两个人那里各连来一点流量,再连到汇点一点流量,然后跑最大流check是否满流即可。

犯了很多低级错误啊

代码:

#include<bits/stdc++.h>
using namespace std;
namespace MaxFlow{
	const int N=21000,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{
		int to,next,val;
	}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,0x3f3f3f3f);
		}
	}
}
using namespace MaxFlow;
int n,m,u[10100],v[10100];
bool che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int i=1;i<=n;i++)ae(S,i,ip);
	for(int i=1;i<=m;i++)ae(u[i],i+n,1),ae(v[i],i+n,1),ae(i+n,T,1);
	Dinic();
	return res==m;
}
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,T=n+m+2;
	for(int i=1;i<=m;i++)scanf("%d%d",&u[i],&v[i]);
	int l=0,r=m;
	while(l<r){
		int mid=(l+r)>>1;
		if(che(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",r);
	che(r);
	for(int i=1;i<=m;i++)for(int j=head[n+i];j!=-1;j=edge[j].next)if(edge[j].to<=n&&edge[j].val)puts(edge[j].to==u[i]?"1":"0");
	return 0;
}

CV.[CQOI2017]老C的方块

题解

CVIII.[CTSC2001]终极情报网

题解

CIX.[SDOI2011]保密

经 典 多 合 一

”总时间和总安全系数的比值“,条件反射0/1分数规划

然后,因为要求出所有点的值,条件反射用整体二分(虽然实际上这是我第一次写整体二分,但写起来还是挺简单的)。二分过程中要求出全体点的距离,因为保证DAG,直接拓扑一下即可。

于是我们求出了所有点的值。

于是问题转换为二分图最小权点覆盖问题。

二分图最小权点覆盖问题可以使用最小割解决。因为每条边的两个端点 \(u,v\) 中至少有一个在选取的点集内,抽象到最小割上就是 \(S\rightarrow u,u\rightarrow v,v\rightarrow T\) 三条边中至少断一条。我们又不想让 \(u\rightarrow v\) 这条边断掉,所以将其赋为 \(\infty\),另外两条边边权赋作该点点权即可。

时间复杂度 \(O\Big((m+n)n'\log val+m'\sqrt{n'}\Big)\),其中 \(val\) 是第一问中边权大小,加 \('\) 的来自于二分图,不加 \('\) 的来自于原图。

代码:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-5;//eps of binary search
const double EPS=1e-8;//eps of comparasion between doubles 
double mn[210];
int nbi,mbi;
namespace FS{
	int n,m;
	double dis[710];
	int arr[210],head[710],cnt,in[710];
	struct node{int to,next;double val;}edge[200100];
	struct EDGE{
		int u,v,s,t;
		void read(){scanf("%d%d%d%d",&u,&v,&t,&s);}
	}e[100100];
	void ae(int u,int v,double w){edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;}
	bool vis[710];
	void dfs(int x){
		if(vis[x])return;vis[x]=true;
		for(int i=head[x];i!=-1;i=edge[i].next)dfs(edge[i].to);
	}
	queue<int>q;
	void bfs(){
		for(int i=1;i<=n;i++)dis[i]=0x3f3f3f3f;dis[n]=0,q.push(n);
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=head[x];i!=-1;i=edge[i].next){
				dis[edge[i].to]=min(dis[edge[i].to],dis[x]+edge[i].val);
				if(!--in[edge[i].to])q.push(edge[i].to);
			}
		}
	}
	void solve(int L,int R,double l,double r){
		if(L>R)return;
		if(r-l<eps){for(int i=L;i<=R;i++)mn[arr[i]]=l;return;}
		double mid=(l+r)/2;
		memset(head,-1,sizeof(head)),cnt=0;
		for(int i=1;i<=m;i++)if(vis[e[i].u]&&vis[e[i].v])ae(e[i].u,e[i].v,e[i].t-mid*e[i].s),in[e[i].v]++;
		bfs();
//		printf("%lf:\n",mid);
//		for(int i=1;i<=n;i++)printf("%lf ",dis[i]);puts("");
		sort(arr+L,arr+R+1,[](int u,int v){return dis[u]<dis[v];});
//		for(int i=L;i<=R;i++)printf("%d ",arr[i]);puts("");
		int pos=L;while(pos<=R&&dis[arr[pos]]<-EPS)pos++;
//		printf("%d\n",pos);
		solve(L,pos-1,l,mid),solve(pos,R,mid,r);
	}
	void init(){
		scanf("%d%d",&n,&m),memset(head,-1,sizeof(head));
		for(int i=1;i<=m;i++)e[i].read(),ae(e[i].u,e[i].v,0);
		dfs(n);
//		for(int i=1;i<=m;i++)printf("%d %d (%d/%d)\n",e[i].u,e[i].v,e[i].t,e[i].s);
		scanf("%d%d",&mbi,&nbi);
		int tot=0;
		for(int i=1;i<=nbi;i++)if(vis[i])arr[++tot]=i;else mn[i]=-1;
		solve(1,tot,0,10);
	}
}
namespace DD{
	const int N=210,M=400000;
	int head[N],cur[N],dep[N],cnt,S,T;
	double res;
	struct node{
		int to,next;
		double val;
	}edge[M];
	void ae(int u,int v,double w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val>EPS&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline double dfs(int x,double flow){
		if(x==T){
			res+=flow;
			reach=true;
			return flow;
		}
		double used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(edge[i].val<EPS||dep[edge[i].to]!=dep[x]+1)continue;
			register double ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){
				edge[i].val-=ff;
				edge[i^1].val+=ff;
				used+=ff;
				if(used==flow)break;
			}
		}
		return used;
	}
	inline void Dinic(){
		while(bfs()){
			reach=true;
			while(reach)reach=false,dfs(S,1e9);
		}
	}
}
using namespace DD;
int main(){
	FS::init(),S=nbi+1,T=nbi+2,memset(head,-1,sizeof(head));
//	for(int i=1;i<=nbi;i++)printf("%.1lf ",mn[i]);puts("");
	for(int i=1,x,y;i<=mbi;i++){
		scanf("%d%d",&x,&y);
		if(mn[x]<-EPS&&mn[y]<-EPS){puts("-1");return 0;}
		ae(x,y,0x3f3f3f3f);
	}
	for(int i=1;i<=nbi;i++){
		if(mn[i]<-EPS)mn[i]=0x3f3f3f3f;
		if(i&1)ae(S,i,mn[i]);
		else ae(i,T,mn[i]);
	}
	Dinic();
	printf("%.1lf\n",res);
	return 0;
}

CX.[JLOI2014]镜面通道

凭直觉可以发现,若两端间有一条可以通过的路径,则光一定可以射过去。

证明?OI不需要证明

于是我们把所有相交的图形间连边,就得到了一张无向图,而我们需要删去一些边保证无向图中上边和下边所对应的点不连通。

于是拆点跑最小割即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define y1 __17680321
namespace MaxFlow{
	const int N=610,M=2000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){edge[i].val-=ff,edge[i^1].val+=ff,used+=ff;if(used==flow)break;}
		}
		return used;
	}
	inline void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace MaxFlow;
struct Vector{
	int x,y;
	Vector(){}
	Vector(int X,int Y){x=X,y=Y;}
	friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
	friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
	ll operator ~()const{return 1ll*x*x+1ll*y*y;}//the modulo of a vector
	void read(){scanf("%d%d",&x,&y);}
	void print(){printf("(%d,%d)",x,y);}
};
int X,Y,n,tp[310];
typedef Vector Point;
struct Rect{
	int x1,y1,x2,y2;
	Point LD,LU,RD,RU;
	void read(){
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		LD=Point(x1,y1),LU=Point(x1,y2),RD=Point(x2,y1),RU=Point(x2,y2);
	}
}r[310];
struct Circ{
	int x,y,r;
	Point O;
	void read(){scanf("%d%d%d",&x,&y,&r);O=Point(x,y);}
}c[310];
bool Inter(Rect u,Rect v){
	return !(u.x2<v.x1||u.x1>v.x2||u.y2<v.y1||u.y1>v.y2);
}
bool Inter(Circ u,Circ v){
	return ~(u.O-v.O)<=1ll*(u.r+v.r)*(u.r+v.r);
}
bool Inter(Rect u,Circ v){
	if(~(u.LD-v.O)<=1ll*v.r*v.r)return true;
	if(~(u.LU-v.O)<=1ll*v.r*v.r)return true;
	if(~(u.RD-v.O)<=1ll*v.r*v.r)return true;
	if(~(u.RU-v.O)<=1ll*v.r*v.r)return true;
	return !(u.x1>v.x+v.r||u.x2<v.x-v.r||u.y1>v.y+v.r||u.y2<v.y-v.r);
}
bool Inter(Rect u,int ip){
	if(ip==1)return u.y2>=Y;
	if(ip==-1)return u.y1<=0;
}
bool Inter(Circ u,int ip){
	if(ip==1)return u.y+u.r>=Y;
	if(ip==-1)return u.y-u.r<=0;
}
int main(){
	scanf("%d%d%d",&X,&Y,&n),S=2*n+1,T=2*n+2,memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++){
		ae(i,i+n,1);
		scanf("%d",&tp[i]);
		if(tp[i]==1){
			c[i].read();
			if(Inter(c[i],1))ae(i+n,T,0x3f3f3f3f);
			if(Inter(c[i],-1))ae(S,i,0x3f3f3f3f);
		}else{
			r[i].read();
			if(Inter(r[i],1))ae(i+n,T,0x3f3f3f3f);
			if(Inter(r[i],-1))ae(S,i,0x3f3f3f3f);
		}
	}
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++){
		if(tp[i]==1&&tp[j]==1&&Inter(c[i],c[j]))ae(i+n,j,0x3f3f3f3f),ae(j+n,i,0x3f3f3f3f);
		if(tp[i]==2&&tp[j]==2&&Inter(r[i],r[j]))ae(i+n,j,0x3f3f3f3f),ae(j+n,i,0x3f3f3f3f);
		if(tp[i]==1&&tp[j]==2&&Inter(r[j],c[i]))ae(i+n,j,0x3f3f3f3f),ae(j+n,i,0x3f3f3f3f);
		if(tp[i]==2&&tp[j]==1&&Inter(r[i],c[j]))ae(i+n,j,0x3f3f3f3f),ae(j+n,i,0x3f3f3f3f);
	}
	Dinic();
	printf("%d\n",res);
	return 0;
}

CXI.[POI2010]MOS-Bridges

“最大值最小”先套个二分罢。问题转换为判断一张有向图上是否存在欧拉回路。

有向图上存在欧拉回路,当且仅当所有点的入度等于出度。但是本题因为有无向边的存在,不能简单判断。

但我们可以先强制定下无向边的方向,同时给它反悔的机会。

具体而言,我们先强制定方向得到一张有向图,此时可能有点的出入度不平衡。对于入度多的点,我们从 \(S\) 连来多的流量;对于出度多的点,我们连到 \(T\) 相应的流量;然后可以反悔的边,就相当于连接两点的度数为 \(2\) 的有向边。(消除原本的度数影响+其本身的度数影响,一共是 \(2\) 点)

于是按照这种建图方式,我们发现若一个点的出入度奇偶不同,则一定不合法(因为反悔不会改变出入度的奇偶性);然后,我们就可以为了避免边的流量不跑满而将所有边权除以 \(2\)。然后判断这张图的最大流是否满流即可。

当二分结束求出答案后,每条边的方向也就可以确定了,我们便得到了一张有向欧拉图。求出上面的欧拉回路即可。

因为本人当初学欧拉回路时就没有好好学,所以这里额外扯一句欧拉回路——它可以通过以链表记录边,然后合并大回路和小回路得到(这就是必须用链表的原因)。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
struct EDGE{
	int u,v,s,t;
	void read(){scanf("%d%d%d%d",&u,&v,&s,&t);}
}e[20100];
namespace MaxFlow{
	const int N=1010,M=200000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){edge[i].val-=ff,edge[i^1].val+=ff,used+=ff;if(used==flow)break;}
		}
		return used;
	}
	inline void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace MaxFlow;
int in[N];
bool che(int ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int i=1;i<=n;i++)in[i]=0;
	for(int i=1;i<=m;i++){
		if(e[i].s<=ip&&e[i].t<=ip)in[e[i].u]++,in[e[i].v]--,ae(e[i].u,e[i].v,1);
		else if(e[i].s<=ip)in[e[i].u]++,in[e[i].v]--;
		else if(e[i].t<=ip)in[e[i].u]--,in[e[i].v]++;
		else return false;
	}
	for(int i=1;i<=n;i++)if(in[i]&1)return false;
	int sum=0;
	for(int i=1;i<=n;i++){
		in[i]/=2;
		if(in[i]>0)ae(S,i,in[i]),sum+=in[i];
		if(in[i]<0)ae(i,T,-in[i]);
	}
	Dinic();
	return sum==res;
}
vector<pair<int,int> >v[N];
set<pair<int,int> >s;
int nex[20100];
pair<int,int>dfs(int x){
	if(v[x].empty())return make_pair(-1,-1);
	int id=v[x].back().first,y=v[x].back().second;v[x].pop_back();
	pair<int,int>tmp=dfs(y);
	nex[id]=tmp.first;
	if(tmp.second==-1)tmp.second=id;
	while(!v[x].empty()){
		int nid=v[x].back().first,ny=v[x].back().second;v[x].pop_back();
		pair<int,int>ntmp=dfs(ny);
		if(ntmp.second==-1)ntmp.second=nid;
		nex[ntmp.second]=id;
		nex[nid]=ntmp.first;
		id=nid;
	}
	return make_pair(id,tmp.second);
}
int main(){
	scanf("%d%d",&n,&m),S=n+1,T=n+2;
	for(int i=1;i<=m;i++)e[i].read(),in[e[i].u]++,in[e[i].v]++;
	for(int i=1;i<=n;i++)if(in[i]&1){puts("NIE");return 0;}
	int l=1,r=1000;
	while(l<r){
		int mid=(l+r)>>1;
		if(che(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",r);
	che(r);
	for(int x=1;x<=n;x++)for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].to<=n&&edge[i].val)s.insert(make_pair(x,edge[i].to));
	for(int i=1;i<=m;i++){
		if(e[i].s<=r&&e[i].t<=r){
			if(s.find(make_pair(e[i].u,e[i].v))!=s.end())v[e[i].u].push_back(make_pair(i,e[i].v));
			if(s.find(make_pair(e[i].v,e[i].u))!=s.end())v[e[i].v].push_back(make_pair(i,e[i].u));
		}
		else if(e[i].s<=r)v[e[i].u].push_back(make_pair(i,e[i].v));
		else if(e[i].t<=r)v[e[i].v].push_back(make_pair(i,e[i].u));
	}
	int x=dfs(1).first;
	while(x!=-1)printf("%d ",x),x=nex[x];puts("");
	return 0;
} 

CXII.[BJWC2018]Kakuro

首先模型是可以建出来的:源点连到所有“右上角的数字”,右上角的数字连到所有与它有交的左下角数字,左下角数字再连到汇点。

我们来进一步完善这个系统。首先,上述三条边应该全部强制让它满流。同时,为了使收支平衡,我们还可以连如下一些边:

  1. 源点到右上角及其反边,对应着修改右上角的值。需要注意的是,反边的权值上限小于右上角的数字,因为须保证非负。

  2. 汇点到左下角及其反边。同上。

  3. 右上到左下及其反边。同上。

代码中已经手动合并了源点和汇点,因此只能见到一个合并后的点 \(O\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,num,tp[50][50],id[50][50][2],val[50][50][3],cst[50][50][3];//0:downwards 1:rightwards
namespace MCMF{
	const int N=1010,M=2000000;
	int head[N],cnt,dis[N],fr[N],S,T,deg[N],flow;
	ll cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){//add a single directed edge, without lower limit
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void AE(int u,int v,int w){//add a must-full directed edge
		deg[u]-=w,deg[v]+=w;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[fr[x]].val),x=edge[fr[x]^1].to;
		cost+=1ll*dis[T]*mn,flow+=mn,x=T;
		while(x!=S)edge[fr[x]].val-=mn,edge[fr[x]^1].val+=mn,x=edge[fr[x]^1].to;
		return true;
	}
}
using namespace MCMF;
int O;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		scanf("%d",&tp[i][j]);
		if(tp[i][j]==1||tp[i][j]==3)id[i][j][0]=++num;
		if(tp[i][j]==2||tp[i][j]==3)id[i][j][1]=++num;
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		if(tp[i][j]==1||tp[i][j]==3)scanf("%d",&val[i][j][0]);
		if(tp[i][j]==2||tp[i][j]==3)scanf("%d",&val[i][j][1]);
		if(tp[i][j]==4)scanf("%d",&val[i][j][2]);
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		if(tp[i][j]==1||tp[i][j]==3)scanf("%d",&cst[i][j][0]);
		if(tp[i][j]==2||tp[i][j]==3)scanf("%d",&cst[i][j][1]);
		if(tp[i][j]==4)scanf("%d",&cst[i][j][2]);
	}
//	for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)printf("(%d,%d)",id[i][j][0],id[i][j][1]);puts("");}puts("");
//	for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)printf("(%d,%d)",val[i][j][0],val[i][j][1]);puts("");}puts("");
//	for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)printf("(%d,%d)",cst[i][j][0],cst[i][j][1]);puts("");}puts("");
	O=num+1,S=num+2,T=num+3;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		if(id[i][j][0]){
			AE(O,id[i][j][0],val[i][j][0]);
			if(cst[i][j][0]!=-1)ae(O,id[i][j][0],0x3f3f3f3f,cst[i][j][0]),ae(id[i][j][0],O,val[i][j][0]-1,cst[i][j][0]);
		}
		if(id[i][j][1]){
			AE(id[i][j][1],O,val[i][j][1]);
			if(cst[i][j][1]!=-1)ae(id[i][j][1],O,0x3f3f3f3f,cst[i][j][1]),ae(O,id[i][j][1],val[i][j][1]-1,cst[i][j][1]);
		}
		if(tp[i][j]==4){
			int I=i,J=j;
			while(!id[I][j][0])I--;
			while(!id[i][J][1])J--;
			AE(id[I][j][0],id[i][J][1],val[i][j][2]);
			if(cst[i][j][2]!=-1)ae(id[I][j][0],id[i][J][1],0x3f3f3f3f,cst[i][j][2]),ae(id[i][J][1],id[I][j][0],val[i][j][2]-1,cst[i][j][2]);
		}
	}
	int sum=0;
	for(int i=1;i<=O;i++){
		if(deg[i]>0)ae(S,i,deg[i],0),sum+=deg[i];
		if(deg[i]<0)ae(i,T,-deg[i],0);
	}
	while(SPFA());
	if(sum!=flow)puts("-1");else printf("%lld\n",cost);
	return 0;
}

CXIV.[TJOI2013]循环格

明显我们可以把“箭头”看作一个格子向另一个格子的有向边,则整张图构成一个内向树森林。

而“完美循环格”,则相当于内向树全部退化为环。

考虑环的特征,发现是所有点的入度和出度都为 \(1\)

于是其可以被抽象为两个点匹配,于是连出边来,需要旋转得到的边费用为 \(1\),不需旋转的边费用为 \(0\),然后跑最小费用二分图匹配即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
char s[20][20];
namespace MCMF{
	const int N=1000,M=20000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d",&n,&m),S=2*n*m,T=2*n*m+1,memset(head,-1,sizeof(head));
	for(int i=0;i<n;i++)scanf("%s",s[i]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		ae(S,i*m+j,1,0),ae(n*m+i*m+j,T,1,0);
		int dr;
		if(s[i][j]=='D')dr=0;
		if(s[i][j]=='R')dr=1;
		if(s[i][j]=='U')dr=2;
		if(s[i][j]=='L')dr=3;
		for(int k=0;k<4;k++)ae(i*m+j,n*m+(i+dx[k]+n)%n*m+(j+dy[k]+m)%m,1,k!=dr);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

CXV.[CEOI2008]order

我们如果考虑只有一件任务,一台机器,一道工序,应该怎样建图?

我们首先考虑源点连到机器,任务连到汇点——因为这两个东西都是多对一的。然后就把工序看作是连接二者的边。

考虑用最小割解决问题,则问题转换为三段中必须切断一个——源点到机器,意味着买了台机器;机器到任务,意味着租了台机器;任务到汇点,意味着放弃该任务。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace MaxFlow{
	const int N=2500,M=5000000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){edge[i].val-=ff,edge[i^1].val+=ff,used+=ff;if(used==flow)break;}
		}
		return used;
	}
	inline void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace MaxFlow;
int n,m;
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,T=n+m+2,memset(head,-1,sizeof(head));
	int sum=0;
	for(int i=1,a,b,c,d;i<=n;i++){
		scanf("%d%d",&a,&b),sum+=a;
		ae(m+i,T,a);
		while(b--)scanf("%d%d",&c,&d),ae(c,m+i,d);
	}
	for(int i=1,x;i<=m;i++)scanf("%d",&x),ae(S,i,x);
	Dinic();
	printf("%d\n",sum-res);
	return 0;
}

CXVI.CF802C Heidi and Library (hard)

建图随便建,拆个点完事。

限制每次请求的书直接上下界网络流随便搞一下就行。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,c[20100];
namespace MCMF{
	int head[20100],cnt,dis[20100],fr[20100],id[20100],S,T,cost,deg[20100];
	struct node{
		int to,next,val,cost;
	}edge[4010000];
	void ae(int u,int v,int w,int c){
//		printf("%d %d (%d,%d)\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	void ae(int u,int v){deg[v]++,deg[u]--;}
	queue<int>q;
	bool in[20100];
	bool SPFA(){
		memset(dis,0x3f,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]>dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}	
}
using namespace MCMF;
int main(){
	scanf("%d%d",&n,&m),s=2*n*n+n,t=2*n*n+n+1,S=2*n*n+n+2,T=2*n*n+n+3,memset(head,-1,sizeof(head));
	for(int i=0,x;i<n;i++){
		scanf("%d",&x),x--;
		for(int j=0;j<n;j++)if(j==x)ae(i*n+j,n*n+i*n+j);else ae(i*n+j,n*n+i*n+j,1,0);
		for(int j=0;j<n;j++)if(i+1<n)ae(n*n+i*n+j,(i+1)*n+j,1,0);else ae(n*n+i*n+j,t,1,0);
	}
	for(int i=0;i<n;i++)scanf("%d",&c[i]);
	ae(s,2*n*n,m,0);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)ae(2*n*n+i,i*n+j,1,c[j]);
		if(i)for(int j=0;j<n;j++)ae(n*n+(i-1)*n+j,2*n*n+i,1,0);
	}
	ae(t,s,0x3f3f3f3f,0);
	for(int i=0;i<=t;i++){
		if(deg[i]>0)ae(S,i,deg[i],0);
		if(deg[i]<0)ae(i,T,-deg[i],0);
	}
	while(SPFA());
	printf("%d\n",cost);
	return 0;
}

CXVII.CF491C Deciphering

随便建图跑费用流即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,g[52][52];
int HASH(char i){
	if('a'<=i&&i<='z')return i-'a';
	if('A'<=i&&i<='Z')return 26+i-'A';
}
char HASH(int i){
	if(i<26)return 'a'+i;
	if(i<52)return 'A'+i-26;
}
char s[2001000],t[2001000];
namespace MCMF{
	const int N=200,M=200000;
	int head[N],cnt,dis[N],fr[N],id[N],S,T,cost;
	struct node{
		int to,next,val,cost;
	}edge[M];
	void ae(int u,int v,int w,int c){
	//	printf("%d %d %d %d\n",u,v,w,c);
		edge[cnt].cost=c,edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].cost=-c,edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool in[N];
	bool SPFA(){
		memset(dis,0xcf,sizeof(dis)),dis[S]=0,q.push(S),in[S]=true;
		while(!q.empty()){
			int x=q.front();q.pop(),in[x]=false;
	//		printf("%d\n",x);
			for(int i=head[x];i!=-1;i=edge[i].next){
				if(!edge[i].val)continue;
				if(dis[edge[i].to]<dis[x]+edge[i].cost){
					dis[edge[i].to]=dis[x]+edge[i].cost,fr[edge[i].to]=x,id[edge[i].to]=i;
					if(!in[edge[i].to])in[edge[i].to]=true,q.push(edge[i].to);
				}
			}
		}
		if(dis[T]==dis[T+1])return false;
		int x=T,mn=0x3f3f3f3f;
		while(x!=S)mn=min(mn,edge[id[x]].val),x=fr[x];
		cost+=dis[T]*mn,x=T;
		while(x!=S)edge[id[x]].val-=mn,edge[id[x]^1].val+=mn,x=fr[x];
		return true;
	}
}
using namespace MCMF;
int main(){
	scanf("%d%d%s%s",&n,&m,s,t),S=2*m,T=2*m+1,memset(head,-1,sizeof(head));
	for(int i=0;i<n;i++)g[HASH(s[i])][HASH(t[i])]++;
	for(int i=0;i<m;i++)ae(S,i,1,0),ae(i+m,T,1,0);
	for(int i=0;i<m;i++)for(int j=0;j<m;j++)ae(i,j+m,1,g[i][j]);
	while(SPFA());
	printf("%d\n",cost);
	for(int i=0;i<m;i++)for(int j=head[i];j!=-1;j=edge[j].next)if(edge[j].to>=m&&edge[j].to<2*m&&!edge[j].val)putchar(HASH(edge[j].to-m));
	return 0;
}

CXVIII.CF513F2 Scaygerboss

首先boss的性别可以根据男女的数量直接确定出来。之后套个二分,然后就是三分图匹配(一个男配一个女再配一个位置),随便拆个点然后最大流check就行了。

需要注意的是本题比较卡常,不能把东西开得太大。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,s,t,X[1010],Y[1010],Z[1010],dis[50][50][50][50],dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
char g[50][50];
queue<pair<int,int> >q;
void bfs(int xx,int yy){
	dis[xx][yy][xx][yy]=0,q.push(make_pair(xx,yy));
	while(!q.empty()){
		int x=q.front().first,y=q.front().second;q.pop();
		for(int i=0;i<4;i++){
			if(x+dx[i]>n||y+dy[i]>m||x+dx[i]<1||y+dy[i]<1||g[x+dx[i]][y+dy[i]]=='#'||dis[xx][yy][x+dx[i]][y+dy[i]]!=-1)continue;
			dis[xx][yy][x+dx[i]][y+dy[i]]=dis[xx][yy][x][y]+1,q.push(make_pair(x+dx[i],y+dy[i]));
		}
	}
}
namespace MaxFlow{
	const int N=2010,M=2001000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
//		printf("%d %d %d\n",u,v,w);
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){edge[i].val-=ff,edge[i^1].val+=ff,used+=ff;if(used==flow)break;}
		}
		return used;
	}
	inline void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace MaxFlow;
bool che(ll ip){
	memset(head,-1,sizeof(head)),cnt=res=0;
	for(int x=1;x<=n;x++)for(int y=1;y<=m;y++){
		for(int i=1;i<=s;i++){
			if(dis[X[i]][Y[i]][x][y]==-1)continue;
			if(1ll*dis[X[i]][Y[i]][x][y]*Z[i]>ip)continue;
			ae(2*n*m+i,(x-1)*m+y,1); 
		}
		for(int j=1;j<=t;j++){
			if(dis[X[s+j]][Y[s+j]][x][y]==-1)continue;
			if(1ll*dis[X[s+j]][Y[s+j]][x][y]*Z[s+j]>ip)continue;
			ae(n*m+(x-1)*m+y,2*n*m+s+j,1);			
		}
	}
	for(int i=1;i<=s;i++)ae(S,2*n*m+i,1);
	for(int i=1;i<=t;i++)ae(2*n*m+s+i,T,1);
	for(int x=1;x<=n;x++)for(int y=1;y<=m;y++)if(g[x][y]!='#')ae((x-1)*m+y,n*m+(x-1)*m+y,1);
	Dinic();
	return res==s;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t),memset(dis,-1,sizeof(dis));
	for(int i=1;i<=n;i++)scanf("%s",g[i]+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(g[i][j]!='#')bfs(i,j);
	if(abs(s-t)!=1){puts("-1");return 0;}
	if(s<t)scanf("%d%d%d",&X[s+1],&Y[s+1],&Z[s+1]);
	else scanf("%d%d%d",&X[s+t+1],&Y[s+t+1],&Z[s+t+1]);
	for(int i=1;i<=s;i++)scanf("%d%d%d",&X[i],&Y[i],&Z[i]);if(s<=t)s++;
	for(int i=1;i<=t;i++)scanf("%d%d%d",&X[s+i],&Y[s+i],&Z[s+i]);if(t<=s)t++;
	S=2*n*m+s+t+1,T=2*n*m+s+t+2;
	ll l=0,r=1e12;
	while(l<r){
		ll mid=(l+r)>>1;
		if(che(mid))r=mid;
		else l=mid+1;
	}
	if(!che(r))puts("-1");
	else printf("%lld\n",r);
	return 0;
}

CXX.CF786E ALT

题解

CXXI.CF903G Yet Another Maxflow Problem

在做完CXIX.CF434D Nanami's Power Plant后,我对这题也立刻来了灵感。

首先,题目要求最大流,想想看发现可以把它变成最小割。

然后,同Nanami那题一样,我们敏锐地发现 \(A_1\rightarrow A_n,B_1\rightarrow B_n\) 这两条路径上,最多只会被各割 \(1\) 条边。

于是我们考虑假如我们割掉了 \(A_i\rightarrow A_{i+1},B_{j}\rightarrow B_{j+1}\) 这两条边,还需要割掉什么边。

发现就是所有的 \(A_x\rightarrow B_y\),其中 \(x\geq i\land y<j\)

明显这是个二维数点问题。

因为对于每个 \(A_i\),不论 \(A_i\rightarrow A_{i+1}\) 的边权怎么变,能使其取得 \(\min\)\(B_j\) 是唯一的。于是我们可以直接使用线段树处理二维数点问题,然后就可以预处理出来上述最优的 \(j\) 的费用。然后使用一个 multiset 之类随便维护 \(A_i\rightarrow A_{i+1}\text{ 的边的边权}+\text{上述最优费用}\)\(\min\) 即可。

需要注意的是可能有不割 \(A\) 边或不割 \(B\) 边的情形。这时就可以看作割掉了 \(A_{n}\rightarrow A_{n+1}\)\(B_0\rightarrow B_1\) 的边,同上一致处理即可。

时间复杂度 \(O(n\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,q,a[200100],b[200100];
ll mn[200100];
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct SegTree{ll mn,tag;}seg[800100];
void ADD(int x,ll y){seg[x].mn+=y,seg[x].tag+=y;}
void pushup(int x){seg[x].mn=min(seg[lson].mn,seg[rson].mn);}
void pushdown(int x){ADD(lson,seg[x].tag),ADD(rson,seg[x].tag),seg[x].tag=0;}
void build(int x,int l,int r){
	if(l==r){seg[x].mn=b[l];return;}
	build(lson,l,mid),build(rson,mid+1,r),pushup(x); 
}
void modify(int x,int l,int r,int P,int val){
	if(l>=P)return;
	if(r<P){ADD(x,val);return;}
	pushdown(x),modify(lson,l,mid,P,val),modify(rson,mid+1,r,P,val),pushup(x);
}
vector<pair<int,int> >v[200100];
multiset<ll>s;
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<n;i++)scanf("%d%d",&a[i],&b[i]);
	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),v[x].push_back(make_pair(y,z));
	build(1,0,n-1);
	for(int i=1;i<=n;i++){
		for(auto j:v[i])modify(1,0,n-1,j.first,j.second);
		mn[i]=seg[1].mn,s.insert(mn[i]+a[i]);
	}
	printf("%lld\n",*s.begin());
	for(int i=1,x,y;i<=q;i++)scanf("%d%d",&x,&y),s.erase(s.find(mn[x]+a[x])),a[x]=y,s.insert(mn[x]+a[x]),printf("%lld\n",*s.begin());
	return 0;
}

CXXII.CF1430G Yet Another DAG Problem

Nanami永远的神(震声)!

因为模型也是一样的(有大小限制的变量取值),所以就直接考虑像Nanami一样做。

明显每个点的值域只要取在 \([0,n-1]\) 中即可一定满足条件。

于是首先先把每个点代表的链建出来。

一开始想的是,对于原图中连边的两个点,它们每一组取值的代价仅在连接两条链上点的边处计算,使用某些方法解出每条边的取值即可。但是这样发现,两条链上的取值对共有 \(\dfrac{n(n-1)}{2}\) 种,但是我们一共却仅有 \(\dfrac{n(n-2)}{2}\) 条可以用来组合的边,该线性方程组不一定有解。

然后不得不转换思路。发现收益 \(w_ib_i=w_i(a_u-a_v)=w_ia_u-w_ia_v\),然后就可以关于 \(u\) 求出所有上述的 \(w_i\)\(-w_i\) 之和,设为 \(s_u\),这样 \(u\) 总共的收益就是函数 \(a_us_u\),然后再加上那些限制,问题就被我们转换成了Nanami。

代码:

#include<bits/stdc++.h>
using namespace std;
const int lim=50000000;
int n,m,a[20];
namespace MaxFlow{
	const int N=201000,M=2001000;
	int head[N],cur[N],dep[N],cnt,S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
//		printf("%d %d %d\n",u,v,w);
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	inline bool bfs(){
		memset(dep,0,sizeof(dep)),q.push(S),dep[S]=1;
		while(!q.empty()){
			register int x=q.front();q.pop();
			for(register int i=cur[x]=head[x];i!=-1;i=edge[i].next)if(edge[i].val&&!dep[edge[i].to])dep[edge[i].to]=dep[x]+1,q.push(edge[i].to);
		}
		return dep[T]>0;
	}
	bool reach;
	inline int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(register int &i=cur[x];i!=-1;i=edge[i].next){
			if(!edge[i].val||dep[edge[i].to]!=dep[x]+1)continue;
			register int ff=dfs(edge[i].to,min(edge[i].val,flow-used));
			if(ff){edge[i].val-=ff,edge[i^1].val+=ff,used+=ff;if(used==flow)break;}
		}
		return used;
	}
	inline void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace MaxFlow;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n*n,T=n*n+1;
	for(int i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z),a[--x]+=z,a[--y]-=z;
		for(int j=1;j<n;j++)ae(y*n+(j-1),x*n+j,0x3f3f3f3f);
		ae(S,x*n,0x3f3f3f3f);
	}
	for(int i=0;i<n;i++){
		ae(S,i*n,lim);
		for(int j=1;j<n;j++)ae(i*n+(j-1),i*n+j,lim+a[i]*j);
		ae(i*n+n-1,T,0x3f3f3f3f);
	}
	Dinic();
//	printf("%d\n",res-lim*n);
	for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(!dep[i*n+j]){printf("%d ",j);break;}
	return 0;
}

CXXVII.[TJOI2010]电影迷

拿一道水题练练板子。

随便建图然后跑最小割即可,事老套路。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,sum;
namespace NetworkFlows{
	const int M=100100;
	int head[N],cur[N],cnt,dep[N],S,T,res;
	struct node{int to,next,val;}edge[M];
	void ae(int u,int v,int w){
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=0,head[v]=cnt++;
	}
	queue<int>q;
	bool bfs(){
		q.push(S),memset(dep,-1,sizeof(dep)),dep[S]=0;
		while(!q.empty()){int x=q.front();q.pop();for(int i=cur[x]=head[x],y;i!=-1;i=edge[i].next)if(dep[y=edge[i].to]==-1&&edge[i].val)dep[y]=dep[x]+1,q.push(y);}
		return dep[T]!=-1;
	}
	bool reach;
	int dfs(int x,int flow){
		if(x==T){res+=flow,reach=true;return flow;}
		int used=0;
		for(int &i=cur[x];i!=-1;i=edge[i].next){
			if(dep[edge[i].to]!=dep[x]+1||!edge[i].val)continue;
			int ff=dfs(edge[i].to,min(flow-used,edge[i].val));
			if(ff){used+=ff,edge[i].val-=ff,edge[i^1].val+=ff;if(used==flow)return used;}
		}
		return used;
	}
	void Dinic(){while(bfs()){reach=true;while(reach)reach=false,dfs(S,0x3f3f3f3f);}}
}
using namespace NetworkFlows;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),S=n+1,T=n+2;
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		if(x>0)sum+=x,ae(S,i,x);
		if(x<0)ae(i,T,-x);
	}
	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z);
	Dinic(),printf("%d\n",sum-res);
	return 0;
} 

CXXVIII.CF1510B Button Lock

题解

CXXIX.[ZJOI2011]营救皮卡丘

题解

posted @ 2021-04-06 13:00  Troverld  阅读(81)  评论(0编辑  收藏  举报