网络流基础题

网络流基础题

数字梯形问题

题目链接
这道题的三个操作,感觉比较全面的包括了建图的想法,也算比较基础的网络流入门题。对理解网络流应该比较有帮助吧
题目描述
给定一个由 n n n 行数字组成的数字梯形如下图所示。
在这里插入图片描述
梯形的第一行有 m m m 个数字。从梯形的顶部的 m m m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。

分别遵守以下规则:

1.从梯形的顶至底的 m m m 条路径互不相交;
2.从梯形的顶至底的 m m m 条路径仅在数字结点处相交;
3.从梯形的顶至底的 m m m 条路径允许在数字结点相交或边相交。

输入格式
1 1 1 行中有 1 1 1 个正整数 m m m n n n,分别表示数字梯形的第一行有 m m m 个数字,共有 n n n 行。接下来的 n n n 行是数字梯形中各行的数字。

1 1 1 行有 m m m 个数字,第 2 2 2 行有 m + 1 m+1 m+1 个数字,以此类推。

输出格式
将按照规则 1 1 1,规则 2 2 2,和规则 3 3 3 计算出的最大数字总和并输出,每行一个最大总和。

输入输出样例

输入 #1
2 5
2 3
3 4 5
9 10 9 1
1 1 10 1 1
1 1 10 12 1 1
输出 #1
66
75
77

分析
操作1: m条路径互不相交,其实就是每个点只能走一次。(拆点)
操作2: 在操作1的基础上,取消了只能走一次的限制,那就不需要拆点正常连流量为1(边不可以相交)的边即可。但是要注意最后一排的点流向超级汇点(路径到达最后一排可以重复)。
操作3: 就是完全没有限制,点边都可以重复,把互相的流量改成INF即可。

关于拆点的问题:我们把一个顶点分成A,B两个点,并且将A点和B点之间连上一条流量为1的边,把A作为流入点,B作为流出点,那么如果有多条路径流向点A的话,最终也只有一条会流出点B,通过拆点就可以限制每个点只会被一条路径流过。
在这里插入图片描述如上图所示,如果点1和点2同时流向点3,那么因为拆点限流的关系,最后结果只会计算一条路径。

然后就是具体的建图方法:
很简单 看代码就懂了

1.建立一个超级原点 s s s,连向每个第一排的点(流量为1,费用为0)
2.然后每一层都向下一层能到的地方连边(流量看操作,费用为点权)
3.操作一额外多了一个拆点,所以记录了一个 o o o表示所有点数,到时候拆点就加个 o o o就行
4.所有最后一层的点连向超级汇点 t t t(流量为INF,费用为点权)

三个操作分开写,每次要记得初始化。

AC代码
不会有人只看这个吧

