//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

网络流 24 题

P2756 飞行员配对方案问题

题目很明确的告诉我们需要将一个歪果仁和一个英国人搭配起来,问你最多的搭配是多少,我们设一个超级源点和超级汇点,把所有的外国人都连到超级源点上,然后把所有的英国人都连到超级汇点上,然后把题目给的能配对的两人连边,跑 dinic 即可,最后从编号 \(n\times 2+2\) (因为在这之前的边都是连在超级源点汇点上的)到最大边数枚举,如果中间有流量为 \(0\) 的边的话就直接输出起点终点即可。

code:

#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int n,m,val[N],to[N*2],nxt[N*2],head[N*2];//val存放边的最大流 
int cnt=1,ans,q[N*2],l,r,vis[N*2],frm[N*2];//q用于模拟队列 
inline void add(int u,int v,int w)//建边 
{
	val[++cnt]=w;//存最大流量 
	frm[cnt]=u;//存起点 
	to[cnt]=v;//存终点 
	nxt[cnt]=head[u];//存头节点 
	head[u]=cnt;//更新 
}
int bfs()
{
	memset(vis,0,sizeof(vis));//清空vis 
	q[r=l=1]=0;//起点入列 
	vis[0]=1;//标记深度为1 
	while(l<=r)//只要队列不空 
	{
		int u=q[l++];//取出队头元素 
		for(int i=head[u];i;i=nxt[i])//枚举每一条与u相连的边 
		{
			int v=to[i];//取出终点 
			if(val[i]&&!vis[v])//如果当前边最大流量不为0并且没有搜过此点 
			{
				vis[v]=vis[u]+1;//标记深度 
				q[++r]=v;//入列 
			}
		}
	}
	return vis[n+1];//返回汇点的深度 
}
int dfs(int u,int in)//深搜找最大流 
{
	if(u==n+1)//如果到达了汇点 
	  return in;//直接返回流入的流量的值 
	int out=0;//out存放流出的流量的值 
	for(int i=head[u];i&&in;i=nxt[i])//遍历每一个与u相连的边,保证流入的流量大小不为0 
	{
		int v=to[i];//取出终点 
		if(val[i]&&vis[v]==vis[u]+1)//如果当前边最大流量不为0并且是向汇点流去 
		{
			int res=dfs(v,min(val[i],in));//计算当前边流的流量 
			val[i]-=res;//正向边减去 
			val[i^1]+=res;//反向边加上 
			in-=res;//流入的流量减去 
			out+=res;//流出的流量加上 
		}
	}
	if(out==0)//如果当前流出的流量大小为0 
	  vis[u]=0;//标记u,下次不搜了 
	return out;//返回out的值 
}
signed main()
{
	cin>>m>>n;
	int u,v;
	for(int i=1;i<=m;i++)
	  add(0,i,1),//预处理把1-m连到0点上 
	  add(i,0,0);
	for(int i=m+1;i<=n;i++)
	  add(i,n+1,1),//预处理把m+1-n连到n+1点上 
	  add(n+1,i,0);
	while(cin>>u>>v)
	{
		if(u==-1&&v==-1)break;
		add(u,v,1);//存边
		add(v,u,0);
	}
	while(bfs())
	  ans+=dfs(0,1e18);//求最大流 
	cout<<ans<<endl;
	for(int i=n*2+2;i<=cnt;i++)//输出答案 
	{
		if(val[i]==0&&frm[i]<=m)
		cout<<frm[i]<<' '<<to[i]<<endl;//输出方案数 
	}
	return 0;//好习惯 
}

P3254 圆桌问题

这个题目比较好想,就是建一个超级源点和一个超级汇点,源点与每一个单位的人数建边,在这里把总人数都给加起来存到 \(sum\) 里,然后每一个桌子的人数与汇点建边,最后将每一个单位都与桌子连一条边,表示能通向,跑一遍最大流,如果最后的 \(ans\) 值是等于 \(sum\) 的就可以有方案了,这时候把每一个单位都枚举一遍,后面的时候如果终点是餐桌的编号且当前边流量是 \(0\) 也就是当前单位有人在这个桌子上的话,输出即可。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,s,t,ans,sum,head[N],cnt=1;
struct sb{int u,v,w,next;}e[N<<2];
int dis[N],r[N],c[N];
inline void add(int u,int v,int w)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline int bfs()
{
	memset(dis,0,sizeof dis);
	queue<int>q;
	dis[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v]&&e[i].w)
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[t];
}
inline int dfs(int x,int in)
{
	if(x==t)return in;
	int out=0;
	for(int i=head[x];i&&in;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[x]+1&&e[i].w)
		{
			int res=dfs(v,min(in,e[i].w));
			out+=res;
			in-=res;
			e[i].w-=res;
			e[i^1].w+=res;
		}
	}
	if(out==0)dis[x]=0;
	return out;
}
signed main()
{
	cin>>m>>n;
	s=0;t=n+m+1;
	for(int i=1;i<=m;i++)
	{
		cin>>r[i];
		sum+=r[i];
		add(0,i,r[i]);
		add(i,0,0);
	}
	for(int i=1;i<=n;i++)
	{
		cin>>c[i];
		add(i+m,t,c[i]);
		add(t,i+m,0);
	}
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			add(i,j+m,1),add(j+m,i,0);
	while(bfs())
	  ans+=dfs(s,INF);
	if(ans==sum)
	{
		cout<<"1"<<endl;
		for(int u=1;u<=m;u++)
		{
		  for(int i=head[u];i;i=e[i].next)
			if(e[i].v>m&&e[i].v<=m+n&&!e[i].w)
			  cout<<e[i].v-m<<" ";
			cout<<endl;
		}
		return 0;
	}
	cout<<"0"<<endl;
	return 0;
}

P4011 孤岛营救问题

这题为啥是网络流啊。
这题需要用到的东西就是状压+bfs。
我们看到钥匙的数量很少,所以我们可以用数组的一维来存放当前点的钥匙持有情况,然后 bfs 即可。

code:

