【网络流24题】星际转移(网络判定)

链接【网络流24题】星际转移

题意:

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

现有 n n 个太空站位于地球与月球之间,且有 m m 艘公共交通太空船在其间来回穿梭。每个太空站可容纳无限多的人,而每艘太空船 i i 只可容纳 H i H_i 个人。每艘太空船将周期性地停靠一系列的太空站,例如: { 1 , 3 , 4 } \{1,3,4\} 表示该太空船将周期性地停靠太空站 134134134 …

每一艘太空船从一个太空站驶往任一太空站耗时均为 1 1 。人们只能在太空船停靠太空站(或月球、地球)时上、下船。

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



分析:

对每个站点 u u 按时间进行拆点(拆分为 u 0 , u 1 , u 2 . . . u_0,u_1,u_2... ),时间从 0 0 开始枚举;

  • 每过一个单位时间,连边 u i 1 u u_{i-1}\rarr u ,容量为 \infin ,表示人可以继续呆在当前站点;

  • 对于每艘太空船,当前将可以携带 H H 人,由站点 u u 驶向站点 v v ,则连边 u i 1 v i u_{i-1} \rarr v_i ,容量为 H H

  • 对于源点 s s 和汇点 t t s 0 0 s\rarr 0_0 ,容量为 k k ,表示初始时地球上有 k k 人;
    ( 1 ) 1 , ( 1 ) 2 , ( 1 ) 3 . . . t (-1)_1,(-1)_2,(-1)_3...\rarr t

0 0 开始枚举时间,每次建立新的连边,并跑一次最大流(增广),加入到总流量当中,当总流量 k \ge k 时,当前时间即为最短时间。



代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int maxn=1e5+10;
int s=maxn-2,t=maxn-1;
int head[maxn],cnt;
struct edge
{
	int w;           //边的流量(残量)
	int to;          //该边通向的结点v
	int next;        //点u邻接表的下一条边
}e[maxn];
void add_edge(int u,int v,int w)   //添加一条u->v,最大容量为w的边
{
    //建立正向边
	e[cnt].w=w;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
	cnt++;
	//建立反向边
	e[cnt].w=0;        //有些图是需要建立双向边(例如求最小割时),则反向边的初始残量不为0
	e[cnt].to=u;
	e[cnt].next=head[v];
	head[v]=cnt;
	cnt++;
}
int dis[maxn];     //dis数组记录层次
bool bfs()         //利用BFS建立分成图,从而可以多次DFS增广
{
	memset(dis,-1,sizeof(dis));     //初始化dis数组
	queue<int> q;
	q.push(s);
	dis[s]=0;      //源点层次为0
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i!=-1;i=e[i].next)
		{
			int v=e[i].to;
			if(e[i].w>0&&dis[v]==-1)     //可达&&未分层
			{
				dis[v]=dis[u]+1;         //分层
				if(v==t)                 //若到达汇点,则分层结束,返回true
					return true;
				q.push(v);
			}
		}
	}
	return false;  //运行到此处,说明汇点已不可达,返回false
}
int cur[maxn];     //弧优化:cur数组用于记录上一次DFS增广时u已经增广到第几条边,从而优化时间
int dfs(int u,int flow)       //flow代表流入u点的最大流量
{
	if(u==t)
		return flow;          //到达汇点,直接返回flow
	for(int &i=cur[u];i!=-1;i=e[i].next)
	{                         //注意i前面用&引用,这样就可以直接改变cur[u]
		int v=e[i].to;
		if(dis[v]==dis[u]+1&&e[i].w>0)   //v为u的下一层&&可达
		{
			int k=dfs(v,min(flow,e[i].w));
			if(k>0)
			{
				e[i].w-=k;         //正向边-=k
				e[i^1].w+=k;       //反向边+=k
				return k;
			}
		}
	}
	return 0;       //无法继续增广,返回0
}
int dinic()
{
	int ans=0;      //记录总流量
	while(bfs())    //分层
	{
		for(int i=0;i<maxn;i++)    //初始化cur数组,即将head数组赋给cur数组
			 cur[i]=head[i];
		while(int k=dfs(s,INF))    //增广
			ans+=k;
	}
	return ans;
}
int n,m,k;
int H[50],r[50],pre[50],now[50],S[50][50];
int main()
{
    memset(head,-1,sizeof(head));
    cnt=0;
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&H[i],&r[i]);
        for(int j=1;j<=r[i];j++)
        {
            scanf("%d",&S[i][j]);
            if(S[i][j]==-1)
                S[i][j]=n+1;
        }
        pre[i]=1;
    }
    int max_flow=0;
    add_edge(s,0,INF);
    for(int i=1;i<=100;i++)
    {
        for(int j=0;j<=n+1;j++)
            add_edge((i-1)*(n+2)+j,i*(n+2)+j,INF);
        for(int j=1;j<=m;j++)
        {
            if(pre[j]==r[j])
                now[j]=1;
            else
                now[j]=pre[j]+1;
            add_edge((i-1)*(n+2)+S[j][pre[j]],i*(n+2)+S[j][now[j]],H[j]);
            pre[j]=now[j];
        }
        add_edge(i*(n+2)+n+1,t,INF);
        max_flow+=dinic();   //加上该次增加的流量
        if(max_flow>=k)
        {
            printf("%d\n",i);
            return 0;
        }
    }
    printf("0\n");
    return 0;
}
发布了214 篇原创文章 · 获赞 40 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Ratina/article/details/102097714