Raki的网络流24题题解总结

网络流24题:
from:Luogu

0.前言

网络流24题总体来说基本难度不大(混进了一些奇怪的东西),因为网络流的模型实在太多了,像我这种无法举一反三的菜鸡只能靠多做来积累

网络流题目的核心也是难点在于怎么将问题进行模型转化从而建图,在本文中我会解释每一题的心路历程,建图方式,以及为什么要这样建图,(作为一篇网络流题解不解释建图方式等于啥也没说)

1.P4011 孤岛营救问题

首先,这不是一个网络流题目

你要问我为什么的话我只能说:老婆饼里面也没有老婆!

题意:有一些钥匙散布在一些地方,有些地方有障碍,有的地方有门,需要拥有对应的钥匙才能通过这条门,一个地方可能有多把钥匙

思路:因为要保存钥匙的状态,开数组不方便并且有空间压力,所以状压,0-1 BFS即可

状态设置为:
x,y :坐标
key:当前拥有的钥匙
dis:离原点的距离

#include<bits/stdc++.h>
using namespace std;
const int maxn=20;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int e[maxn][maxn][maxn][maxn],key[maxn][maxn][maxn];
int cnt[maxn][maxn],vis[maxn][maxn][1<<15];
int n,m,k,p,s;
struct node{
	int x,y,key,dis;
};
inline int getkey(int x,int y){
	int ans=0;
	for(int i=1;i<=cnt[x][y];i++){
		ans|=(1<<(key[x][y][i]-1));
	}
	return ans;
}
inline int bfs(int sx,int sy){
	int skey=getkey(sx,sy);
	vis[sx][sy][skey]=1;
	queue<node>q;
	q.push((node){sx,sy,skey,0});
	while(!q.empty()){
		node u=q.front();
		q.pop();
		if(u.x==n&&u.y==m){
			return u.dis;
		}
		int nowkey=u.key;
		for(int i=0;i<4;i++){
			int xx=u.x+dx[i];
			int yy=u.y+dy[i];
			int nexkey=getkey(xx,yy);
			int dd=nowkey|nexkey;
			if(e[u.x][u.y][xx][yy]==-1||xx<1||xx>n||yy<1||yy>m||(e[u.x][u.y][xx][yy]>0&&!(nowkey&(1<<(e[u.x][u.y][xx][yy]-1)))))continue;
			if(!vis[xx][yy][dd]){
				vis[xx][yy][dd]=1;
				q.push((node){xx,yy,dd,u.dis+1});
			}
		}
	}
	return -1;
}
int main(){
	cin>>n>>m>>p;
	cin>>k;
	for(int i=1;i<=k;i++){
		int x1,y1,x2,y2,g;
		cin>>x1>>y1>>x2>>y2>>g;
		if(g){
			e[x1][y1][x2][y2]=g;
			e[x2][y2][x1][y1]=g;
		}
		else{
			e[x1][y1][x2][y2]=-1;
			e[x2][y2][x1][y1]=-1;
		}
	}
	cin>>s;
	for(int i=1;i<=s;i++){
		int x,y,q;
		cin>>x>>y>>q;
		key[x][y][++cnt[x][y]]=q;
	}
	printf("%d",bfs(1,1));
	return 0;
}

2.P2756 飞行员配对方案问题

输出配对方案即可

纯板子题

匈牙利完直接输出

#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=2e3+5;
int n,m,k;
int e[maxn][maxn];
int match[maxn],vis[maxn];
bool dfs(int u){
	for(int v=m+1;v<=n;v++){
		if(vis[v]||!e[u][v])continue;
		vis[v]=1;
		if(!match[v]||dfs(match[v])){// 如果v没有匹配,或者v的匹配找到了新的匹配
			match[v]=u;
			match[u]=v;          // 更新匹配信息
			return true;
		}
	}
	return false;
}
int xyl(){
	int ans=0;
	memset(match,0,sizeof(match));
	for(int i=1;i<=n;i++){// 对每一个点尝试匹配
		memset(vis,0,sizeof(vis));
		if(dfs(i))ans++;
	}
	return ans;
}
inline void init(){
	memset(e,0,sizeof(e));
}
int main(){
	scanf("%d%d",&m,&n);
	int u,v;
	while(scanf("%d%d",&u,&v)){
		if(u==-1&&v==-1)break;
		e[u][v]=1;
		e[v][u]=1;
	}
	printf("%d\n",xyl());
	for(int i=1;i<=m;i++){
		if(match[i]){
			printf("%d %d\n",i,match[i]);
		}
	}
	return 0;
}

3.P2761 软件补丁问题

题意:对于每一个补丁 i i i,都有 2 个与之相应的错误集合 B 1 [ i ] B1[i] B1[i] B 2 [ i ] B2[i] B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B 2 [ i ] B2[i] B2[i]中的任何错误时,才可以使用补丁 i i i。补丁 i i i 将修复软件中的某些错误 F 1 [ i ] F1[i] F1[i],而同时加入另一些错误 F 2 [ i ] F2[i] F2[i]。另外,每个补丁都耗费一定的时间。