#include<bits/stdc++.h>
#define pll pair<int,int>
#define pb push_back
#define FAST std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define ll long long
#define INF 1e18
#define inf 0x3f3f3f3f
#define pi acos(-1)
//#define ls now<<1
//#define rs now<<1|1
using namespace std;
const int mod = 1e9 + 7;
const int MAXN = 1e7 + 10;
const int N = 5e3 + 10;
int a[44][44],b[44][44];
int n,m,s,t;//从这里开始都是模板
struct node{
	int t,nex;
	ll w,c;
}e[MAXN];
ll ans,cost;
int head[N],cnt=1;
void add(int a,int b,ll c,ll d)
{
	e[++cnt].t=b;e[cnt].nex=head[a];e[cnt].w=c;	e[cnt].c=d;head[a]=cnt;
	e[++cnt].t=a;e[cnt].nex=head[b];e[cnt].w=0;e[cnt].c=-d;head[b]=cnt;
}
ll st[N];
int pre[N];
bool vs[N];
bool spfa() 
{
	queue<int>q;
	for(int i=0;i<=t;i++)
	{
		vs[i]=0;st[i]=-INF;pre[i]=-1;
	}
	q.push(s);st[s]=0;vs[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();vs[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nex)
		{
			int v=e[i].t;
			if(st[v]<st[u]+e[i].c&&e[i].w)
			{
				st[v]=st[u]+e[i].c;pre[v]=i;
				if(!vs[v]){vs[v]=1;q.push(v);}
			}
		}
	}
	if(st[t]==-INF)return false;
	ll d=INF;
	for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        d=min(d,e[i].w);
    }
    for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        e[i].w-=d;
        e[i^1].w+=d;
        cost+=e[i].c*d;
    }
    ans+=d;
    return true;
}//到这里开始建图
int o;
void op1()
{
	memset(head,-1,sizeof(head));cnt=1;
	s=0;t=o*2+1;
	for(int i=1;i<=n;i++)add(s,b[1][i],1,0);
	for(int i=1;i<=n+m-1;i++)add(b[m][i]+o,t,1,0);
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n+i-1;j++)
		{
			add(b[i][j],b[i][j]+o,1,a[i][j]);
		}
	}
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<=n+i-1;j++)
		{
			add(b[i][j]+o,b[i+1][j],1,0);add(b[i][j]+o,b[i+1][j+1],1,0);
		}
	}
	cost=0;ans=0;
	while(spfa());
	cout<<cost<<endl;
}
void op2()
{
	memset(head,-1,sizeof(head));cnt=1;
	s=0;t=o+1;
	for(int i=1;i<=n;i++)add(s,b[1][i],1,0);
	for(int i=1;i<=n+m-1;i++)add(b[m][i],t,INF,a[m][i]);//注意这里是INF
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<=n+i-1;j++)
		{
			add(b[i][j],b[i+1][j],1,a[i][j]);
			add(b[i][j],b[i+1][j+1],1,a[i][j]);
		}
	}
	cost=0;ans=0;
	while(spfa());
	cout<<cost<<endl;
}
void op3()
{
	memset(head,-1,sizeof(head));cnt=1;
	s=0;t=o+1;
	for(int i=1;i<=n;i++)add(s,b[1][i],1,0);
	for(int i=1;i<=n+m-1;i++)add(b[m][i],t,INF,a[m][i]);
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<=n+i-1;j++)
		{
			add(b[i][j],b[i+1][j],INF,a[i][j]);
			add(b[i][j],b[i+1][j+1],INF,a[i][j]);
		}
	}
	cost=0;ans=0;
	while(spfa());
	cout<<cost<<endl;
}
void work() 
{
	cin>>n>>m;
	o=(n*2+m-1)*m/2;//计算总点数
	int cntn=0;
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n+i-1;j++)
		{
			cin>>a[i][j];
			b[i][j]=++cntn;//给每个点加编号
		}
	}
	op1();op2();op3();
}
int main() {//个人习惯分开写
	FAST;
	int t = 1;
	//cin >> t;
	while (t--)work();
	return 0;
}

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

题目链接
题目描述
由于人类对自然资源的消耗,人们意识到大约在 2300 年之后,地球就不能再居住了。于是在月球上建立了新的绿地,以便在需要时移民。令人意想不到的是,2177 年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。

现有 n n n 个太空站位于地球与月球之间,且有 m m m 艘公共交通太空船在其间来回穿梭。每个太空站可容纳无限多的人,而太空船的容量是有限的,第 i i i 艘太空船只可容纳 h i h_i hi 个人。每艘太空船将周期性地停靠一系列的太空站,例如 ( 1 , 3 , 4 ) (1,3,4) (1,3,4)表示该太空船将周期性地停靠太空站 134134134 … 134134134… 134134134。每一艘太空船从一个太空站驶往任一太空站耗时均为 1 1 1。人们只能在太空船停靠太空站(或月球、地球)时上、下船。

初始时所有人全在地球上,太空船全在初始站。试设计一个算法,找出让所有人尽快地全部转移到月球上的运输方案。

输入格式
输入的第一行是三个用空格隔开的整数,分别代表太空站个数 n n n,太空船个数 m m m 和地球上的人数 k k k

2 2 2 到第 ( m + 1 ) (m+1) (m+1) 行,每行给出一艘太空船的信息,第 ( i + 1 ) (i+1) (i+1) 行的第一个整数 h i h_i hi 代表第 i i i 艘太空船可容纳的人数。随后有一个整数 rir_iri​,代表第 iii 艘太空船停靠的站点数。之后有 r i r_i ri 个整数,依次代表该太空船停靠站点的编号 S i , j S_{i, j} Si,j,其中太空站自 1 1 1 n n n 编号,地球编号为 0 0 0,月球编号为 − 1 -1 1