#include<bits/stdc++.h>
using namespace std;
int vis[11][11][(1<<11)];//标记在ij点的钥匙状态 
int mp[11][11][11][11];//标记两个格子是否有门 
int key[11][11][11];//当前点的钥匙
int num[11][11];//标记当前点的钥匙数目
int dx[]={0,1,-1,0,0},dy[]={0,0,0,1,-1};
int n,m,p,k,s;
struct sb{int x,y,dis,cos;};
queue<sb>q;
inline int bfs()
{
	int now=0;
	for(int i=1;i<=num[1][1];i++)
	  now|=(1<<(key[1][1][i]-1));
	vis[1][1][now]=1;
	q.push(sb{1,1,0,now});
	while(!q.empty())
	{
		sb qwq=q.front();
		q.pop();
		if(qwq.x==n&&qwq.y==m)
		  return qwq.dis;
		for(int i=1;i<=4;i++)
		{
			int xx=qwq.x+dx[i];
			int yy=qwq.y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m)continue;
			if(mp[qwq.x][qwq.y][xx][yy]==-1)continue;
			int t=mp[qwq.x][qwq.y][xx][yy];
			if(t!=0)
			  if((qwq.cos&(1<<(t-1)))==0)continue;
			int cc=qwq.cos;
			for(int j=1;j<=num[xx][yy];j++)
			  cc|=(1<<key[xx][yy][j]-1);
			if(vis[xx][yy][cc])continue;
			vis[xx][yy][cc]=1;
			q.push(sb{xx,yy,qwq.dis+1,cc});
		}
	}
	return -1;
}
int main()
{
	cin>>n>>m>>p>>k;
	for(int i=1;i<=k;i++)
	{
		int a1,b1,a2,b2,c;
		cin>>a1>>b1>>a2>>b2>>c;
		if(c==0)
		{
			mp[a1][b1][a2][b2]=-1;
			mp[a2][b2][a1][b1]=-1;
		}
		else 
		{
			mp[a1][b1][a2][b2]=c;
			mp[a2][b2][a1][b1]=c;
		}
	}
	cin>>s;
	for(int i=1;i<=s;i++)
	{
		int x,y,p;
		cin>>x>>y>>p;
		num[x][y]++;
		key[x][y][num[x][y]]=p;
	}
	int ans=bfs();
	cout<<ans<<endl;
	return 0;
}

P2774 方格取数问题

我们可以发现,互斥的两个点横纵坐标和奇偶性不同,这样可以分为两类点,一类是横纵坐标和是奇数,一类是横纵坐标和是偶数。
我们假设一开始全都要,然后扔掉一些点让答案合法,问题就转化为了求最小割。
我们用超级源点和其中一类以点权为流量链接,另一类同样连到超级汇点上。
将两两互斥的边之间连一条 INF 的边,保证最小割不会割掉这条边使图不连通。
根据最小割等于最大流,我们就可以用点权的总和减去最大流求出答案了。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1001000
using namespace std;
struct sb{int u,v,w,next;}e[N];
int n,m,s,t,dis[N],mp[510][510],head[N],cnt=1,ans,sum;
int dx[]={0,0,0,1,-1},dy[]={0,1,-1,0,0};
inline void add(int u,int v,int w)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
	e[++cnt].u=v;
	e[cnt].v=u;
	e[cnt].w=0;
	e[cnt].next=head[v];
	head[v]=cnt;
}
int bfs()
{
	memset(dis,0,sizeof dis);
	queue<int>q;
	q.push(s);
	dis[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v]&&e[i].w)
			{
				dis[v]=dis[u]+1;
				q.push(v); 
			}
		}
	}
	return dis[t];
}
inline int dfs(int x,int in)
{
	if(x==t)return in;
	int out=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[x]+1&&e[i].w)
		{
			int res=dfs(v,min(in,e[i].w));
			in-=res;
			out+=res;
			e[i].w-=res;
			e[i^1].w+=res;
			if(!in)break;
		}
	}
	if(!out)dis[x]=0;
	return out;
}
signed main()
{
	cin>>n>>m;
	s=n*m+1;t=s+1;
	for(int i=1;i<=n;i++)
	{
	    for(int j=1;j<=m;j++)
		{
			cin>>mp[i][j];
			sum+=mp[i][j];
			if((i+j)%2==0)
			{
				add(s,(i-1)*m+j,mp[i][j]);
				for(int k=1;k<=4;k++)
				{
					int xx=i+dx[k];
					int yy=j+dy[k];
					if(xx>=1&&xx<=n&&yy>=1&&yy<=m)
					  add((i-1)*m+j,(xx-1)*m+yy,INF);
				}
			}
			else add((i-1)*m+j,t,mp[i][j]);
		}
	}
	while(bfs())
//	  cout<<ans<<endl,
	  ans+=dfs(s,INF);
	cout<<(sum-ans)<<endl;
	return 0;
}

P4016 负载平衡问题

题目的意思很明确,我们也可以根据标签根据题意判断出这题需要用到最小费用最大流。
首先我们把所有的仓库以当前货物数量为流量给连到超级源点上,把当前所有仓库的点都与他右边的仓库连流量为 INF (保证两个仓库之间可以随意分货物)的边,最后计算出平均值 \(xx\) 把所有的点都以 \(xx\) 为流量向超级汇点连边,直接跑最小费用最大流即可。

code:

