2187. 星际转移问题 (利用分层图思想建图)

题目
题意: 略。
思路: 首先用并查集或者dfs的方式判断起点和终点是否连通。之后呢,感觉这个题比较难建图。一个比较好的想法是根据天来建图,每一天建立所有点,不同天同一个点之间建立INF的边(停在某个点的人可以再留一天),再根据每趟车建立某一天的点和下一天的点之间的连边。之后天数从1开始增加即可,因为网络流是可扩展的,新增加了某一天的边之后再跑网络流并不慢。除此之外,建立超级源点和超级汇点,超级源点向第0天的起点连一条流量为k的边;所有天的终点向超级汇点连一条流量为INF的边。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 15*15*50+100;
const int M = 2*(20+15+1)*(15+1)*51;
const int INF = 1e9;
int q[N],cur[N],d[N];
int h[N],e[M],ne[M],w[M],idx = 0;
int n,m,k,T; int st,ed;
void add(int a,int b,ll c)
{
	e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
bool bfs()
{
	int hh = 0,tt = 0;
	memset(d,-1,sizeof(d));
	d[st] = 0,cur[st] = h[st],q[tt++] = st;
	while(hh!=tt)
	{
		int u = q[hh++];
		for(int i=h[u];~i;i=ne[i])
		{
			int j = e[i];
			if(d[j]==-1&&w[i])
			{
				d[j] = d[u] + 1;
				cur[j] = h[j];
				if(j==ed) return true;
				q[tt++] = j;
			}
		}
	}
	return 0;
}
ll find(int u,ll limit)
{
	if(u==ed) return limit;
	ll flow = 0;
	for(int i=cur[u];~i&&flow<limit;i=ne[i])
	{
	    cur[u] = i;
		int j = e[i];
		if(d[j]==d[u]+1&&w[i])
		{
			ll t = find(j,min(1ll*w[i],limit-flow));
			if(!t) d[j] = -1;
			flow += t; w[i] -= t,w[i^1] += t;
		}
	}
	return flow;
}
ll dinic()
{
	ll ans = 0,flow;
	while(bfs()) while(flow = find(st,INF)) ans += flow;
	return ans;
}
int get(int day,int id) //第day天的id节点
{
	return day*(n+2)+id;
}
int a[22]; //容量
vector<int> va[22]; //节点
int fa[22];
int find(int x)
{
    return x==fa[x]?x:fa[x] = find(fa[x]);
}
void solve()
{
	memset(h,-1,sizeof(h));
	cin>>n>>m>>k;
	st = 0,ed = N-1;
	for(int i=1;i<=n+2;++i) fa[i] = i;
	for(int i=0;i<m;++i)
	{
		cin>>a[i];
		int num; cin>>num;
		while(num--)
		{
			int x; cin>>x;
			if(x==-1) x = n+2;
			else x++;
			va[i].push_back(x);
		}
	}
	for(int i=0;i<m;++i)
	{
	    for(int j=0;j<va[i].size()-1;++j)
	    {
	        int l = va[i][j];
	        int r = va[i][j+1];
	        int u = find(l),v = find(r);
	        if(u!=v)
	        {
	            fa[v] = u;
	        }
	    }
	}
	if(find(n+2)!=find(1))
	{
	    cout<<0; return;
	}
	add(st,get(0,1),k);
	add(get(0,1),st,0);
	int now = 0; int ans = 0;
	while(now <= 1000)
	{
		add(get(now+1,n+2),ed,INF); //第0天必没有流量,下一天的月球
		add(ed,get(now+1,n+2),0);
		for(int i=1;i<=n+1;++i)
		{
			add(get(now,i),get(now+1,i),INF); //待一天
			add(get(now+1,i),get(now,i),0);
		}
		for(int i=0;i<m;++i)
		{
			int l = va[i][now%va[i].size()];
			int r = va[i][(now+1)%va[i].size()];
			// cout<<l<<" "<<r<<"\n";
			add(get(now,l),get(now+1,r),a[i]); //传输
			add(get(now+1,r),get(now,l),0);
		}
		ans += dinic();
		// cout<<now<<":"<<ans<<"\n";
		now ++ ;
		if(ans == k) break;
	}
	cout<<now;
}
signed main(void)
{
	solve();
	return 0;
}
/*
3 2 1 3
1 2 1000 2000
2 3 100 200
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值