思路:数据范围和状态表示暗示要状压,可是dp会有后效性,所以 s p f a spfa spfa,因为边权并非全相等,用普通 b f s bfs bfs的话不一定选到的是最优方式,重载优先队列的话可以达到同样效果

注意:如果暴力加上所有边的话边数会过多,直接在 s p f a spfa spfa的过程中进行状态转移

#include<bits/stdc++.h>
using namespace std;
const int maxn=25;
int b1[1<<22],b2[1<<22],f2[1<<22],f1[1<<22];
int t[105];
int n,m;
int inque[1<<22],dis[1<<22];
inline void spfa(int s){
	memset(dis,0x3f,sizeof dis);
	memset(inque,0,sizeof inque);
	dis[s]=0;
	queue<int>q;
	q.push(s);
	inque[s]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=1;i<=m;i++){
			if((u&b1[i])==b1[i]&&!(u&b2[i])){
				int v=(u|f1[i])^f1[i]|f2[i];
				if(dis[v]>dis[u]+t[i]){
					dis[v]=dis[u]+t[i];
					if(!inque[v]){
						inque[v]=1;
						q.push(v);
					}
				}
			}
		}
	}
	cout<<(dis[0]>=0x3f3f3f3f/2?0:dis[0]);
	return;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		scanf("%d",&t[i]);
		scanf(" ");
		char x;
		for(int j=1;j<=n;j++){
			scanf("%c",&x);
			if(x=='+')b1[i]|=(1<<j-1);
			else if(x=='-')b2[i]|=(1<<j-1);
		}
		scanf(" ");
		for(int j=1;j<=n;j++){
			scanf("%c",&x);
			if(x=='-')f1[i]|=(1<<j-1);
			else if(x=='+')f2[i]|=(1<<j-1);
		}
	}
	int st=(1<<n)-1;
	spfa(st);
	return 0;
}

4.P4016 负载平衡问题

题意: G G G公司有 n n n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 n n n 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。

思路:
1.源点到每个仓库连上货量的流量,费用为 0 0 0

2.仓库间两两连无限流量,费用为 1 1 1的边

3.仓库到汇点连上流量为总货物的平均值,费用为0的边

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f,maxn=1005,maxm=100005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn];
int dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
int maxflow,mincost;
void mcmf(int s,int t){
	maxflow=0;
	mincost=0;
	while(spfa(s,t)){
		int u=t;
//		printf("maxflow==%d %d %d\n",maxflow,flow[t],flow[t]*dis[t]);
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return;
}
signed main(){
	cin>>n;
	int s=n+1;
	int t=n+2;
	int x=0,sum=0;
	for(int i=1;i<=n;i++){
		cin>>x;
		sum+=x;
		add_edge(s,i,0,x);
		if(i!=n){
			add_edge(i,i+1,1,inf);
		}
		else{
			add_edge(n,1,1,inf);
		}
		if(i!=1){
			add_edge(i,i-1,1,inf);
		}
		else{
			add_edge(1,n,1,inf);
		}
	}
	int av=sum/n;
	for(int i=1;i<=n;i++){
		add_edge(i,t,0,av);
	}
	mcmf(s,t);
	cout<<mincost;
	return 0;
}

5.P3358 最长k可重区间集问题

6.P4014 分配问题

题意: n n n 件工作要分配给 n n n 个人做。第 i i i 个人做第 j j j 件工作产生的效益为 c i j c_{ij} cij 。试设计一个将 n n n 件工作分配给 n n n 个人做的分配方案,使产生的总效益最大。

思路:给每个人分配到最大价值的工作,二分图最大权匹配,裸 k m km km即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long maxn=1005,inf=1e18;
int n,m,mp[maxn][maxn],matched[maxn];
int slack[maxn],ex[maxn],ey[maxn],pre[maxn];
int visx[maxn],visy[maxn];
inline void init(){
	memset(slack,0,sizeof slack);
	memset(ex,0,sizeof ex);
	memset(ey,0,sizeof ey);
	memset(pre,0,sizeof pre);
	memset(matched,0,sizeof matched);
	memset(visx,0,sizeof visx);
	memset(visy,0,sizeof visy);
}
void match(int u){
	int x,y=0,yy=0,d;
	memset(pre,0,sizeof(pre));
	for(int i=1;i<=n;i++)slack[i]=inf;
    matched[y]=u;
	while(1){
		x=matched[y];
		d=inf;
		visy[y]=1;
		for(int i=1;i<=n;i++){
			if(visy[i])continue;
			if(slack[i]>ex[x]+ey[i]-mp[x][i]){
				slack[i]=ex[x]+ey[i]-mp[x][i];
				pre[i]=y;
			}
			if(slack[i]<d){
				d=slack[i];
				yy=i;
			}
		}
		for(int i=0;i<=n;i++){
			if(visy[i])ex[matched[i]]-=d,ey[i]+=d;
			else slack[i]-=d;
		}
		y=yy;
		if(matched[y]==-1)break;
	}
	while(y){
		matched[y]=matched[pre[y]];
		y=pre[y];
	}
}
int km(){
	memset(matched,-1,sizeof(matched));
	memset(ex,0,sizeof(ex));
	memset(ey,0,sizeof(ey));
	for(int i=1;i<=n;i++){
		memset(visy,0,sizeof(visy));
		match(i);
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(matched[i]!=-1)ans+=mp[matched[i]][i];
	}
	return ans;
}
signed main(){	
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mp[i][j]=-inf;
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=n;j++){
    		scanf("%lld",&mp[i][j]);
		}
	}
    int mm=km();
    init();
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=n;j++){
    		mp[i][j]=-mp[i][j];
		}
	}
	for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(mp[i][j]==-inf)mp[i][j]=inf;
	printf("%lld\n%lld",-km(),mm);
    return 0;
}