#include<bits/stdc++.h>
#define int long long
#define INF 2147483647
#define N 100010
using namespace std;
int n,a[N],cnt=1,head[N],cost[N];
int s,t,mflow,mcost,dis[N],vis[N],sum;
struct sb1{int fa,v;}b[N];
struct sb{int u,v,w,val,next;}e[N];
inline void add(int u,int v,int w1,int w2)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].val=w1;
	e[cnt].w=w2;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline bool bfs()
{
	queue<int>q;
    memset(b,0,sizeof b);
    memset(vis,0,sizeof vis);
    for(int i=0;i<=t;i++)
    	dis[i]=INF;
    dis[s]=0;
    q.push(s);
    vis[s]=1;
    while(!q.empty())
	{
        int u=q.front();
        vis[u]=0;
        q.pop();
        for(int i=head[u];i;i=e[i].next)
		{
            int v=e[i].v,w=e[i].w;
            if(e[i].val>0&&dis[v]>dis[u]+w)
			{
                dis[v]=dis[u]+w;
                b[v].fa=u,b[v].v=i;
                if(vis[v]==0)
				{
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dis[t]!=INF;
}
inline void slove()
{
    while(bfs())
	{
        int minn=INF;
        for(int i=t;i!=s;i=b[i].fa)
			minn=min(minn,e[b[i].v].val);
        for(int i=t;i!=s;i=b[i].fa)
		{
            e[b[i].v].val-=minn;
            e[b[i].v^1].val+=minn;
        }
        mflow+=minn;
        mcost+=minn*dis[t];
//        cout<<mflow<<endl;
    }
    return ;
}
signed main()
{
	cin>>n;
	s=0,t=n+1;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		sum+=a[i];
		add(s,i,a[i],0);
		add(i,s,0,0);
	}
	sum/=n;
	for(int i=1;i<=n;i++)
	{
		int kk;
		if(i==n)kk=1;
		else kk=i+1;
		add(i,kk,INF,1);
		add(kk,i,0,-1);
		add(kk,i,INF,1);
		add(i,kk,0,-1);
	}
	for(int i=1;i<=n;i++)
	{
		add(i,t,sum,0);
		add(t,i,0,0);
	}
	slove();
	cout<<mcost<<endl;
	return 0;
}

P4014 分配问题

好像是最大流,但是最小费用最大流。
我们把每一个人员和源点连起来,流量为 \(1\),花费为 \(0\),把每一个工件和汇点都链接起来,流量为 \(1\),费用为 \(0\)
然后把每一个人员和每一个的工件都给链接起来,流量为 \(1\),花费为 \(c_{ij}\)
欸你这只求了最小总效益啊
把边的花费改成负的重新建一次图再跑一遍不就好了吗。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 100100
using namespace std;
struct sb{int next,to,flow,dis;}e[N];
int n,m,s,t,x,y,z,k,head[N],cnt=1,mp[110][110];
int xb[N],flow[N],dis[N],pre[N],vis[N];
queue<int>q;
inline void add(int x,int y,int z,int cost)
{
    e[++cnt].next=head[x];
    e[cnt].to=y;
    e[cnt].flow=z;
    e[cnt].dis=cost;
    head[x]=cnt;
}
inline int bfs(int s,int t)
{
    memset(flow,INF,sizeof flow);
    memset(dis,INF,sizeof dis);
    memset(vis,0,sizeof vis);
    q.push(s);
	vis[s]=1;
	dis[s]=0;
	pre[t]=-1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int v=e[i].to; 
            if(e[i].flow>0&&dis[v]>dis[u]+e[i].dis)
            {
                dis[v]=dis[u]+e[i].dis;
                pre[v]=u;
                xb[v]=i;
                flow[v]=min(flow[u],e[i].flow);
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v); 
                }
            }
        }
    }
    return dis[t]!=INF;
}
int dfs()
{
    int ans=0;
    while(bfs(s,t))
    {
        int k=t;
        ans+=flow[t]*dis[t];
        while(k!=s)
        {
            e[xb[k]].flow-=flow[t];
            e[xb[k]^1].flow+=flow[t];
            k=pre[k];
        }
    }
    return ans;
}
signed main()
{
    cin>>n;
    s=0,t=n*2+1;
    memset(head,-1,sizeof head);
    for(int i=1;i<=n;i++)
    {
		add(0,i,1,0);
		add(i,0,0,0);
	}
    for(int i=n+1;i<=2*n;i++)
	{
		add(i,t,1,0);
		add(t,i,0,0);
	}
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
    	{
    		cin>>mp[i][j];
    		add(i,n+j,1,mp[i][j]);
    		add(n+j,i,0,-mp[i][j]);
        }
    cout<<dfs()<<endl;
    memset(e,0,sizeof e);
    memset(xb,0,sizeof xb);
    memset(pre,0,sizeof pre);
    memset(head,-1,sizeof head);
    cnt=1;
    for(int i=1;i<=n;i++)
    {
		add(0,i,1,0);
		add(i,0,0,0);
	}
    for(int i=n+1;i<=2*n;i++)
	{
		add(i,t,1,0);
		add(t,i,0,0);
	}
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
    	{
    		add(i,n+j,1,-mp[i][j]);
    		add(n+j,i,0,mp[i][j]);
        }
    cout<<(-dfs())<<endl;
    return 0;
}

P4015 运输问题