输出格式
输出一行一个整数,代表将所有人转移到月球上的最短用时。若无解则输出 0 0 0
输入输出样例

输入 #1
2 2 1
1 3 0 1 2
1 3 1 2 -1
输出 #1
5
数据范围
1 ≤ n ≤ 13 1 \leq n \leq13 1n13
1 ≤ m ≤ 20 1 \leq m \leq 20 1m20
1 ≤ k ≤ 50 1 \leq k \leq 50 1k50
1 ≤ r i ≤ n + 2 1 \leq r_i \leq n + 2 1rin+2
− 1 ≤ S i , j ≤ n -1 \leq S_{i, j}\leq n 1Si,jn

分析
这道题数据范围很小,我们还是结合上面拆点的思想,将每个点编号,再按时间来拆点,造成一张分层图

我们写一个结构体来存飞船信息(代码17到24行)
记录飞船容量以及流程,vector存每天所在位置,getd找两天的位置

我们考虑天数每增加一天该做的操作:

1.首先上一天肯定要和今天对应的点连边,表示可以停留。(第一个循环)
2.然后飞船会运人,那么就找到飞船上一天的位置和今天所在的位置连边,表示运输(第二个循环)

灵魂画师 呜呜 将就看吧
这里由于输入的时候地球为 0 0 0,月球为 − 1 -1 1,所以我整体加了 2 2 2,保证正整数,所以每一层的节点数就是 n + 2 n+2 n+2
在这里插入图片描述

1.S原点连向day0的地球,边权为K
2.所有天数的月球向T连边,边权为INF,因为到了月球就可以了
3.每新建一天就让上一天连向这一天,边权为INF,表示可以停留(船上不了了)
4.找到每个飞船这一天和上一天的位置,连边,边权为 h i h_i hi(一次只有那么多容量)

那么这题就做完了(doge)
好像还要判无解??
无解其实很简单,每个飞船能到的点用并查集维护,最后看看地球(2)和月球(1)是否在一个集合即可

AC代码

#include<bits/stdc++.h>
#define pll pair<int,int>
#define mp make_pair
#define pb push_back
#define FAST std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define ll long long
#define INF 1e18
#define inf 0x3f3f3f3f
#define pi acos(-1)
//#define ls now<<1
//#define rs now<<1|1
using namespace std;
const int mod = 1e9 + 7;
const int MAXN = 1e6 + 10;
const int N = 2e4 + 10;
int n,m,s,t,k;
struct ship{
	int res,day;
	vector<int>v;
	pll getd(int res)
	{
		return mp(v[(res-1)%day],v[res%day]);
	}
}sh[25];
struct node{
	int t,nex;
	ll w,c;
}e[MAXN];
ll ans,cost;
int head[N],cnt=1;
void add(int a,int b,ll c,ll d)
{
	e[++cnt].t=b;e[cnt].nex=head[a];e[cnt].w=c;	e[cnt].c=d;head[a]=cnt;
	e[++cnt].t=a;e[cnt].nex=head[b];e[cnt].w=0;e[cnt].c=-d;head[b]=cnt; 
}
ll st[N];
int pre[N];
bool vs[N];
bool spfa() 
{
	queue<int>q;
	for(int i=0;i<=t;i++)
	{
		vs[i]=0;st[i]=INF;pre[i]=-1;
	}
	q.push(s);st[s]=0;vs[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();vs[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nex)
		{
			int v=e[i].t;
			if(st[v]>st[u]+e[i].c&&e[i].w)
			{
				st[v]=st[u]+e[i].c;pre[v]=i;
				if(!vs[v]){vs[v]=1;q.push(v);}
			}
		}
	}
	if(st[t]==INF)return false;
	ll d=INF;
	for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        d=min(d,e[i].w);
    }
    for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        e[i].w-=d;
        e[i^1].w+=d;
        cost+=e[i].c*d;
    }
    ans+=d;
    return true;
}
int fa[20];
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void work()
{
	memset(head,-1,sizeof(head));
    cin>>n>>m>>k;
    for(int i=1;i<=n+2;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
    	cin>>sh[i].res>>sh[i].day;
    	for(int j=1;j<=sh[i].day;j++)
    	{
    		int x;
			cin>>x;
    		x+=2;
    		sh[i].v.pb(x);
		}
		int a=find(sh[i].v[0]);
		for(int j=1;j<sh[i].v.size();j++)
		{
			int b=find(sh[i].v[j]);
			if(a!=b)fa[b]=a;
		}
	}
	if(find(1)!=find(2))
	{
		cout<<0<<endl;
		return;
	}
	s=0;t=10000;//t稍微赋大一些,不知道要几天才结束
	add(s,2,INF,0);add(1,t,INF,0);
	for(int day=1;;day++)
	{
		add(1+day*(n+2),t,INF,0);
		for(int i=1;i<=n+2;i++)
		{
			add((day-1)*(n+2)+i,day*(n+2)+i,INF,0);
			//cout<<(day-1)*(n+2)+i<<" "<<(day)*(n+2)+i<<endl;
		}
		for(int i=1;i<=m;i++)
		{
			pll a=sh[i].getd(day);
			add((day-1)*(n+2)+a.first,day*(n+2)+a.second,sh[i].res,0);
			//cout<<(day-1)*(n+2)+a.first<<" "<<day*(n+2)+a.second<<endl;
		}
		while(spfa())
		//cout<<ans<<" "<<day<<endl;
		if(ans>=k)
		{
			cout<<day<<endl;
			return;
		}
	}
}
int main() {
    FAST;
    int t = 1;
    //cin >> t;
    while (t--)work();
    return 0;
}

