[BZOJ 1061][NOI2008]志愿者招募
膜了一发BYVoid的题解……竟然搞懂了
这首先是个非常简单的线性规划,就是有若干限制(每天要求的志愿者),形如:
\[P_i = X_a + X_b +\ldots + X_c\geq A_i\]
(这里用\(X_i\)表示第\(i\)类志愿者雇佣了多少个,\(P_i\)表示第\(i\)天的志愿者总数,\(A_i\)同原题面)
且最小化总费用。
既然我们我知道\(P_i\geq A_i\),那么说明\(P_i\)一定可以表示为\(A_i + Y_i\)(\(Y_i\geq 0\))。然后这样限制就变成了:
\[P_i = X_a + X_b +\ldots + X_c + Y_i = A_i\]
这个长得很像可以流量平衡的样子,但是流量的变动是表示不了的……
然后假设\(P_0 = 0\)且\(P_{n + 1} = 0\),这样就可以对限制差分一下,我们就有了\(n + 1\)个限制,然后这个式子就可以流量平衡做了(因为不考虑常数项的话,同一变量只可能在两个限制中出现,并且一正一负,这样就可以通过一个限制送给另一个限制流量来表示了。至于常数项,通过源或者汇连边即可表达)。
然后由于志愿者无限,所以这个东西也一定有解……
我局的我这么渣各位看官看懂的可能性基本上是零……还是推荐BYVoid神犇的题解,比我透彻多了。
代码:
/************************************************************** Problem: 1061 User: danihao123 Language: C++ Result: Accepted Time:3164 ms Memory:6824 kb ****************************************************************/ #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <utility> #include <algorithm> #include <queue> #include <cassert> typedef long long ll; int m, n; const int maxno = 100005; const int maxe = 100005; int first[maxno]; int next[maxe], from[maxe], to[maxe]; ll flow[maxe], cap[maxe]; ll cost[maxe]; int gcnt = 0; void add_edge(int u, int v, ll f, ll c) { gcnt ++; next[gcnt] = first[u]; first[u] = gcnt; from[gcnt] = u; to[gcnt] = v; cap[gcnt] = f; flow[gcnt] = 0; cost[gcnt] = c; } int rev(int i) { return ((i - 1) ^ 1) + 1; } int ins_edge(int u, int v, ll f, ll c = 0LL) { #ifdef LOCAL printf("Inserting Edge (%d, %d, %lld, %lld)\n", u, v, f, c); #endif add_edge(u, v, f, c); add_edge(v, u, 0, -c); return gcnt - 1; } const ll LINF = 0x7f7f7f7f7f7f7f7fLL; int tot; bool spfa(int s, int t, ll &f, ll &c) { static ll d[maxno]; static bool inq[maxno]; static ll a[maxno]; static int p[maxno]; std::fill(d, d + tot + 1, LINF); std::fill(inq, inq + tot + 1, false); std::fill(a, a + tot + 1, 0LL); std::fill(p, p + tot + 1, 0); d[s] = 0; std::queue<int> Q; Q.push(s); inq[s] = true; a[s] = LINF; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int i = first[u]; i; i = next[i]) { if(cap[i] > flow[i]) { int v = to[i]; if(d[v] > d[u] + cost[i]) { d[v] = d[u] + cost[i]; a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i; if(!inq[v]) { Q.push(v); inq[v] = true; } } } } } if(a[t] == 0LL) return false; f += a[t]; c += a[t] * d[t]; for(int e = p[t]; e; e = p[from[e]]) { flow[e] += a[t]; flow[rev(e)] -= a[t]; } return true; } void mcmf(int s, int t, ll &f, ll &c) { while(spfa(s, t, f, c)); } const int maxn = 1005, maxm = 10005; int E[maxn]; int A[maxn], D[maxn]; int main() { scanf("%d%d", &n, &m); int s = 0, t = n + 2; tot = n + 2; for(int i = 1; i <= n; i ++) scanf("%d", &A[i]); for(int i = 1; i <= n + 1; i ++) { D[i] = A[i] - A[i - 1]; if(D[i] >= 0) { E[i] = ins_edge(s, i, D[i]); } else { E[i] = ins_edge(i, t, -D[i]); } } for(int i = 1; i <= n; i ++) { ins_edge(i + 1, i, LINF); } while(m --) { int S, T, C; scanf("%d%d%d", &S, &T, &C); ins_edge(S, T + 1, LINF, C); } ll f = 0, c = 0; mcmf(s, t, f, c); printf("%lld\n", c); return 0; }
[BZOJ 3171][TJOI2013]循环格
流量平衡入门中……
我竟然想民白了……
这个题就是要求每个点在且仅在一个环中,这样每个点的入、出度都是1。出度肯定是1,接下来我们想想怎么保证入度为1。
我们建两排点(也可以说是拆点?),一排表示流出,一排表示接受。然后流出点向相邻的接收点连边,费用的话考虑是否和原来这一格的方向不一样就行了。
这个不需要判断是否满流啥的……因为一定有解(比如说每个行构成一个环啥的)。
代码:
/************************************************************** Problem: 3171 User: danihao123 Language: C++ Result: Accepted Time:28 ms Memory:13844 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> const int maxn = 20; typedef long long ll; char S[maxn][maxn]; int tot = 0; int num[maxn][maxn][2]; int R, C; char D[4] = {'L', 'R', 'U', 'D'}; int tow(int i, int j, char dir, int flag) { int dx = 0, dy = 0; if(dir == 'L') { dx = -1; } else if(dir == 'R') { dx = 1; } else if(dir == 'U') { dy = -1; } else if(dir == 'D') { dy = 1; } int nx = (i + dy + R) % R; int ny = (j + dx + C) % C; return num[nx][ny][flag]; } const int maxno = 100005; const int maxe = 400005; int first[maxno]; int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe]; ll cost[maxe]; int gcnt = 0; void add_edge(int u, int v, int f, ll c) { gcnt ++; next[gcnt] = first[u]; first[u] = gcnt; from[gcnt] = u; to[gcnt] = v; cap[gcnt] = f; flow[gcnt] = 0; cost[gcnt] = c; } int rev(int i) { return ((i - 1) ^ 1) + 1; } void ins_edge(int u, int v, int f, ll c = 0LL) { #ifdef LOCAL printf("Inserting Edge (%d, %d, %d, %lld)\n", u, v, f, c); #endif add_edge(u, v, f, c); add_edge(v, u, 0, -c); } const ll LINF = 0x7f7f7f7f7f7f7f7fLL; bool spfa(int s, int t, int &f, ll &c) { static ll d[maxno]; static bool inq[maxno]; static int a[maxno], p[maxno]; std::fill(d, d + tot + 1, LINF); std::fill(inq, inq + tot + 1, false); std::fill(a, a + tot + 1, 0); std::fill(p, p + tot + 1, 0); d[s] = 0; std::queue<int> Q; Q.push(s); inq[s] = true; a[s] = 0x7fffffff; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int i = first[u]; i; i = next[i]) { if(cap[i] > flow[i]) { int v = to[i]; if(d[v] > d[u] + cost[i]) { d[v] = d[u] + cost[i]; a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i; if(!inq[v]) { Q.push(v); inq[v] = true; } } } } } if(a[t] == 0) return false; f += a[t]; c += (ll(a[t])) * d[t]; for(int e = p[t]; e; e = p[from[e]]) { flow[e] += a[t]; flow[rev(e)] -= a[t]; } return true; } void mcmf(int s, int t, int &f, ll &c) { while(spfa(s, t, f, c)); } int main() { tot = 1; int s = 0, t = 1; scanf("%d%d", &R, &C); for(int i = 0; i < R; i ++) { scanf("%s", S[i]); for(int j = 0; j < C; j ++) { num[i][j][0] = ++ tot; num[i][j][1] = ++ tot; } } for(int i = 0; i < R; i ++) { for(int j = 0; j < C; j ++) { ins_edge(s, num[i][j][0], 1); ins_edge(num[i][j][1], t, 1); for(int it = 0; it < 4; it ++) { int u = num[i][j][0]; int v = tow(i, j, D[it], 1); ins_edge(u, v, 1, (D[it] == S[i][j]) ? 0LL : 1LL); } } } int f = 0; ll c = 0; mcmf(s, t, f, c); printf("%lld\n", c); return 0; }
[BZOJ 1070][SCOI2007]修车
啊啊啊我为什么要去颓费用流啊……
这个题可以保证每个人的车最后都会被修,所以说也就是让你最小化总等待时间辣!
直接考虑给一个技术人员建时间轴是麻烦而不可取的。我们考虑一点,对于每一个技术人员,若总共修了\(t\)辆车,那么第\(i\)个来找他修车的人会影响后面人的等待时间,换言之我们可以认为第\(i\)个来修车的人给答案做出了\(c\cdot (t - i + 1)\)(这里用\(c\)表示他自己的修车时间,注意他也会影响他自己)的贡献,似乎可以苟最小费用最大流呢。
但问题在于我们并不能提前知道\(t\)是多少,并且这样的话费用不是递增的,一个人可能会优先考虑到后面的位置去修车。那么我们考虑倒过来,去考虑倒数第\(i\)个来修车的人的贡献,显然这个贡献是\(c\cdot i\)。然后接下来就肥肠简单了,,,
代码:
/************************************************************** Problem: 1070 User: danihao123 Language: C++ Result: Accepted Time:576 ms Memory:13928 kb ****************************************************************/ #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <utility> #include <algorithm> #include <queue> #include <cassert> typedef long long ll; int m, n; const int maxno = 100005; const int maxe = 400005; int first[maxno]; int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe]; ll cost[maxe]; int gcnt = 0; void add_edge(int u, int v, int f, ll c) { gcnt ++; next[gcnt] = first[u]; first[u] = gcnt; from[gcnt] = u; to[gcnt] = v; cap[gcnt] = f; flow[gcnt] = 0; cost[gcnt] = c; } int rev(int i) { return ((i - 1) ^ 1) + 1; } void ins_edge(int u, int v, int f, ll c = 0LL) { #ifdef LOCAL printf("Inserting Edge (%d, %d, %d, %lld)\n", u, v, f, c); #endif add_edge(u, v, f, c); add_edge(v, u, 0, -c); } const ll LINF = 0x7f7f7f7f7f7f7f7fLL; int tot; bool spfa(int s, int t, int &f, ll &c) { static ll d[maxno]; static bool inq[maxno]; static int a[maxno], p[maxno]; std::fill(d, d + tot + 1, LINF); std::fill(inq, inq + tot + 1, false); std::fill(a, a + tot + 1, 0); std::fill(p, p + tot + 1, 0); d[s] = 0; std::queue<int> Q; Q.push(s); inq[s] = true; a[s] = 0x7fffffff; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int i = first[u]; i; i = next[i]) { if(cap[i] > flow[i]) { int v = to[i]; if(d[v] > d[u] + cost[i]) { d[v] = d[u] + cost[i]; a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i; if(!inq[v]) { Q.push(v); inq[v] = true; } } } } } if(a[t] == 0) return false; f += a[t]; c += (ll(a[t])) * d[t]; for(int e = p[t]; e; e = p[from[e]]) { flow[e] += a[t]; flow[rev(e)] -= a[t]; } return true; } void mcmf(int s, int t, int &f, ll &c) { while(spfa(s, t, f, c)); } int pos[105][105][2]; int main() { scanf("%d%d", &m, &n); int s = 0, t = 1; tot = 1; for(int i = 1; i <= n; i ++) { ins_edge(s, ++ tot, 1); } for(int i = 1; i <= m; i ++) { for(int j = 1; j <= n; j ++) { pos[i][j][0] = ++ tot; pos[i][j][1] = ++ tot; ins_edge(tot - 1, tot, 1); ins_edge(tot, t, 1); } } for(int i = 1; i <= n; i ++) { for(int j = 1; j <= m; j ++) { int T; scanf("%d", &T); for(int k = 1; k <= n; k ++) { int id = pos[j][k][0]; int id2 = pos[j][k][1]; ins_edge(i + 1, id, 1, k * T); } } } int f = 0; ll c = 0; mcmf(s, t, f, c); #ifdef LOCAL printf("%d\n", f); #endif printf("%.2lf\n", (double(c)) / (double(n))); return 0; }
[BZOJ 1449][JSOI2009]球队收益
二次函数费用流的入门题……我真太弱了……
可以给比赛、队伍都建点,然后\(S\)向每个比赛连容量为1的边,每个比赛向队伍连容量为\(1\)的边,来表示赢家是谁。这样一来一次比赛只有一个队伍赢了。
考虑怎么处理那个二次函数费用。费用函数为\(f(x, y) = C x^2 + D y^2\),由于一个队伍的总比赛次数是已知的,所以\(y\)不需要,不妨假设一个队伍有\(t\)场比赛,则费用函数为\(f(x) = C x^2 + D(t - x)^2\)。
对这个函数做差分:\(\Delta f(x) = f(x + 1) - f(x) = 2Cx - 2D(t - x) + C + D\),然后这个差分是单调不降的。因此我们对所有差分都从队伍向\(T\)连边(费用为这一点的差分),这样一来的话会优先选择费用小的边,保证这个队伍的费用一直合法。
代码:
/************************************************************** Problem: 1449 User: danihao123 Language: C++ Result: Accepted Time:732 ms Memory:1448 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> #include <cassert> typedef long long ll; const int maxn = 5005; const int maxm = 1005; const int maxno = maxn + maxm + 5; const int maxe = (1000 * 2 + 1000 * 3) * 2 + 50; int first[maxno]; int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe]; ll cost[maxe]; int gcnt = 0; void add_edge(int u, int v, int f, ll c) { gcnt ++; next[gcnt] = first[u]; first[u] = gcnt; from[gcnt] = u; to[gcnt] = v; cap[gcnt] = f; flow[gcnt] = 0; cost[gcnt] = c; } int rev(int i) { return ((i - 1) ^ 1) + 1; } void ins_edge(int u, int v, int f, ll c) { add_edge(u, v, f, c); add_edge(v, u, 0, -c); } int n, m; const ll LINF = 0x7f7f7f7f7f7f7f7fLL; bool spfa(int s, int t, int &f, ll &c) { static ll d[maxno]; static bool inq[maxno]; static int a[maxno], p[maxno]; std::fill(d, d + n + m + 2, LINF); std::fill(inq, inq + n + m + 2, false); std::fill(a, a + n + m + 2, 0); std::fill(p, p + n + m + 2, 0); d[s] = 0; std::queue<int> Q; Q.push(s); inq[s] = true; a[s] = 0x7fffffff; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int i = first[u]; i; i = next[i]) { if(cap[i] > flow[i]) { int v = to[i]; if(d[v] > d[u] + cost[i]) { d[v] = d[u] + cost[i]; a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i; if(!inq[v]) { Q.push(v); inq[v] = true; } } } } } if(a[t] == 0) return false; f += a[t]; c += (ll(a[t])) * d[t]; for(int e = p[t]; e; e = p[from[e]]) { flow[e] += a[t]; flow[rev(e)] -= a[t]; } return true; } void mcmf(int s, int t, int &f, ll &c) { while(spfa(s, t, f, c)); } ll win[maxn], lose[maxn], C[maxn], D[maxn]; ll tot[maxn]; int main() { scanf("%d%d", &n, &m); ll ans = 0; for(int i = 1; i <= n; i ++) { scanf("%lld%lld%lld%lld", &win[i], &lose[i], &C[i], &D[i]); tot[i] = win[i] + lose[i]; } int s = 0, t = n + m + 1; for(int i = 1; i <= m; i ++) { int a, b; scanf("%d%d", &a, &b); ins_edge(0, i + n, 1, 0); ins_edge(i + n, a, 1, 0); ins_edge(i + n, b, 1, 0); tot[a] ++; tot[b] ++; } for(int i = 1; i <= n; i ++) { ans += C[i] * win[i] * win[i] + D[i] * (tot[i] - win[i]) * (tot[i] - win[i]); for(ll j = win[i]; j <= (tot[i] - lose[i] - 1LL); j ++) { ins_edge(i, t, 1, 2LL * C[i] * j - 2LL * D[i] * (tot[i] - j) + C[i] + D[i]); } } int f = 0; mcmf(s, t, f, ans); printf("%lld\n", ans); return 0; }
[COGS 741]负载平衡
这个网络流题挺简单的……
首先从[tex]S[/tex]向每个点连容量为该点存货量的边(费用为0)。首先注意最后所有存货量都要变成总的平均值,所以要从每个点向[tex]T[/tex]连一条容量为总的平均值的边(费用为0)。最后根据题目要求在相邻点之间连边(容量都是无限大,费用为1)。
跑一遍最小费用最大流,得出的费用就是答案。
代码:
#include <cstdio> #include <cstring> #include <queue> #include <vector> #include <algorithm> using namespace std; const int maxn=110; namespace MCMF{ struct Edge{ int u,v,cap,flow,cost; }; vector<Edge> edges; vector<int> G[maxn]; int m; inline void AddEdge(int u,int v,int cap,int cost){ edges.push_back((Edge){u,v,cap,0,cost}); edges.push_back((Edge){v,u,0,0,-cost}); m=edges.size(); G[u].push_back(m-2); G[v].push_back(m-1); } int a[maxn]; bool inQueue[maxn]; int d[maxn],p[maxn]; bool BellmanFord(int s,int t,long long& cost){ register int i,u; queue<int> Q; memset(d,0x7f,sizeof(d)); memset(inQueue,0,sizeof(inQueue)); d[s]=0; inQueue[s]=true; p[s]=0; a[s]=0x7f7f7f7f; Q.push(s); while(!Q.empty()){ u=Q.front(); Q.pop(); inQueue[u]=false; for(i=0;i<G[u].size();i++){ Edge& e=edges[G[u][i]]; if(e.cap>e.flow && d[e.v]>d[u]+e.cost){ d[e.v]=d[u]+e.cost; p[e.v]=G[u][i]; a[e.v]=min(a[u],e.cap-e.flow); if(!inQueue[e.v]){ Q.push(e.v); inQueue[e.v]=true; } } } } if(d[t]==0x7f7f7f7f) return false; cost+=((long long)d[t])*((long long)a[t]); for(u=t;u!=s;u=edges[p[u]].u){ edges[p[u]].flow+=a[t]; edges[p[u]^1].flow-=a[t]; } return true; } long long MincostMaxflow(int s,int t){ register long long ans=0; while(BellmanFord(s,t,ans)); return ans; } }; int A[maxn]; int main(){ register int i,ave=0; int n; freopen("overload.in","r",stdin); freopen("overload.out","w+",stdout); scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",&A[i]); ave+=A[i]; MCMF::AddEdge(0,i,A[i],0); } ave/=n; for(i=1;i<=n;i++){ MCMF::AddEdge(i,n+1,ave,0); if(i>1){ MCMF::AddEdge(i-1,i,0x7f7f7f7f,1); MCMF::AddEdge(i,i-1,0x7f7f7f7f,1); } if(i<n){ MCMF::AddEdge(i+1,i,0x7f7f7f7f,1); MCMF::AddEdge(i,i+1,0x7f7f7f7f,1); } } MCMF::AddEdge(1,n,0x7f7f7f7f,1); MCMF::AddEdge(n,1,0x7f7f7f7f,1); printf("%lld\n",MCMF::MincostMaxflow(0,n+1)); fclose(stdin); fclose(stdout); return 0; }
[COGS 740]分配问题
很明显是二分图最大权匹配。可以用匈牙利算法求解,但我比较弱……所以就写了个费用流。
注意最优解和最差解都要给出!
代码:
#include <cstdio> #include <cstring> #include <vector> #include <queue> #include <algorithm> using namespace std; const int maxn=205; namespace MCMF{ struct Edge{ int u,v,cap,flow,cost; }; vector<Edge> edges; vector<int> G[maxn]; int m; inline void AddEdge(int u,int v,int cap,int cost){ edges.push_back((Edge){u,v,cap,0,cost}); edges.push_back((Edge){v,u,0,0,-cost}); m=edges.size(); G[u].push_back(m-2); G[v].push_back(m-1); } inline void ClearGraph(){ register int i; edges.clear(); for(i=0;i<maxn;i++) G[i].clear(); } int a[maxn]; bool inQueue[maxn]; int d[maxn],p[maxn]; bool BellmanFord(int s,int t,long long& cost){ register int i,u; queue<int> Q; memset(d,0x7f,sizeof(d)); memset(inQueue,0,sizeof(inQueue)); d[s]=0; inQueue[s]=true; p[s]=0; a[s]=0x7f7f7f7f; Q.push(s); while(!Q.empty()){ u=Q.front(); Q.pop(); inQueue[u]=false; for(i=0;i<G[u].size();i++){ Edge& e=edges[G[u][i]]; if(e.cap>e.flow && d[e.v]>d[u]+e.cost){ d[e.v]=d[u]+e.cost; p[e.v]=G[u][i]; a[e.v]=min(a[u],e.cap-e.flow); if(!inQueue[e.v]){ Q.push(e.v); inQueue[e.v]=true; } } } } if(d[t]==0x7f7f7f7f) return false; cost+=((long long)d[t])*((long long)a[t]); for(u=t;u!=s;u=edges[p[u]].u){ edges[p[u]].flow+=a[t]; edges[p[u]^1].flow-=a[t]; } return true; } long long MincostMaxflow(int s,int t){ register long long ans=0; while(BellmanFord(s,t,ans)); return ans; } }; int A[105][105]; int main(){ int n; register int i,j; freopen("job.in","r",stdin); freopen("job.out","w+",stdout); scanf("%d",&n); for(i=1;i<=n;i++) for(j=1;j<=n;j++){ scanf("%d",&A[i][j]); MCMF::AddEdge(i,j+n,1,A[i][j]); } for(i=1;i<=n;i++) MCMF::AddEdge(0,i,1,0); for(i=n+1;i<=2*n;i++) MCMF::AddEdge(i,2*n+1,1,0); printf("%lld\n",MCMF::MincostMaxflow(0,2*n+1)); MCMF::ClearGraph(); for(i=1;i<=n;i++) for(j=1;j<=n;j++){ MCMF::AddEdge(i,j+n,1,-A[i][j]); } for(i=1;i<=n;i++) MCMF::AddEdge(0,i,1,0); for(i=n+1;i<=2*n;i++) MCMF::AddEdge(i,2*n+1,1,0); printf("%lld\n",-(MCMF::MincostMaxflow(0,2*n+1))); return 0; }