这个题目和上面的差不多,不再多说。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,mp[110][110],dis[N],head[N],cnt=1;
int s,t,vis[N],A[N],B[N],flow[N],pre[N],xb[N];
struct sb{int v,w,val,next;}e[N];
inline void add(int u,int v,int z,int val)//w是流量,val是花费 
{
	e[++cnt].v=v;
	e[cnt].val=val;
	e[cnt].w=z;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline int bfs()
{
	queue<int>q;
    memset(vis,0,sizeof vis);
    memset(dis,INF,sizeof dis);
    memset(flow,INF,sizeof flow);
    dis[s]=0;
    q.push(s);
    vis[s]=1;
    while(!q.empty())
	{
        int u=q.front();
        vis[u]=0;
        q.pop();
        for(int i=head[u];i;i=e[i].next)
		{
            int v=e[i].v,w=e[i].val;
            if(e[i].w&&dis[v]>dis[u]+w)
			{
                dis[v]=dis[u]+w;
            	pre[v]=u,xb[v]=i;
            	flow[v]=min(flow[u],e[i].w);
                if(vis[v]==0)
				{
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dis[t]!=INF;
}
inline int dfs()
{
	int ans=0;
	while(bfs())
	{
//		cout<<"cao"<<endl;
		ans+=dis[t]*flow[t];
		int k=t;
		while(k!=0)
		{
			e[xb[k]].w-=flow[t];
			e[xb[k]^1].w+=flow[t];
			k=pre[k];
		}
//		cout<<ans<<endl;
	}
	return ans;
}
inline void build(int x)
{
	for(int i=1;i<=n;i++)
		add(s,i,A[i],0),
		add(i,s,0,0);
	for(int i=1;i<=m;i++)
		add(i+n,t,B[i],0),
		add(t,i+n,0,0);
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	    add(i,j+n,INF,mp[i][j]*x),
	    add(j+n,i,0,-mp[i][j]*x);
}
signed main()
{
	cin>>n>>m;
	s=0,t=n+m+1;
	for(int i=1;i<=n;i++)
	  cin>>A[i];
	for(int i=1;i<=m;i++)
	  cin>>B[i];
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	    cin>>mp[i][j];
	build(1);
	cout<<dfs()<<endl;
	memset(head,0,sizeof head);
	cnt=1;
	build(-1);
	cout<<(-dfs())<<endl;
	return 0;
}

P2764 最小路径覆盖问题

首先我们需要知道一个定理:最小覆盖=点数-最大流。
这个很好证明。因为当点之间没有边时,路径数=点数-\(0\)=点数;当有一条边 \((x,y)\) 时,相当于把这两个点合在一起,所以路径数=点数-1;以此类推。
然后就是改如何建图了。
我们知道如果要是直接把这些点连到源点起来然后再都连到汇点的话,跑最大流中间点与点之间的边是不会走到的,所以我们想到了拆点,把一个点拆成两个,这样每一个点都会被标记上谁到谁,保证中间的点与点之间的边是走过的,也就是说,如果要是有从某个点到某个点的关系的话,指向某一个点的所有路径流量都是 \(1\) 的话,说明没有点在他前面,他就是起点,这样的点的数目就是路径数量,也就是上面的点数-最大流。
最后输出路径的时候用两个数组存一下就好了。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,S,T,head[N],cnt=1,dis[N];
int s[N],t[N],pf[N],vis[N];
struct sb{int u,v,w,next;}e[N<<2];
inline void add(int u,int v,int w)
{
	e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].u=v;e[cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
//	cout<<"cao"<<endl;
	memset(dis,0,sizeof dis);
	queue<int>q;
	q.push(S);
	dis[S]=1;
	while(!q.empty())
	{
//		cout<<"cao"<<endl;
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next)
		{
//			cout<<"cao"<<endl;
			int v=e[i].v;
			if(e[i].w&&!dis[v])
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T];
}
inline int dfs(int x,int in)
{
//	cout<<"FUCK"<<endl;
	if(x==T)return in;
	int out=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(out==in)return in;
		if(dis[v]==dis[x]+1&&e[i].w)
		{
			int res=dfs(v,min(e[i].w,in-out));
			e[i].w-=res;
			e[i^1].w+=res;
			out+=res;
			if(res!=0&&x!=S&&v!=T)
			{
				s[x]=v-n;
				t[v-n]=x;
			}
		}
	}
	if(!out)dis[x]=0;
	return out;
}
void dfs(int x)
{
	if(x==0)return ;
	if(t[x]!=x)dfs(t[x]);
	vis[x]=1;
	cout<<x<<" ";
	return ;
}
inline int dinic()
{
	int ans=0;
	while(bfs())
//	  cout<<ans<<endl,
	  ans+=dfs(S,INF);
	return ans;
}
signed main()
{
//	freopen("P2764_2.in","r",stdin);
//	freopen("551.out","w",stdout);
	cin>>n>>m;
	S=0;T=2*n+1;
	for(int i=1;i<=n;i++)
		s[i]=t[i]=i;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y+n,1);
	}
	for(int i=1;i<=n;i++)
	{
		add(S,i,1);
		add(i+n,T,1);
	}
	int ans=dinic();
	for(int i=n;i;i--)
	{
		if(s[i]==i&&vis[i]==0)
		{
			dfs(i);
			cout<<endl;
		}
	}
	cout<<(n-ans)<<endl;
	return 0;
}

P3355 骑士共存问题

和方格取数的题是差不多的,建图方式也是。
同样跟据横纵坐标和奇偶性的不同来建图,互斥的点流量设为 INF。
注意一开始的有障碍的点不能放骑士,建图的时候过滤掉,然后输出答案的时候减去 \(m\)

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1001000
using namespace std;
int n,m,s,t,dis[N],mp[210][210],cnt=1,head[N];
int dx[]={0,1,1,-1,-1,2,2,-2,-2};
int dy[]={0,2,-2,2,-2,1,-1,1,-1};
struct sb{int v,w,next;}e[N];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
//	cout<<"cao"<<endl;
	memset(dis,0,sizeof dis);
	queue<int>q;
	q.push(s);
	dis[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v]&&e[i].w)
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[t];
}
inline int dfs(int x,int in)
{
	if(x==t)return in;
	int out=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[x]+1&&e[i].w)
		{
			int res=dfs(v,min(e[i].w,in));
			out+=res;
			in-=res;
			e[i].w-=res;
			e[i^1].w+=res;
			if(in==0)break;
		}
	}
	if(out==0)dis[x]=0;
	return out;
}
int main()
{
//	freopen("P3355_3.in","r",stdin);
	cin>>n>>m;
	s=n*n+1;t=n*n+2;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		mp[x][y]=1;
//		mp[y][x]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(mp[i][j])continue;
			if((i+j)%2==0)
			{
				add(s,(i-1)*n+j,1);
				for(int k=1;k<=8;k++)
				{
					int xx=i+dx[k];
					int yy=j+dy[k];
					if(mp[i][j]||xx<1||xx>n||yy<1||yy>n)continue;
					add((i-1)*n+j,(xx-1)*n+yy,INF);
//					add((xx-1)*n+yy,(i-1)*n+j,INF);
				}
			}
			else add((i-1)*n+j,t,1);
		}
	}
	int sum=0;
	while(bfs())
//	  cout<<sum<<endl,
	  sum+=dfs(s,INF);
	cout<<(n*n-sum-m)<<endl;
	return 0;
}

P1251 餐巾计划问题