火星探险问题

题目链接
题目描述
火星探险队的登陆舱将在火星表面着陆,登陆舱内有多部障碍物探测车。登陆舱着陆后,探测车将离开登陆舱向先期到达的传送器方向移动。

探测车在移动中还必须采集岩石标本。每一块岩石标本由最先遇到它的探测车完成采集。每块岩石标本只能被采集一次。岩石标本被采集后,其他探测车可以从原来岩石标本所在处通过。探测车不能通过有障碍的地面。

本题限定探测车只能从登陆处沿着向南或向东的方向朝传送器移动,而且多个探测车可以在同一时间占据同一位置。如果某个探测车在到达传送器以前不能继续前进,则该车所采集的岩石标本将全部损失。

用一个 p × q p \times q p×q 网格表示登陆舱与传送器之间的位置。登陆舱的位置在 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 处,传送器的位置在 ( x p , y q ) (x_p,y_q) (xp,yq) 处。
在这里插入图片描述给定每个位置的状态,计算探测车的最优移动方案,使到达传送器的探测车的数量最多,而且探测车采集到的岩石标本的数量最多。

输入格式
第一行为探测车数 n n n,接下来两行分别为 p , q p,q p,q

接下来的 q q q 行是表示登陆舱与传送器之间的位置状态的 p × q p \times q p×q网格。
用三种数表示火星表面位置的状态:0 表示平坦无障碍,1 表示障碍,2 表示石块。

输出格式
每行包含探测车号和一个移动方向,0 表示向南移动,1 表示向东移动。

输入输出样例

输入 #1
2
10
8
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 0 0 0
0 0 0 1 0 2 0 0 0 0
1 1 0 1 2 0 0 0 0 1
0 1 0 0 2 0 1 1 0 0
0 1 0 1 0 0 1 1 0 0
0 1 2 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0
输出 #1
1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 0
1 0
1 0
2 1
2 1
2 1
2 1
2 0
2 0
2 0
2 0
2 1
2 0
2 0
2 1
2 0
2 1
2 1
2 1
数据范围
1 ≤ n , p , q ≤ 35 1 \le n,p,q \le 35 1n,p,q35

分析
典型的拆点跑费用流
建图思路很简单

1.拆点,两个点连上流量为INF的边,当这个点有岩石时在连一条流量为1费用为1的边,表示岩石只能采集一次但可以多次通过这个点。
2.只能向下和向右走,所以如果能往两边走(另一边不为死路)就连流量为INF费用为0的边
3.(1,1)不是死路就由S流向,(n,n)不是死路就流向T。流量为INF费用为0

建图跑费用流
完结撒花 好像题目不是算最大费用emmmmmm

输出路径的话,首先我们想的话,路径的条数肯定就是最大流。
每个机器人走其中一条路径,那么我们就从1开始遍历到最大流