7.P4009 汽车加油行驶问题

题意:如果油不满,遇到加油站必须加满油付出代价a,如果回头走需要付出代价b,可以在任意一个地方建加油站付出代价c(神仙?)

思路:分层图最短路,记录每个点的剩余油量,遇到加油站且油不满直接加油并 c o n t i n u e continue continue

#include<bits/stdc++.h>
using namespace std;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
int n,m,k,a,b,c;
int vis[105][105][12];
int mp[105][105];
int dis[105][105][12],inque[105][105][12];
struct node{
	int x,y,res;
};
inline void spfa(int sx,int sy){
	memset(dis,0x3f,sizeof dis);
	dis[sx][sy][k]=0;
	queue<node>q;
	q.push((node){sx,sy,k});
	inque[sx][sy][k]=1;
	while(!q.empty()){
		node u=q.front();
		q.pop();
		int x=u.x;
		int y=u.y;
		int res=u.res;
		inque[x][y][res]=0;
		if(mp[x][y]&&res!=k){
			if(dis[x][y][k]>dis[x][y][res]+a){
				dis[x][y][k]=dis[x][y][res]+a;
				if(!inque[x][y][k]){
					inque[x][y][k]=1;
					q.push((node){x,y,k});
				}
			}
			continue;
		}
		else{
			if(dis[x][y][k]>dis[x][y][res]+a+c){
				dis[x][y][k]=dis[x][y][res]+a+c;
				if(!inque[x][y][k]){
					inque[x][y][k]=1;
					q.push((node){x,y,k});
				}
			}
		}
		if(res>0){
			for(int i=0;i<4;i++){
	    		int xx=x+dx[i];
		    	int yy=y+dy[i];
		    	int f=0;
		    	if(xx<x||yy<y)f=b;
		    	if(xx<1||yy<1||xx>n||yy>n)continue;
		    	if(dis[xx][yy][res-1]>dis[x][y][res]+f){
			    	dis[xx][yy][res-1]=dis[x][y][res]+f;
			    	if(!inque[xx][yy][res-1]){
		    			inque[xx][yy][res-1]=1;
		    			q.push((node){xx,yy,res-1});
		    		}
  	        	}
    		}
		}
	}
}
int main(){
	cin>>n>>k>>a>>b>>c;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>mp[i][j];
		}
	}
	spfa(1,1);
	int mm=0x3f3f3f3f;
	for(int i=0;i<=k;i++){
		mm=min(mm,dis[n][n][i]);
	}
	cout<<mm;
	return 0;
}

8.P4015 运输问题

题意:设计一个将仓库中所有货物运送到零售商店的运输方案,输出最小运输费用和最大运输费用

思路:二分图,分别跑一次最大最小费用流即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){
	int maxflow=0;
	int mincost=0;
	while(spfa(s,t)){
		int u=t;
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return {maxflow,mincost};
}
inline int id(int i,int j){
	return (i-1)*n+j;
}
int a[50005],b[50005],x[50005];
int main(){
	cin>>m>>n;
	int s=n+m+1;
	int t=n+m+2;
	for(int i=1;i<=m;i++){
		scanf("%d",&a[i]);
		add_edge(s,i,0,a[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&b[i]);
		add_edge(i+m,t,0,b[i]);
	}
	
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&x[id(i,j)]);
			add_edge(i,j+m,x[id(i,j)],a[i]);
		}
	}
	pii yaoyao=mcmf(s,t);
	cout<<yaoyao.second<<endl;
	
	
	cnt=1;
	memset(head,0,sizeof head);
	for(int i=1;i<=m;i++){
		add_edge(s,i,0,a[i]);
	}
	for(int i=1;i<=n;i++){
		add_edge(i+m,t,0,b[i]);
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			add_edge(i,j+m,-x[id(i,j)],a[i]);
		}
	}
	pii yaoyao2=mcmf(s,t);
	cout<<-yaoyao2.second<<endl;
	return 0;
}

9.P2774 方格取数问题

题意:选出一些数,这些数不能相邻,怎么取能使所取的数总和最大

思路:
首先通过观察可以发现,相邻的格子是一定不能取的

先把整个图进行染色

选取一些点,逆向思维可以转化为一开始全选,然后舍弃一些点,于是想到最小割