这道题目我们把每一天拆成晚上和早上两个点。
首先我们可以对于每一个晚上的点,从源点连一条边,容量为当天产生的脏餐巾,费用是零(因为脏餐巾的产生不需要花费)。
然后我们从每一天的早上向汇点连一条边,如果要是流满了就代表当前白天的餐巾已经够用了。
从每一天晚上向下一天晚上连一条边,流量为 INF ,表示脏餐巾可以留到第二天,INF的原因是可以将任意数量的脏餐巾给留到下一天。
从每一天晚上向第 \(t1\) 天后的早上连一条边,容量为INF,花费为 \(w1\),表示送去快洗店的情况。
从每一天晚上向第 \(t2\) 天后的早上连一条边,容量为INF,花费为 \(w2\),表示送去慢洗店的情况。
从源点向每一天早上连一条容量为INF,花费为 \(ww\) 的边,表示每一天早上购进新的餐巾的情况。
然后就是跑一遍最小费用最大流的模板即可。

code:

#include<bits/stdc++.h>
#define INF 999999999
//#define int long long
#define N 10010
using namespace std;
int n,m,s,t,dis[N],head[N],cnt=1;
int flow[N],vis[N],ww,w1,w2,t1,t2;
long long mcost;
struct sb1{int fa,v;}b[N<<2];
struct sb{int v,w,val,next;}e[N<<2];
inline void add(int u,int v,int w,int val)//w是流量,val是花费 
{
    e[++cnt].v=v;e[cnt].val=val;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
    e[++cnt].v=u;e[cnt].val=-val;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
//  cout<<"cao"<<endl;
    for(int i=1;i<=2*n+2;i++)
    	dis[i]=INF,vis[i]=0;
    queue<int>q;
    q.push(s);
    flow[s]=INF;
    dis[s]=0;
    vis[s]=1;
    while(!q.empty())
    {
//      	cout<<q.front()<<endl;
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].v,w=e[i].val;
//        	cout<<v<<" "<<w<<endl;
            if(e[i].w>0&&dis[v]>dis[u]+w)
            {
                dis[v]=dis[u]+w;
                b[v].fa=u;b[v].v=i;
                flow[v]=min(flow[u],e[i].w);
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dis[t]<INF;
}
inline void slove()
{
    while(bfs())
    {
//      cout<<dis[t]<<endl;
        int k=t;
        while(s!=k)
        {
            e[b[k].v].w-=flow[t];
            e[b[k].v^1].w+=flow[t];
            k=b[k].fa;
        }
//        mflow+=flow[t];
        mcost+=flow[t]*dis[t];
    }
}
signed main()
{
    scanf("%d",&n);
    s=0;t=2*n+1;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        add(s,i,x,0);
        add(i+n,t,x,0);
    }
    scanf("%d%d%d%d%d",&ww,&t1,&w1,&t2,&w2);
    for(int i=1;i<=n;i++)
    {
        if(i+1<=n)add(i,i+1,INF,0);
        if(i+t1<=n)add(i,i+n+t1,INF,w1);
        if(i+t2<=n)add(i,i+n+t2,INF,w2);
        add(s,i+n,INF,ww);
    }
    slove();
    cout<<mcost<<endl;
    return 0;
}

P2761 软件补丁问题

和前面的那个题目一样是状压bfs。
我们发现 n 的数据范围很小,所以我们就可以利用二进制状压一下。
首先我们需要把 \(b1\)\(b2\)\(f1\)\(f2\) 给求出来。然后在bfs的过程中利用位运算操作即可。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
#define N (1<<20)+1
#define M 110
using namespace std;
int n,m,b1[M],b2[M],f1[M],f2[M];
int vis[N],t[M];
struct sb{
	int now,dis;
	bool operator < (const sb &a)const{return this->dis > a.dis;}
}now;	
inline void bfs()
{
	priority_queue<sb>q;
//	priority_queue<sb,vector<sb>, greater<sb> > q;
	q.push(now);
	while(!q.empty())
	{
		sb qwq=q.top();
		q.pop();
		if(qwq.now==0)
		{
			cout<<qwq.dis<<endl;
			return ;
		}
		if(vis[qwq.now])continue;
		vis[qwq.now]=1;
		for(int i=1;i<=m;i++)
		{
			if((qwq.now|b1[i])!=qwq.now||(qwq.now&(~b2[i]))!=qwq.now)continue;
			sb k=qwq;
			k.now&=(~f1[i]);
			k.now|=f2[i];
			k.dis+=t[i];
			q.push(k);
		}
	}
	cout<<"0"<<endl;
	return ;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>t[i];
		char s;
		for(int j=1;j<=n;j++)
		{
			cin>>s;
			if(s=='+')b1[i]|=(1<<(j-1));
			else if(s=='-')b2[i]|=(1<<(j-1));
		}
		for(int j=1;j<=n;j++)
		{
			cin>>s;
			if(s=='-')f1[i]|=(1<<(j-1));
			else if(s=='+')f2[i]|=(1<<(j-1));
		}
	}
	for(int i=1;i<=n;i++)
		now.now|=(1<<(i-1));
	now.dis=0;
//	cout<<now.now<<endl;
	bfs();
	return 0;
}

P4009 汽车加油行驶问题

怎么啥都能用网络流

首先我们看到题目中有两个很棘手的问题那就是如何处理油箱内的油 \(K\),我们可以考虑把图搞成三维的,在 \(x\)\(y\) 的基础上再加一个 \(z\) 轴,代表当前油箱内的剩余油量,比如第 \(k\) 层代表还剩 \(K-k\) 份油,然后我们建一个超级源点,由于一开始肯定是从满油的状态从 \((1,1)\) 开始走,所以我们就把这个点与源点连起来;然后到每一层的 \((n,n)\) 都是结束,所以我们把每一层的这个点连到源点上,然后再分两种情况讨论;一是当前点是加油站,会被强行加满,所以我们把每一层的这个点都给连到第一层的这个点,然后再进行上下左右的加边,(注意横纵坐标减一移动时有 \(B\) 的花费),二是对于所有的不是加油站的点,每一层都可以向油量减一的那一层上下左右连边(除油量为 \(0\) 的那一层),然后我们对于所有的点,都可以花钱建个加油站自给自足,所以最后再连一条边。

