[BZOJ2879][Noi2012]美食节(费用流)

Address

洛谷P2050
BZOJ2879
LOJ#2674

Solution

一看发现和 SCOI2007 修车 是一样的。
这里再说一下建图:
把每个厨师拆成 ∑ i = 1 n p i \sum_{i=1}^np_i i=1npi 个点,拆出的第 i i i 个点表示倒数 i i i 个时刻的该厨师。
(1)由源点向每个时刻的厨师连一条容量为 1 1 1 费用为 0 0 0 的边,这限制了每个厨师每个时刻只能做一人份。
(2)由第 i i i 个菜品向汇点连容量为 p i p_i pi 费用为 0 0 0 的边,这表示第 i i i 个菜品要做 p i p_i pi 份。
(3)由倒数 j j j 时刻的第 i i i 个厨师向第 k k k 个菜品连边,容量为 1 1 1 ,费用为 j × t k , i j\times t_{k,i} j×tk,i ,这表示倒数 j j j 时刻做菜会有 j j j 倍的等待时间。
看上去一样,但是:数据变大了。
如果直接建图,则边数会达到 6.4 × 1 0 6 6.4\times 10^6 6.4×106 级别,啃腚会 TLE 。
可以撕烤一下,发现:费用流第一次增广,一定是走某个厨师的倒数 1 1 1 时刻(否则不是从源到汇的最短路)。
同样地,如果第 i i i 个厨师的倒数 1 1 1 r i r_i ri 时刻都满流,那么这次增广一定是选取一个 i i i ,走第 i i i 个厨师的倒数 r i + 1 r_i+1 ri+1 时刻。
所以考虑动态加边:首先,我们只加上菜品到汇点的边、源点到每个厨师倒数 1 1 1 时刻的边,以及每个厨师倒数 1 1 1 时刻到每个菜品的边。
然后每一次增广,如果增广走的是第 i i i 个厨师倒数第 j j j 时刻的边,那么这时候就从源点向第 i i i 个厨师倒数第 j + 1 j+1 j+1 时刻的点连边,再由第 i i i 个厨师第 j + 1 j+1 j+1 时刻的点向每个菜品连边。裆燃,如果 j = ∑ i = 1 n p i j=\sum_{i=1}^np_i j=i=1npi 就不要加边。
这样复杂度就有了保证。

Code

#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define CostFlow(e) for (e = pre[T]; e; e = pre[go[e ^ 1]])
using namespace std;

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int R = 45, Z = 105, N = 1e5 + 5, M = 7e6 + 5, INF = 0x3f3f3f3f;
int n, m, p[R], t[R][Z], ecnt = 1, nxt[M], adj[N], go[M],
cap[M], cost[M], dis[N], S, T, tm, pre[N], ans;
bool vis[N];
queue<int> q;

void add_edge(int u, int v, int w, int x)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	cap[ecnt] = w; cost[ecnt] = x;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
	cap[ecnt] = 0; cost[ecnt] = -x;
}

bool spfa()
{
	int i;
	For (i, S, T) dis[i] = INF, vis[i] = 0;
	dis[S] = 0; q.push(S);
	while (!q.empty())
	{
		int u = q.front(); q.pop(); vis[u] = 0;
		Edge(u)
			if (cap[e] && dis[u] + cost[e] < dis[v])
			{
				dis[v] = dis[u] + cost[pre[v] = e];
				if (!vis[v]) vis[v] = 1, q.push(v);
			}
	}
	return dis[T] < INF;
}

void jiejuediao()
{
	int i, e, u = T, fl = INF;
	CostFlow(e)
	{
		fl = Min(fl, cap[e]);
		if (go[pre[u] ^ 1] != S) u = go[pre[u] ^ 1];
	}
	CostFlow(e) cap[e] -= fl, cap[e ^ 1] += fl;
	ans += fl * dis[T];
	if ((u - 2) % tm + 1 != tm)
	{
		int x = (u - 1) / tm + 1, y = (u - 1) % tm + 1;
		add_edge(S, u + 1, 1, 0);
		For (i, 1, n) add_edge(u + 1, 1 + m * tm + i, 1, y * t[i][x]);
	}
}

int mcmf()
{
	while (spfa()) jiejuediao();
	return ans;
}

int main()
{
	int i, j, k;
	n = read(); m = read();
	For (i, 1, n) tm += (p[i] = read());
	For (i, 1, n) For (j, 1, m) t[i][j] = read();
	S = 1; T = 2 + m * tm + n;
	For (i, 1, m) For (j, 1, n)
		add_edge(1 + (i - 1) * tm + 1, 1 + m * tm + j, 1, t[j][i]);
	For (i, 1, n) add_edge(1 + m * tm + i, T, p[i], 0);
	For (i, 1, m) add_edge(S, 1 + (i - 1) * tm + 1, 1, 0);
	cout << mcmf() << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值