源点向所有白点连边,黑点向所有汇点连边,相邻的黑白点连容量为 i n f inf inf的边

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline int id(int x,int y){
	return (x-1)*m+y;
}
int f[maxn],se[maxn],sum; 
signed main(){
	scanf("%lld%lld",&n,&m);
	int u,v,w;
	s=0,t=n*m+1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lld",&f[id(i,j)]);
			sum+=f[id(i,j)];
			if((i+j)%2==1){
				se[id(i,j)]=1;
				add_edge(s,id(i,j),f[id(i,j)]);
			}
			else if((i+j)%2==0){
				add_edge(id(i,j),t,f[id(i,j)]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if((i+j)%2==1){
			int dd=id(i,j);
			int ff=f[dd];
			for(int k=0;k<4;k++){
				int xx=i+dx[k];
				int yy=j+dy[k];
				if(xx>=1&&yy<=m&&xx<=n&&yy>=1){
					add_edge(id(i,j),id(xx,yy),inf);
				}
			}
	    	}
		}
	}
	printf("%lld",sum-dinic());
	return 0;
}

10.P4012 深海机器人问题

题意:k个机器人从出发点到终点路径上的最大价值是多少,每个格子只能取一次

思路:直接按照题意建图即可

源点连一条容量为 K K K到出发点的边,表示有 K K K个机器人

拆点:
连一条边流量为 1 1 1,费用 w w w,代表只能取走一次价值

