1449: [JSOI2009]球队收益
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1131 Solved: 640
[Submit][Status][Discuss]
Description
Input
Output
一个整数表示联盟里所有球队收益之和的最小值。
Sample Input
3 3
1 0 2 1
1 1 10 1
0 1 3 3
1 2
2 3
3 1
1 0 2 1
1 1 10 1
0 1 3 3
1 2
2 3
3 1
Sample Output
43
HINT
分析:很妙的idea.
首先要能想到用最小费用最大流求解. 为什么呢?把比赛当作点,每一场比赛有两种结果,那么就可以认为容量为1. 要使得收益最小,加上费用,就是求最小费用最大流了.但是确定不了每场比赛的收益.
一个非常巧妙的做法:
我们考虑费用的增量:多赢一场比赛产生的收益。
即(C(w + 1)^2 + D(l − 1)^2 ) − (Cw^2 + Dl^2 ) = 2wC − 2lD + C + D。
对于第i支队伍,假设后m场中i参加的有x场,那么最初w = win,
l = lose + x,之后每赢一场w + +,l − −。我们从第i支队伍的点向汇连x条边,
分别代表第i支队伍赢了j场比赛时相对赢j − 1场时收益的增量。由于增量一定
越来越大(平方嘛),所以流量最先流过的一定是费用较小的边,即j最小的边。
答案即所有队伍最初收益+最小费用最大流的费用。
费用递增模型可以采用拆边的方式跑费用流. 先确定最初状态,保证以后的费用都是递增的,这是算法正确性的保证!
#include <cstdio> #include <queue> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 10010,inf = 0x7fffffff; int n,m,win[maxn],lose[maxn],c[maxn],d[maxn]; int a[maxn],b[maxn],ans,S,T,vis[maxn],vis2[maxn]; int head[maxn],to[maxn * 6],nextt[maxn * 6],w[maxn * 6],cost[maxn * 6],tot = 2; void add(int x,int y,int z,int p) { w[tot] = z; cost[tot] = p; to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; w[tot] = 0; cost[tot] = -p; to[tot] = x; nextt[tot] = head[y]; head[y] = tot++; } bool spfa() { queue <int> q; memset(vis,0,sizeof(vis)); memset(vis2,0,sizeof(vis2)); for (int i = 1; i <= T; i++) d[i] = inf; d[S] = 0; vis[S] = 1; q.push(S); while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for (int i = head[u];i;i = nextt[i]) { int v = to[i]; if (w[i] && d[v] > d[u] + cost[i]) { d[v] = d[u] + cost[i]; if (!vis[v]) { vis[v] = 1; q.push(v); } } } } return d[T] < inf; } int dfs(int u,int f) { if (u == T) { ans += f * d[u]; return f; } int res = 0; vis2[u] = 1; for (int i = head[u];i;i = nextt[i]) { int v = to[i]; if (w[i] && !vis2[v] && d[v] == d[u] + cost[i]) { int temp = dfs(v,min(f - res,w[i])); w[i] -= temp; w[i ^ 1] += temp; res += temp; if (res == f) return res; } } return res; } void dinic() { while(spfa()) dfs(S,inf); } int main() { scanf("%d%d",&n,&m); S = n + m + 1; T = S + 1; for (int i = 1; i <= n; i++) scanf("%d%d%d%d",&win[i],&lose[i],&c[i],&d[i]); for (int i = 1; i <= m; i++) { scanf("%d%d",&a[i],&b[i]); lose[a[i]]++; lose[b[i]]++; } for (int i = 1; i <= n; i++) ans += win[i] * win[i] * c[i] + lose[i] * lose[i] * d[i]; for (int i = 1; i <= m; i++) { add(S,i,1,0); add(i,a[i] + m,1,0); add(i,b[i] + m,1,0); add(a[i] + m,T,1,c[a[i]] * (2 * win[a[i]] + 1) - d[a[i]] * (2 * lose[a[i]] - 1)); win[a[i]]++; lose[a[i]]--; add(b[i] + m,T,1,c[b[i]] * (2 * win[b[i]] + 1) - d[b[i]] * (2 * lose[b[i]] - 1)); win[b[i]]++; lose[b[i]]--; } dinic(); printf("%d ",ans); return 0; }