【XSY-Contest2618.Problem A】抢夺

传送门:Problem A: 抢夺

(放不了题面。)

S o l u t i o n \mathfrak{Solution} Solution

一道很像最大流的费用流。

1

第一眼看过去觉得是分层图。原因:到某个节点时车的状态(第几天)是不同的。

但数据范围明显不让你这么干。

然后就不难想到二分。

二分的明显是天数。

2

如何判断当前天数能否满足题意?

使用增广路来实现。每次找源点和汇点之间的增广路径,因为每走一条路用一天,所以不妨设路径长度都为 1。那么这条增广路的长度就是走这条路所用的天数,记为 d i s n dis_n disn。它等价于费用流里的费用。

对于每条路径的限制,将它等价于费用流中的流量。那么最后能从源点走此增广路到汇点的流数量 即为 能同时花 d i s n dis_n disn 天,走此条增广路到终点的人数,记为 i n c f n incf_n incfn

综上,对于一条增广路径而言,假设当前二分出的天数为 m i d mid mid,有:在 m i d mid mid 天中,能走这条路到终点的总人数为 ( m i d − d i s n + 1 ) × i n c f n (mid - dis_n +1) \times incf_n (middisn+1)×incfn。这个很好理解。

3

二分的 c h e c k ( ) check() check() 函数:

对于二分到的一个总天数 m i d mid mid,我们反复寻找增广路经,每找到一条就用 k k k(总人数)减去能走此增广路经的人总数,若 k ≤ 0 k\le 0 k0,证明 m i d mid mid 可行。

A C C o d e \mathfrak{AC Code} ACCode

注意每次 c h e c k ( ) check() check() 函数前都要重新建边。

#include<bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define maxn 2005
#define maxm 10005
#define inf 2147483647

int n, m, k;
int cnt = 1, hd[maxn];
struct node{
	int to, nxt, flw, cst;
}e[maxm];
int ui[maxm], vi[maxm], fi[maxm];
int dis[maxn], incf[maxn];
int pre[maxn];
int l, r;
int s, t;
bool vis[maxn];

inline void init()
{
	l = r = s = t = 0;
	cnt = 1;
}

inline void add(int u, int v, int f)
{
	e[++cnt] = (node){v, hd[u], f, 1};
	hd[u] = cnt;
	e[++cnt] = (node){u, hd[v], 0, -1};
	hd[v] = cnt;
}

inline int read()
{
	int x = 1, ss = 0;
	char ch = getchar();
	while(ch < '0' or ch > '9'){if(ch == '-') x = -1; ch = getchar();}
	while(ch >= '0' and ch <= '9') ss = ss * 10 + ch - '0', ch = getchar();
	return x * ss;
}

inline bool spfa()
{
	queue <int> q;
	rep(i, 0, n + 10) dis[i] = inf;
	memset(vis, 0, sizeof vis);
	vis[1] = 1, q.push(1), dis[1] = 0;	 
	incf[1] = inf;
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for(int i = hd[u], v; i; i = e[i].nxt)
		{
			if(!e[i].flw) continue;
			if(dis[v = e[i].to] > dis[u] + e[i].cst)
			{
				dis[v] = dis[u] + e[i].cst;
				incf[v] = min(incf[u], e[i].flw);
				pre[v] = i;
				if(!vis[v]) 
					vis[v] = 1, q.push(v);
			}
		}
	}
	return dis[n] != dis[n + 1];
}

inline bool check(int day)
{
	int tmp = k;
	while(spfa())
	{
		if(day - dis[n] + 1 < 1) return 0;
		tmp -= (day - dis[n] + 1) * incf[n];
		if(tmp <= 0) return 1;
		int x = n, i;
		while(x != 1)
		{
			i = pre[x];
			e[i].flw -= incf[n], e[i ^ 1].flw += incf[n];
			x = e[i ^ 1].to;
		}
	}
	return 0;
}

signed main()
{
	int u, v, f;
	while(scanf("%d%d%d", &n, &m, &k) != EOF)
	{
		init();
		rep(i, 1, m) 
		{
			ui[i] = read() + 1, vi[i] = read() + 1,
			fi[i] = read();
		}
		if(!k)
		{
			printf("0\n");
			continue;
		}
		l = 0, r = k << 1;
		int flg = -1;
		while(l < r)
		{ 
			int mid = l + r >> 1;
			cnt = 1, memset(hd, 0, sizeof hd);
			rep(i, 1, m) add(ui[i], vi[i], fi[i]);
			if(check(mid)) r = flg = mid;
			else l = mid + 1;
		}
		if(~flg) printf("%d\n", flg);
		else puts("No solution");
	}
	return 0;
}

—— E n d \mathfrak{End} End——

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值