连一条边流量 i n f inf inf,费用 0 0 0,代表每个点可以走无限次

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=20005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
inline pair<int,int> mcmf(int s,int t){
	int maxflow=0;
	int mincost=0;
	while(spfa(s,t)){
		int u=t;
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return {maxflow,mincost};
}
int p,q,a,b; 
inline int id(int x,int y){
	return x*(q+1)+y+1;
}
int main(){
	cin>>a>>b>>p>>q;
	int s=(p+1)*(q+1)+1;
	int t=s+1;
	for(int i=0;i<p+1;i++){
		for(int j=0;j<q;j++){
			int w;
			cin>>w;
			add_edge(id(i,j),id(i,j+1),-w,1);
			add_edge(id(i,j),id(i,j+1),0,inf);
		}
	}
	for(int j=0;j<q+1;j++){
		for(int i=0;i<p;i++){
			int w;
			cin>>w;
			add_edge(id(i,j),id(i+1,j),-w,1);
			add_edge(id(i,j),id(i+1,j),0,inf);
		}
	}
	for(int i=1;i<=a;i++){
		int f,x,y;
		cin>>f>>x>>y;
		add_edge(s,id(x,y),0,f);
	}
	for(int i=1;i<=b;i++){
		int f,x,y;
		cin>>f>>x>>y;
		add_edge(id(x,y),t,0,f);
	}
	pii yaoyao=mcmf(s,t);
	cout<<-yaoyao.second;
}

11.P2762 太空飞行计划问题

题意:有一些实验可以获利,需要花钱买一些仪器才能完成,问净收益最大的试验计划

思路:
最大权闭合子图

建二分图,实验在左边,仪器在右边

割掉左边的边代表不选择这个实验,放弃它的价值,割右边的边代表选择付出这个代价,用价值总和-最小割即为答案

(对最大权闭合子图的理解已经在之前的博客写过,不再赘述)

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
vector<int>yq,sy;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t,qq;
inline int read(int &x){
    char c;x=0;
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return c=='\r'||c=='\n'?0:1;
}
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(v>=m+1&&v<=m+n){
					sy.push_back(u);
					yq.push_back(v);
				}
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline int dd(int x,int y){
	return (x-1)*m+y;
}
int f[maxn],id[maxn],sum; 
signed main(){
	scanf("%lld%lld",&m,&n);
	int u,v,w;
	s=0,t=n+m+1;
	for(int i=1;i<=m;i++){
		qq=read(w);
		add_edge(s,i,w);
		sum+=w;
		while(qq){
		qq=read(w);
		add_edge(i,m+w,inf);
		}
	}
	for(int i=1;i<=n;i++){
		scanf("%lld",&w);
		add_edge(m+i,t,w);
	}
	int yaoyao=dinic();
	for(int i=1;i<=m;i++){
		if(dep[i])printf("%d ",i);
	}
	printf("\n");
	for(int i=1;i<=n;i++){
		if(dep[i+m])printf("%d ",i);
	}
	printf("\n%lld",sum-yaoyao);
	return 0;
}

12.P2770 航空路线问题

题意:从起点到终点走一个来回,求最多能经过多少个城市(起点终点可经过两次)

思路:
因为求一来一回有点不方便,先将边转化为全部去终点,转化成求起点去终点的两条不相交路径

先跑一发费用流,求出最大经过城市数量

终点在于怎么输出路径,这里利用走过的边流量为 0 0 0的性质,跑两次 d f s dfs dfs即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
string str[105];
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){
	int maxflow=0;
	int mincost=0;
	int tim=0;
	while(spfa(s,t)){
		int u=t;
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		++tim;
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return {maxflow,mincost};
}
int vis[1005];
vector<string>jl1,jl2;
void dfs1(int u){
	jl1.push_back(str[u]);
//	cout<<str[u]<<endl;
	vis[u]=1;
	for(int i=head[u+n];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;
		else{
			dfs1(v);
			break;
		}
	}
}
void dfs2(int u){
//	vis[u]=1;
    jl2.push_back(str[u]);
	for(int i=head[u+n];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;
	    dfs2(v);
	}
//	cout<<str[u]<<endl;	
}
int main(){
	cin>>n>>m;
	int num=0;
	int s=1;
	int t=n*2;
	bool ok=false;
	for(int i=1;i<=n;i++){
		string a;
		cin>>a;
		mp[a]=i;
		str[i]=a;
		if(i==1||i==n){
			add_edge(i,i+n,-1,2);
		}
		else{
			add_edge(i,i+n,-1,1);
		}
	}
	for(int i=1;i<=m;i++){
		string a,b;
		cin>>a>>b;
		int x=mp[a];
		int y=mp[b];
		if(x>y)swap(x,y);
		if(x==1&&y==n)ok=true;
		add_edge(x+n,y,0,1);
	}
	pii yaoyao=mcmf(s,t);
	if(yaoyao.first==2){
		printf("%d\n",-yaoyao.second-2);
		dfs1(s);
    	dfs2(s);
    	for(auto v:jl1){
    		cout<<v<<endl;
		}
		string zz[105];
		int numzz=0;
		for(auto v:jl2){
			zz[++numzz]=v;
		}
		for(int i=numzz;i>=1;i--){
			cout<<zz[i]<<endl;
		}
	}
	else if(yaoyao.first==1&&ok){
		cout<<2<<endl;
		cout<<str[1]<<endl;
		cout<<str[n]<<endl;
		cout<<str[1]<<endl;
	}
	else{
		printf("No Solution!");
	}
	return 0;
	
} 

13.P2754 [CTSC1999]家园 / 星际转移问题

题意:有n个太空站,m艘飞船按照一定路线来回飞行,求地球到月球的最短时间

思路:
分层图网络流

每过一天建一套站点,借用一下luogu大佬题解的图片
在这里插入图片描述
进行解释一下,样例的走法是:
d 1 d1 d1:到 1 1 1号点
d 2 d2 d2:到 2 2 2号点
d 3 d3 d3:不走
d 4 d4 d4:不走
d 5 d5 d5:走到月球

因为飞船是不会停下来的,有的时候就必须在原地飞船走到这个路线

每一次循环建立一套如图所示的站点

飞船的路线之间建立容量为 c a p cap cap的边,代表飞船一次能带这么多的人

前一天的这个点和后一天的这个点建立容量为 i n f inf inf的边(代表所有人都可以继续在这个站点等飞船过来)

然后不断跑最大流,直到 s u m > = k sum>=k sum>=k

设定一个较大的天数,如果超过了就break,代表不能将所有人都运输到月球

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],cap[maxn],cz[maxn],mp[205][205];
int cnt=1,n,m,s,t,k;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof head);
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	int u,v,w;
	n+=2;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&cap[i],&cz[i]);
		for(int j=0;j<cz[i];j++){
			scanf("%d",&mp[i][j]);
			mp[i][j]+=2;
		}
	}
	int day=0;
	s=0,t=9999;
	int sum=0;
	while(day<500){
		add_edge(s,day*n+2,inf);
		add_edge(day*n+1,t,inf);
		if(day){
			for(int i=1;i<=n;i++){
				add_edge((day-1)*n+i,day*n+i,inf);
			}
			for(int i=1;i<=m;i++){
				int x=mp[i][(day-1)%cz[i]];
				int y=mp[i][day%cz[i]];
				add_edge((day-1)*n+x,day*n+y,cap[i]);
			}
		}
		sum+=dinic();
		if(sum>=k)break;
		day++;
	}
	if(day==500)printf("0");
	else printf("%d",day);
	return 0;
}

14.P3254 圆桌问题

题意:有n个单位,m张餐桌,求每个单位的人不跟同单位的人在一个餐桌的方案

思路:
源点向每个单位建一条容量为单位人数的边

每个餐桌向汇点建一条容量为餐桌容纳的边

每个单位向每张餐桌连一条容量为1的边,代表各单位只能一个人选择这张餐桌

跑最大流

利用走过的路径反向边流量为1的性质,跑 d f s dfs dfs记录每个单位的人的选择即可

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
vector<int>jl[maxn];
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=1;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
void dfs2(int u){
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(v==t)return;
		if(v>=1&&v<=n){
			dfs2(v);
		}
		if(e[i^1].w==1&&v>=n+1&&v<=n+m){
			jl[u].push_back(v-n);
		}
	}
	return;
}
int main(){
	int sum=0;
	cin>>n>>m;
	s=n+m+1;
	t=n+m+2;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		sum+=x;
		add_edge(s,i,x);
	}
	for(int i=1;i<=m;i++){
		int x;
		cin>>x;
		add_edge(i+n,t,x);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			add_edge(i,j+n,1);
		}
	}
	int yaoyao=dinic();
	if(sum!=yaoyao)printf("0\n");
	else{
		dfs2(s);
		printf("1\n");
		for(int i=1;i<=n;i++){
			for(auto v:jl[i]){
				printf("%d ",v);
			}
			printf("\n");
		}
	}
	return 0;
}