然后直接最小费用最大流输出答案就可了。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
#define N 1001000
using namespace std;
int n,k,A,B,C,dis[N],vis[N],cnt=1,s,t,flow[N];
int mp[110][110],mcost,head[N];
struct sb1{int fa,v;}b[N<<2];
struct sb{int v,next,w,val;}e[N<<2];
inline int wz(int x,int y,int z){return n*n*(z-1)+(x-1)*n+y;}
inline void add(int u,int v,int w,int val)
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
	memset(dis,INF,sizeof dis);
	queue<int>q;
	q.push(s);
	vis[s]=1;
	flow[s]=INF;
	dis[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].val;
			if(e[i].w>0&&dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				b[v].fa=u;b[v].v=i;
				flow[v]=min(flow[u],e[i].w);
				if(!vis[v])
				{
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return dis[t]<INF;
}
inline void solve()
{
	while(bfs())
	{
		int k=t;
		while(k!=s)
		{
			e[b[k].v].w-=flow[t];
			e[b[k].v^1].w+=flow[t];
			k=b[k].fa;
		}
		mcost+=dis[t]*flow[t];
	}
}
signed main()
{
	cin>>n>>k>>A>>B>>C;
	s=0,t=n*n*(k+1)+1;
	add(s,wz(1,1,1),1,0);
	for(int i=2;i<=k+1;i++)
		add(wz(n,n,i),t,1,0);
	for(int i=1;i<=n;i++)
	{
	  for(int j=1;j<=n;j++)
	  {
	  	int x;
	  	cin>>x;
//	  	cout<<"cao"<<endl;
	  	if(x==1)
	  	{
	  		for(int o=2;o<=k+1;o++)
			  add(wz(i,j,o),wz(i,j,1),1,A);
	  		if(i<n)add(wz(i,j,1),wz(i+1,j,2),1,0);
	  		if(j<n)add(wz(i,j,1),wz(i,j+1,2),1,0);
	  		if(i>1)add(wz(i,j,1),wz(i-1,j,2),1,B);
	  		if(j>1)add(wz(i,j,1),wz(i,j-1,2),1,B);
		}
		else 
		{
			for(int o=1;o<=k;o++)
			{
				if(i<n)add(wz(i,j,o),wz(i+1,j,o+1),1,0);
				if(j<n)add(wz(i,j,o),wz(i,j+1,o+1),1,0);
				if(i>1)add(wz(i,j,o),wz(i-1,j,o+1),1,B);
				if(j>1)add(wz(i,j,o),wz(i,j-1,o+1),1,B);
			}
			add(wz(i,j,k+1),wz(i,j,1),1,A+C);
		}
	  }
	}
	solve();
	cout<<mcost<<endl;
	return 0;
}

P2765 魔术球问题

什么 jb 题目真 jb 恶心。
首先我们知道我们可以把每一个点拆成两个分别与源点汇点相连,因为要是一个与源点和汇点相连的话跑最大流没有意义。
其次,我们知道两个编号和为完全平方数的可以连边,但是要统一一下,比如从第一部分的当前点连向第二部分的其他点。
建完图之后就会发现转化成了求最小路径覆盖问题。
但是题目正好是反着的,所以我们边跑最大流边加球,具体注释在代码里。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
//#define int long long
#define endl '\n'
#define N 1001000
#define M 5000
using namespace std;
int n,dis[N],cnt=1,head[N],sum;
int ans,num,s,t,vis[N],re[N],pre[N];
struct sb{int v,w,next;}e[N<<2];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
bool bfs()
{
	memset(dis,0,sizeof dis);
	dis[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
//		cout<<u<<endl;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v]&&e[i].w)
			  dis[v]=dis[u]+1,q.push(v);
		}
	}
	return dis[t];
}
int dfs(int x,int in)
{
	if(x==t)return in;
	int out=0;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[x]+1&&e[i].w)
		{
			int res=dfs(v,min(e[i].w,in));
			e[i].w-=res;
			e[i^1].w+=res;
			in-=res;
			out+=res;
			if(v!=t)pre[x>>1]=v>>1;//如果要是当前点不是终点就存一下下一个点 
			if(!in)break;
		}
	}
	if(out==0)dis[x]=0;
	return out;
}
inline int dinic()
{
	int cao=0;
	while(bfs())
//		cout<<cao<<endl,
		cao+=dfs(s,1e9);
	return cao;
}
signed main()
{
	cin>>n;
	s=M*2+1,t=s+1;
	while(ans<=n)//找到最少覆盖路径数为n的时候退出 
	{
		num++;//当前球的数量加一 
		add(s,num<<1,1);//×2的目的是把一个点拆成两个,这是源点向第一部分的点连边 
		add((num<<1)|1,t,1);//第二部分的点向汇点连边 
		int GG=sqrt(num)+1; //从每一个数的根号向下取整开始枚举 
		for(int i=GG;i*i<2*num;i++)//枚举每一个能与之构成完全平方数的i
//		  if(i*i==2*num)如果不是<2*num的话,可能会出现自己两部分的点相连或者后面有两个重复的情况 
//		    cout<<num<<": "<<i*i<<endl,
//		  cout<<num<<":"<<((i*i-num))<<endl,
		  add((i*i-num)<<1,(num<<1)|1,1);//第一部分的i方-num与第二部分的num点建边 
		int kk=dinic();
		if(!kk)re[++ans]=num;//如果当前没有增加流量就说明当前的球需要新开一个柱子 
//		cout<<kk<<" "<<ans<<" "<<num<<endl;
	}
	cout<<(--num)<<endl;
	for(int i=1;i<=n;i++)
	{
		if(vis[re[i]])continue;//如果当前答案的值已经访问过就跳过 
		int x=re[i];
		vis[x]=1;
		while(x!=0)
		{
			printf("%d ",x);
			x=pre[x];
			vis[x]=1;
		}
		cout<<endl;
	}
	return 0;
}