思考如果机器人走了这条路径,那么路径会有什么变化,首先正向边的流量减少1,反向边的流量就是加上1,然而反向边一开始的流量就是0。
所以如果最大流走的是这一条边的话,那么反向边就不为0,那我们就找反向边权不为0的边走,走过之后让反向边的边权减少1。
然后dfs遍历即可(代码70到91行)
注意点:

1.我们每次都是遍历拆点后的B点。注意点的编号pos
2.如果遍历到S,T和他的A点,边权也不为0,但我们不会在遍历下去,所以直接continue;

这下是真的AC了

AC代码

#include<bits/stdc++.h>
#define pll pair<int,int>
#define pb push_back
#define FAST std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define ll long long
#define INF 1e18
#define inf 0x3f3f3f3f
#define pi acos(-1)
#define debug(x) cout<<x<<endl
//#define ls now<<1
//#define rs now<<1|1
using namespace std;
const int mod = 1e9 + 7;
const int MAXN = 1e7 + 10;
const int N = 5e3 + 10;
int n,m,s,t;
struct node{
	int t,nex;
	ll w,c;
}e[MAXN];
ll ans,cost;
int head[N],cnt=1;
void add(int a,int b,ll c,ll d)
{
	e[++cnt].t=b;e[cnt].nex=head[a];e[cnt].w=c;	e[cnt].c=d;head[a]=cnt;
	e[++cnt].t=a;e[cnt].nex=head[b];e[cnt].w=0;e[cnt].c=-d;head[b]=cnt;
}
ll st[N];
int pre[N];
bool vs[N];
bool spfa() 
{
	queue<int>q;
	for(int i=0;i<=t;i++)
	{
		vs[i]=0;st[i]=-INF;pre[i]=-1;
	}
	q.push(s);st[s]=0;vs[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();vs[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nex)
		{
			int v=e[i].t;
			if(st[v]<st[u]+e[i].c&&e[i].w)
			{
				st[v]=st[u]+e[i].c;pre[v]=i;
				if(!vs[v]){vs[v]=1;q.push(v);}
			}
		}
	}
	if(st[t]==-INF)return false;
	ll d=INF;
	for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        d=min(d,e[i].w);
    }
    for(int i=pre[t];i!=-1;i=pre[e[i^1].t]){
        e[i].w-=d;
        e[i^1].w+=d;
        cost+=e[i].c*d;
    }
    ans+=d;
    return true;
}
int get(int i,int j)
{
	return (i-1)*m+j;
}
int ff[40][40];
void dfs(int x,int y,int pos,int k)
{
	for(int i=head[pos];~i;i=e[i].nex)
	{
		int v=e[i].t;
		if(v==s||v==t||v==pos-n*m)continue;
		if(!e[i^1].w)continue;
		e[i^1].w--;
		if((v-1)%m+1==y+1)
		{
			cout<<k<<" "<<1<<endl;
			dfs(x,y+1,v+n*m,k);
			return;
		}
		else
		{
			cout<<k<<" "<<0<<endl;
			dfs(x+1,y,v+n*m,k);
			return;
		}
	}
}
void work() 
{
	memset(head,-1,sizeof(head));
	int k;cin>>k;
	cin>>m>>n;
	int o=n*m;
	s=0;t=n*m*2+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>ff[i][j];
			if(ff[i][j]==1)continue;
			add(get(i,j),get(i,j)+o,INF,0);
			if(ff[i][j]==2)add(get(i,j),get(i,j)+o,1,1);
		}
	}
	if(ff[1][1]!=1);add(s,get(1,1),k,0);
	if(ff[n][m]!=1)add(get(n,m)+o,t,INF,0);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(i<n&&ff[i+1][j]!=1)
			{
				add(get(i,j)+o,get(i+1,j),INF,0);
			}
			if(j<m&&ff[i][j+1]!=1)
			{
				add(get(i,j)+o,get(i,j+1),INF,0);
			}
		}
	}
	while(spfa());
	for(int i=1;i<=ans;i++)
	{
		dfs(1,1,1+n*m,i);
	}
}
int main() {
	FAST;
	int t = 1;
	//cin >> t;
	while (t--)work();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值