15.P1251 餐巾计划问题

16.P2763 试题库问题

题意:假设一个试题库中有 nn 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 mm 道题组成试卷。并要求试卷包含指定类型的试题。

思路:
每个类型向汇点建一条容量为此类型需要的题目数量的边即可

增广路的过程中记录这个类型选择的题目

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k;
vector<int>jl[maxn];
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(v>=n+1&&v<=n+k){
					jl[v-n].push_back(u);
				}
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int main(){
	cin>>k>>n;
	s=0,t=n+k+1;
	int x;
	for(int i=1;i<=k;i++){
		scanf("%d",&x);
		add_edge(n+i,t,x);
		m+=x;
	}
	int p;
	for(int i=1;i<=n;i++){
		scanf("%d",&p);
		add_edge(0,i,1);
		for(int j=1;j<=p;j++){
			scanf("%d",&x);
			add_edge(i,n+x,1);
		}
	}
	int ss=dinic();
	if(ss<m){
		printf("No Solution!");
	}
	else{
		for(int i=1;i<=k;i++){
			printf("%d:",i);
			for(int j=0;j<jl[i].size();j++){
				printf(" %d",jl[i][j]);
			}
			printf("\n");
		}
	}
	return 0;
}

17.P2766 最长不下降子序列问题

题意:
t a s k 1 task1 task1:求 l i s lis lis

t a s k 2 task2 task2:每个点取一次,求有多少个长度为 s s s l i s lis lis

t a s k 3 task3 task3:首尾可以取无限次,求有多少个长度为 s s s l i s lis lis

思路:
t a s k 1 task1 task1 d p dp dp l i s lis lis,并记录每个点的值(到此为止的 l i s lis lis长度)

t a s k 2 task2 task2:每个点与 l i s lis lis为相邻长度的点连边,跑出最大流即为答案

t a s k 3 task3 task3:在前一问的条件下把首尾点容量改为无限即可

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,ss,ll;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof head);
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int a[maxn],dp[maxn];
inline void init(){
	add_edge(s,1,inf);
	add_edge(1,1+n,inf);
	if(dp[n]==ll){
		add_edge(n+n,t,inf);
		add_edge(n,n+n,inf);
	}
}
int main(){
	scanf("%d",&n);
	int u,v,w;
	s=0,t=n+n+1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		dp[i]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(a[j]<=a[i]){
				dp[i]=max(dp[i],dp[j]+1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		ll=max(ll,dp[i]);
//		cout<<dp[i]<<" ";
	}
	cout<<ll<<endl;
	for(int i=1;i<=n;i++){
		add_edge(i,i+n,1);
		if(dp[i]==1){
			add_edge(s,i,1);
		}
		if(dp[i]==ll){
			add_edge(i+n,t,1);
		}
		for(int j=1;j<i;j++){
			if(a[j]<=a[i]&&dp[j]+1==dp[i]){
				add_edge(j+n,i,1);
			}
		}
	}
	int ff=dinic();
	printf("%d\n",ff);
	init();
	ff+=dinic();
	printf("%d\n",ff<1e9?ff:1);
	return 0;
}

18.P3355 骑士共存问题

题意:有一些障碍,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击

思路:
这题跟方格取数那一题的思想是一样的,每个点有与它限制的位置的点,但是有有所不同,这一题本质上是求最大独立集(因为互相不能攻击到),而那一题有点权,是求最小割

染色后将能互相攻击的位置连边,求出最大匹配

总点数-障碍数-最大匹配=最大独立集

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf =0x3f3f3f3f,maxn=2e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,1,-1,-1,2,-2,2,-2};
const int dy[]={2,-2,2,-2,1,1,-1,-1};
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline int id(int x,int y){
	return (x-1)*n+y;
}
int mp[505][505]; 
signed main(){
	scanf("%lld%lld",&n,&m);
	int u,v,w;
	s=0,t=n*n+1;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		mp[x][y]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(mp[i][j])continue;
			if((i+j)%2==1){
				add_edge(s,id(i,j),1);
			}
			else if((i+j)%2==0){
				add_edge(id(i,j),t,1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if((i+j)%2==1&&!mp[i][j]){
	    		for(int k=0;k<8;k++){
		    		int xx=i+dx[k];
		    		int yy=j+dy[k];
		    		if(xx>=1&&yy<=n&&xx<=n&&yy>=1){
		    			add_edge(id(i,j),id(xx,yy),inf);
		    		}
		    	}
	    	}
		}
	}
	printf("%lld",n*n-m-dinic());
	return 0;
}

19.P3357 最长k可重线段集问题