P4013 数字梯形问题

我们发现边不会相交只会重复。
第一问:边和点都不能重合,那就拆点建图,然后所有的点的边最大流量都是1
第二问:边不能重合,点可以,点可以重合,我们可以不用拆点了,然后我们所有的最后一层的点到源点的边流量都是INF
第三问:边和点都可以重合,我们可以在点与点之间的边流量都设为INF,这样边也能重合了

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 5010
using namespace std;
int n,m,s,t,mcost,mflow,ff[N],p[110][110],mp[110][110];
int cnt=1,head[N],dis[N],flow[N],pre[N],vis[N],id;
struct sb{int v,next,w,val;}e[N];//w是流量,val是花费
inline void add(int u,int v,int w,int val)
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline void qk()
{
	cnt=1;
	memset(head,0,sizeof head);
	memset(pre,0,sizeof pre);
	memset(ff,0,sizeof ff);
	memset(e,0,sizeof e);
	mcost=0;mflow=0;	
}
inline int SPFA()
{
	queue<int>q;
	memset(dis,INF,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;
	vis[s]=1;
	q.push(s);
	flow[s]=INF;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].val;
			if(e[i].w&&dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				flow[v]=min(flow[u],e[i].w);
				ff[v]=u;
				pre[v]=i;
				if(!vis[v])
				{
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return dis[t]!=INF;
}
inline void EK()
{
	while(SPFA())
	{
		int k=t;
		while(k!=s)
		{
			int i=pre[k];
			e[i].w-=flow[t];
			e[i^1].w+=flow[t];
			k=ff[k];
		}
		mflow+=flow[t];
		mcost+=flow[t]*dis[t];
	}
}
signed main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m+i-1;j++)
		{
			cin>>mp[i][j];//存点权 
			p[i][j]=++id;//存编号 
		}
	}
	qk();
	s=0;t=id*2+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m+i-1;j++)
		{
			add(p[i][j],p[i][j]+id,1,-mp[i][j]);//每一个点与自己拆出来的点相连 
			if(p[i+1][j+1])add(p[i][j]+id,p[i+1][j+1],1,0);//每一个点与自己的右下方的点相连因为不能重复所以流量是1 
			if(p[i+1][j])add(p[i][j]+id,p[i+1][j],1,0);//每一个点与自己的左下方的点相连 
		}
	}
	for(int i=1;i<=m;i++)
		add(s,p[1][i],1,0);//起点与源点相连 
	for(int i=1;i<=m+n-1;i++)
		add(p[n][i]+id,t,1,0);//终点与汇点相连 
	EK();
	cout<<(-mcost)<<endl;
	qk();
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m+i-1;j++)
		{//点可以重复就不拆点了 
			if(p[i+1][j+1])add(p[i][j],p[i+1][j+1],1,-mp[i][j]);//边不能重复所以流量还是1 
			if(p[i+1][j])add(p[i][j],p[i+1][j],1,-mp[i][j]);
		}
	}
	for(int i=1;i<=m;i++)
		add(s,p[1][i],1,0);
	for(int i=1;i<=m+n-1;i++)
		add(p[n][i],t,INF,-mp[n][i]);//因为点可以重复所以最大流量是INF 
	EK();
	cout<<(-mcost)<<endl;
	qk();
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m+i-1;j++)
		{
			if(p[i+1][j+1])add(p[i][j],p[i+1][j+1],INF,-mp[i][j]);//边也可以重复了 
			if(p[i+1][j])add(p[i][j],p[i+1][j],INF,-mp[i][j]);//流量设为INF 
		}
	}
	for(int i=1;i<=m;i++)
		add(s,p[1][i],1,0);
	for(int i=1;i<=m+n-1;i++)
		add(p[n][i],t,INF,-mp[n][i]);
	EK();
	cout<<(-mcost)<<endl;
	return 0;
}

P4012 深海机器人问题

给你一个网格图,每一条网格边上都有边权,给你 \(a\) 个起点,每一个起点可以有 \(A_{i}\) 个人从这里走;给你 \(b\) 个终点,每一个终点最多可以有 \(B_{i}\) 个人在这里结束行程,问你最大的边权和。

考虑最大费用最大流。

我们需要弄清楚的一点就是题目里说了机器人只能向北或者向东走,也就是说只会从左下角往右上角走,我们把他给反过来,让他从左上角往右下角走,也就是只能向下或者向右。

我们可以看到输入中是 \(Q\)\(P\),但由于是从 \((0,0)\) 开始的,所以实际的点数是 \((Q+1)\times(P+1)\) 的,在给点编号的时候需要注意一下。首先是把横着的边给存起来,这个时候我们根据上面说的只能往下或往右走,我们知道当前横着边是链接了他左右两个端点,所以我们直接把他给存起来。但是有一个问题就是一个标本只有一个,也就是说后面的机器人来这里的话是不会对答案产生贡献,也就是得到的价值为 \(0\),这样的话,我们在存边的时候存一条流量为 \(1\),花费为当前网格边边权的边,表示只有一个机器人可以采到标本,然后再存一条流量为 \(\infty\),花费为 \(0\) 的边,表示其他经过这条网格边的机器人都不会采到标本。同理我们把后面的那些竖边也处理出来(在枚举的时候要注意横着的边一共是 \(Q+1\) 行,但是一行只有 \(P\) 条边,因为两点之间的才是边,同理竖边一共是 \(P+1\) 列,但是一列只有 \(Q\) 条边)。

然后我们来处理每一个起点和每一个终点。