20.P4013 数字梯形问题

t a s k 1 task1 task1:不允许有路径相交:
很简单,一看就是点和边流量限制都为1,这样就不可能有右边的流跨到左边去了

t a s k 2 task2 task2:允许选重复的点:
点的流量限制无限即可,主要点跟汇点的流量也要改掉

t a s k 3 task3 task3:允许路径相交,允许重复点:
点边都无限即可

建图三次

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
inline pair<int,int> mcmf(int s,int t){
	int maxflow=0;
	int mincost=0;
	while(spfa(s,t)){
		int u=t;
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return {maxflow,mincost};
}
inline int id(int x,int y){
	return (x-1)*(m+n-1)+y;
}
int mp[1000][1000];
int main(){
	cin>>m>>n;
	int num=10000;
	int k=m-1;
	int s=20001;
	int t=20002;
	for(int i=1;i<=n;i++){
		k++;
		for(int j=1;j<=k;j++){
			cin>>mp[i][j];
			add_edge(id(i,j),id(i,j)+num,-mp[i][j],1);
			if(i==1){
				add_edge(s,id(i,j),0,1);
			}
			if(i==n){
				add_edge(id(i,j)+num,t,0,1);
			}
		}
	}
	k=m-1;
	for(int i=1;i<=n-1;i++){
		k++;
		for(int j=1;j<=k;j++){
			add_edge(id(i,j)+num,id(i+1,j),0,1);
			add_edge(id(i,j)+num,id(i+1,j+1),0,1);
		}
	}
	pii yaoyao1=mcmf(s,t);
	cout<<-yaoyao1.second<<endl;
	
	
	memset(head,0,sizeof head);
	cnt=1;
	for(int i=1;i<=n;i++){
		k++;
		for(int j=1;j<=k;j++){
		//	cin>>mp[i][j];
			add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);
			if(i==1){
				add_edge(s,id(i,j),0,1);
			}
			if(i==n){
				add_edge(id(i,j)+num,t,0,inf);
			}
		}
	}
	k=m-1;
	for(int i=1;i<=n-1;i++){
		k++;
		for(int j=1;j<=k;j++){
			add_edge(id(i,j)+num,id(i+1,j),0,1);
			add_edge(id(i,j)+num,id(i+1,j+1),0,1);
		}
	}
	pii yaoyao2=mcmf(s,t);
	cout<<-yaoyao2.second<<endl;
	
	
	memset(head,0,sizeof head);
	cnt=1;
	for(int i=1;i<=n;i++){
		k++;
		for(int j=1;j<=k;j++){
		//	cin>>mp[i][j];
			add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);
			if(i==1){
				add_edge(s,id(i,j),0,1);
			}
			if(i==n){
				add_edge(id(i,j)+num,t,0,inf);
			}
		}
	}
	k=m-1;
	for(int i=1;i<=n-1;i++){
		k++;
		for(int j=1;j<=k;j++){
			add_edge(id(i,j)+num,id(i+1,j),0,inf);
			add_edge(id(i,j)+num,id(i+1,j+1),0,inf);
		}
	}
	pii yaoyao3=mcmf(s,t);
	cout<<-yaoyao3.second<<endl;
	return 0;
}

21.P3356 火星探险问题

题意:有一些障碍,有些点上面有矿石,有n辆车,只能向左或向下走,求采到最多矿石的路径

思路:建图跟深海那题一样,利用反向边流量的性质跑 d f s dfs dfs记录路径即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=400005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
inline pair<int,int> mcmf(int s,int t){
	int maxflow=0;
	int mincost=0;
	while(spfa(s,t)){
		int u=t;
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return {maxflow,mincost};
}
inline int id(int x,int y){
	return (x-1)*m+y;
}
inline int id2(int x,int y){
	return (x-1)*m+y+n*m;
}
int vis[maxn];
void dfs(int u,int num){
	for(int i=head[u+n*m];i;i=e[i].nex){
		int v=e[i].v;
		if(v==u+1&&vis[i]<e[i^1].flow){
			printf("%d 1\n",num);
			vis[i]++;
			dfs(v,num);
			break;
		}
		if(v==u+m&&vis[i]<e[i^1].flow){
			printf("%d 0\n",num);
			vis[i]++;
			dfs(v,num);
			break;
		}
	}
}
int mp[105][105];
int main(){
	cin>>k>>m>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>mp[i][j];
			if(mp[i][j]==1)continue;
			add_edge(id(i,j),id2(i,j),mp[i][j]==2?-1:0,1);
			add_edge(id(i,j),id2(i,j),0,inf);
		}
	}
	int s=n*m*2+1;
	int t=s+1;
	if(mp[1][1]!=1)add_edge(s,id(1,1),0,k);
	if(mp[n][m]!=1)add_edge(id2(n,m),t,0,k);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(mp[i][j]==1)continue;
			if(mp[i][j+1]!=1&&j+1<=m){
				add_edge(id2(i,j),id(i,j+1),0,inf);
			}
			if(mp[i+1][j]!=1&&i+1<=n){
				add_edge(id2(i,j),id(i+1,j),0,inf);
			}
		}
	}
	pii yaoyao=mcmf(s,t);
	for(int i=1;i<=k;i++){
		dfs(1,i);
	}
	return 0;
}

22.P2765 魔术球问题

题意:
假设有 n n n 根柱子,现要按下述规则在这 n n n 根柱子中依次放入编号为 1 1 1 2 2 2 3 3 3,…的球“

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 2 2 2 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 n n n 根柱子上最多能放多少个球。例如,在 4 4 4 根柱子上最多可放 11 11 11 个球。

对于给定的 n n n,计算在 n n n 根柱子上最多能放多少个球。

思路:
枚举n,拆点,求出小于n能和n组成平方数的数,连边,在残量网络上跑网络流,如果能找到新的增广路代表现有的柱子上还可以放,否则增加柱子,在增广路的过程中记录每个柱子上的数,跟最小路径覆盖那题异曲同工

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn],vis[maxn];
int cnt=1,n,m,s=100001,t=100002,now,p;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof head);
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(u!=t)to[u>>1]=v>>1;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int zz[maxn];
int main(){
	scanf("%d",&n);
	int p=0;
	while(p<=n) {
        now++;
        add_edge(s,now*2,1);
        add_edge(now*2+1,t,1);
   //     printf("s %d\n",now);
    //    printf("%d+%d t\n",now,now);
        for(int i=sqrt(now)+1;i*i<now*2;i++) {
            add_edge((i*i-now)*2,now*2+1,1);
    //        printf("%d %d+%d\n",(i*i-now),now,now);
        }
        int flow = dinic();
        if(!flow){
            zz[++p] = now;
        }
    }
	cout<<now-1<<endl;
	for(int i=1;i<=n;i++){
		int u=zz[i];
		printf("%d",u);
		u=to[u];
		while(u&&u!=t>>1){
			printf(" %d",u);
			u=to[u];
		}
		cout<<endl;
	}
	return 0;
}

23.P2764 最小路径覆盖问题

给定有向图 G = ( V , E ) G=(V,E) G=(V,E) 。设 P P P G G G 的一个简单路(顶点不相交)的集合。如果 V V V 中每个定点恰好在 P P P的一条路上,则称 P P P G G G 的一个路径覆盖。 P P P中路径可以从 V V V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 0 0 G G G 的最小路径覆盖是 G G G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) G G G 的最小路径覆盖。

提示:设 V = { 1 , 2 , . . . , n } V=\{1,2,...,n\} V={1,2,...,n} ,构造网络 G 1 = { V 1 , E 1 } G_1=\{V_1,E_1\} G1={V1,E1} 如下:

V 1 = { x 0 , x 1 , . . . , x n } ∪ { y 0 , y 1 , . . . , y n } V_1=\{x_0,x_1,...,x_n\}\cup\{y_0,y_1,...,y_n\} V1={x0,x1,...,xn}{y0,y1,...,yn}

E 1 = { ( x 0 , x i ) : i ∈ V } ∪ { ( y i , y 0 ) : i ∈ V } ∪ { ( x i , y j ) : ( i , j ) ∈ E } E_1=\{(x_0,x_i):i\in V\}\cup\{(y_i,y_0):i\in V\}\cup\{(x_i,y_j):(i,j)\in E\} E1={(x0,xi):iV}{(yi,y0):iV}{(xi,yj):(i,j)E}

每条边的容量均为 1 1 1 ,求网络 G 1 G_1 G1 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 最大流。

题目直接给出了提示,可是我太蒻了没有看懂…

原图是这样子的:
在这里插入图片描述
要我们求最小覆盖路径,我们可以先把图看成没有边,此时覆盖路径数为11,通过手玩可以发现,每当我们连上一条边之后,这个路径数就会减少1,而每一条路径上,一个点只能被另外一个点连上,这就符合匹配的原理,我们由此构建出二分图
在这里插入图片描述
可以看出最小路径覆盖数=总点数-最大流,因为最大流即为匹配数,被匹配上的点就不需要做出发点

题目要求我们输出路径,在dfs找增广路的时候记录下每一个点 u − > v u->v u>v v v v就好

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=2*n+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				to[u]=v;
				if(u!=s)flag[v-n]=1;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0; 
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int main(){
	scanf("%d%d",&n,&m);
	int u,v,w;
	s=0,t=2*n+1;
	for(int i=1;i<=n;i++){
		add_edge(s,s+i,1);
		add_edge(s+n+i,t,1);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		add_edge(s+u,s+n+v,1);
	}
	int ans=dinic();
	for(int i=1;i<=n;i++){
		if(!flag[i]){
			int now=i;
			printf("%d ",now);
			while(to[now]&&to[now]!=t){
				printf("%d ",to[now]-n);
				now=to[now]-n;
			}
			cout<<endl;
		}
	}
	printf("%d",n-ans);
	return 0;
}

24.P2775 机器人路径规划问题

著名假题,可以用ida*过,咕 咕 咕~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值