我们可以发现每一个起点和终点都有机器人个数限制,比如第 \(i\) 个起点最多有 \(A_{i}\) 个机器人从这里出发,所以我们从超级源点向每一个起点都连一条流量为 \(A_{i}\),花费为 \(0\) 的边,这样就解决了每一个起点机器人数量的限制;同理也可以这样来限制到达每一个终点的机器人的数量,所以我们在建完图之后直接跑最小费用最大流(在存边的时候把边权取个相反数就转化为最小费用最大流了)就好了。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 100010
using namespace std;
int cnt=1,head[N],n,m,s,t,a,b,mcost;
int dis[N],vis[N],flow[N],pre[N],ff[N];
struct sb{int v,w,val,next;}e[N];
inline int bh(int x,int y){return x*(m+1)+y+1;}//求出编号 
inline void add(int u,int v,int w,int val)//W是流量,val是花费 
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int SPFA()
{
	queue<int>q;
	memset(dis,INF,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;
	vis[s]=1;
	flow[s]=INF;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].val;
			if(e[i].w&&dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				pre[v]=i;
				ff[v]=u;
				flow[v]=min(flow[u],e[i].w);
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return dis[t]!=INF;
}
inline void EK()
{
	while(SPFA())
	{
		int k=t;
		while(k!=s)
		{
			e[pre[k]].w-=flow[t];
			e[pre[k]^1].w+=flow[t];
			k=ff[k];
		}
		mcost+=dis[t]*flow[t];
	}
}
signed main()
{
	cin>>a>>b>>n>>m;
	s=0;t=N-1;//起点和终点编号 
	for(int i=0;i<=n;i++)//枚举所有横着的边 
	{
		for(int j=0;j<m;j++)//一行m条 
		{
			int x;
			cin>>x;
			add(bh(i,j),bh(i,j+1),1,-x);//将当前点和下一个点链接起来,花费是-x因为要求的是最大花费最大流,因为标本只能采一次所以流量是1 
			add(bh(i,j),bh(i,j+1),INF,0);//这个是后面的边,标本没了花费为0,可以无限重复走,所以流量是INF 
		}
	}
	for(int i=0;i<=m;i++)//同理枚举所有竖着的边 
	{
		for(int j=0;j<n;j++)
		{
			int x;
			cin>>x;
			add(bh(j,i),bh(j+1,i),1,-x);
			add(bh(j,i),bh(j+1,i),INF,0);
		}
	}
	for(int i=1;i<=a;i++)//这个是枚举每一个起点, 
	{
		int x,y,z;
		cin>>z>>x>>y;
		add(s,bh(x,y),z,0);
	}
	for(int i=1;i<=b;i++)
	{
		int x,y,z;
		cin>>z>>x>>y;
		add(bh(x,y),t,z,0);
	}
	EK();
	cout<<(-mcost)<<endl;
	return 0;
}

Q:为什么这一篇这么详细?
A:因为我从我洛谷的题解搬过来的。

P3356 火星探险问题

这道题目和上面那道的思路是差不多的,到那时要输出路径并且是有障碍的。

首先我们和上面那道题一样,先用一个双重循环赋个编号,然后这个题目是点到点,需要拆点,把第一部分的起点和第二部分的终点分别连向源点和汇点,然后我们开始读入地图信息,如果要是 2 的话就表示当前点是有石头标本的,我们就可以从第一部分向第二部分连一条花费为 1,容量为 1 的边,因为只能拿一次。对于是 1 的点,前面我们存了编号,直接用一维数组来标记当前点是障碍,然后不进行对自己的建边,表示当前点不能走,然后对于剩下的点,都建花费为 0,容量为 \(\infty\) 的边即可,表示可以无限经过。

然后我们开始对于所有的点向自己的右边和下边的点连边,跑一边费用流,然后开始进行路径的输出,我参考了题解里面的思路,建一个新图然后dfs。

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,cnt=1,kk,s,t,mcost,head[N],dis[N],vis[N];
int pre[N],ff[N],mp[110][110],v[N],flow[N],G;
struct sb{int v,w,val,next;}e[N];
inline void add(int u,int v,int w,int val)//W是流量,val是花费 
{
	e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
	e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int SPFA()
{
	queue<int>q;
	memset(dis,INF,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;
	vis[s]=1;
	flow[s]=INF;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].val;
			if(e[i].w&&dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				pre[v]=i;
				ff[v]=u;
				flow[v]=min(flow[u],e[i].w);
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return dis[t]!=INF;
}
inline void EK()
{
	while(SPFA())
	{
		int k=t;
		while(k!=s)
		{
			e[pre[k]].w-=flow[t];
			e[pre[k]^1].w+=flow[t];
			k=ff[k];
		}
		mcost+=dis[t]*flow[t];
	}
}
inline void print(int id,int x)
{
	int temp=x-2*n*m;
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].v,w=e[i].w;
		if(!w)continue;
		int tt=v-2*n*m;
		if(temp+1==tt)cout<<id<<" 1"<<endl;
		else cout<<id<<" 0"<<endl;
		e[i].w--;
		print(id,v);
		break;
	}
}
signed main()
{
	cin>>kk>>m>>n;
	s=3*n*m+1;t=s+1;
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	    mp[i][j]=++G;
	add(s,1,kk,0);
	add(2*n*m,t,kk,0);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>G;
			if(G==2)add(mp[i][j],mp[i][j]+n*m,1,-1);
			if(G==1)v[mp[i][j]]=1;
			else add(mp[i][j],mp[i][j]+n*m,INF,0);
		}
	}
	for(int i=1;i<=n;i++)
	  for(int j=2;j<=m;j++)
	    add(mp[i][j-1]+n*m,mp[i][j],INF,0);
	for(int i=2;i<=n;i++)
	  for(int j=1;j<=m;j++)
	    add(mp[i-1][j]+n*m,mp[i][j],INF,0);
	EK();
	for(int i=n*m+1;i<n*m*2;i++)
	{
		if(v[i-n*m])continue;
		for(int j=head[i];j;j=e[j].next)
		{
			int w=e[j].w,v=e[j].v;
			if(v+n*m==i)continue;
			if(w==INF)continue;
			if(v!=t)add(i+n*m,v+2*n*m,INF-w,0);
		}
	}
	for(int i=1;i<=kk;i++)
	  print(i,n*m*2+1);
	return 0;
}

我要去学二分图了,剩下的以后来复习网络流的时候补完。

posted @ 2023-02-24 21:06  北烛青澜  阅读(30)  评论(0编辑  收藏  举报