@bzoj - 3691@ 游行
@description@
每年春季,在某岛屿上都会举行游行活动。
在这个岛屿上有N个城市,M条连接着城市的有向道路。
你要安排英雄们的巡游。英雄从城市si出发,经过若干个城市,到城市ti结束,需要特别注意的是,每个英雄的巡游的si可以和ti相同,但是必须至少途径2个城市。
每次游行你的花费将由3部分构成:
1.每个英雄游行经过的距离之和,需要特别注意的是,假如一条边被途径了k次,那么它对答案的贡献是k*ci,ci表示这条边的边权。
2.如果一个英雄的巡游的si不等于ti,那么会额外增加C的费用。因为英雄要打的回到起点。
3.如果一个城市没有任何一个英雄途经,那么这个城市会很不高兴,需要C费用的补偿。
你有无数个的英雄。你要合理安排游行方案,使得费用最小。
由于每年,C值都是不一样的。所以你要回答Q个询问,每个询问都是,当C为当前输入数值的时候的答案。
@solution@
如果起点 si = ti,代价为环上边权之和。联想到最小权可相交环覆盖。
我们不妨在所有点对之间连权值为 C 的边(包括自环),则在这个图上任意可相交环覆盖都对应一种合法的方案(自环表示没有人经过)。
因此转化为最小权可相交环覆盖(可以先用 floyd 转化为最小权不相交环覆盖,但不是必须的)。拆点,用上下界网络流可以建图,不细谈。
注意到最终答案总可以表示为 k*C + b (0 <= k <= N)。
只要求出每种 k 对应的最小 b 就可以 O(QN)(或者用二分 O(QlogN))回答询问。
我们构造出来的图是原图 + 费用为 C 的完全图,长得就很有特殊性质。
一条从 S -> x' -> ... -> y -> T 的增广路,中间要么是原图中的一条路径,要么是一条 C 边。
满流时,如果经过 k 条 C 边,则在原图上有 N - k 条增广路。
因此在原图上每次发送单位流量,记录此时的最小费用,就可以找到 k*C + b 中每个 k 对应的 b。
@accepted code@
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
#define fi first
#define se second
#define mp make_pair
const int MAXN = 250, MAXC = 10000, INF = int(1E9);
namespace FlowGraph{
const int MAXV = 2*MAXN, MAXE = 3*MAXN*MAXN;
struct edge{
int to, cap, flow, cost;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
int d[MAXV + 5], h[MAXV + 5], n, s, t;
void clear(int _n) {
n = _n;
for(int i=1;i<=n;i++)
adj[i] = NULL, h[i] = d[i] = 0;
ecnt = edges;
}
void addedge(int u, int v, int c, int w) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->flow = 0, p->cost = w;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->flow = 0, q->cost = -w;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
// printf("! %d %d %d %d\n", u, v, c, w);
}
int f[MAXV + 5], hp[MAXV + 5];
void update(int x, int k) {
f[x] = k;
while( x ) {
hp[x] = x;
if( (x<<1) <= n && f[hp[x<<1]] < f[hp[x]] )
hp[x] = hp[x<<1];
if( (x<<1|1) <= n && f[hp[x<<1|1]] < f[hp[x]] )
hp[x] = hp[x<<1|1];
x >>= 1;
}
}
bool relabel() {
for(int i=1;i<=n;i++)
h[i] += d[i], f[i] = d[i] = INF, cur[i] = adj[i], hp[i] = i;
update(t, d[t] = 0);
while( f[hp[1]] != INF ) {
int x = hp[1]; update(x, INF);
for(edge *p=adj[x];p;p=p->nxt) {
int c = p->rev->cost + h[x] - h[p->to];
if( p->rev->cap > p->rev->flow && d[p->to] > d[x] + c )
update(p->to, d[p->to] = d[x] + c);
}
}
return d[s] != INF;
}
bool vis[MAXV + 5];
int aug(int x, int tot) {
if( x == t ) return tot;
vis[x] = true; int sum = 0;
for(edge *&p=cur[x];p;p=p->nxt) {
int c = p->cost + h[p->to] - h[x];
if( d[x] == d[p->to] + c && !vis[p->to] && p->cap > p->flow ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) break;
}
}
vis[x] = false; return sum;
}
int min_cost_max_flow(int _s, int _t) {
int cost = 0, flow = 0; s = _s, t = _t;
while( relabel() ) {
int del = aug(s, INF);
flow += del, cost += del * (d[s] + h[s]);
}
return cost;
}
}
int A[MAXN + 5][MAXN + 5], d[MAXN + 5], N, M, Q;
int main() {
scanf("%d%d%d", &N, &M, &Q);
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
A[i][j] = INF;
for(int i=1,a,b,c;i<=M;i++)
scanf("%d%d%d", &a, &b, &c), A[a][b] = min(A[a][b], c);
int s = 2*N + 1, t = 2*N + 2; FlowGraph::clear(t);
for(int i=1;i<=N;i++) {
for(int j=1;j<=N;j++)
if( A[i][j] != INF )FlowGraph::addedge(i + N, j, INF, A[i][j]);
FlowGraph::addedge(s, i + N, 1, 0);
FlowGraph::addedge(i, i + N, INF, 0);
FlowGraph::addedge(i, t, 1, 0);
}
FlowGraph::s = s, FlowGraph::t = t; int n;
for(n=1;FlowGraph::relabel()&&n<=N;FlowGraph::aug(s, 1),n++)
d[n] = d[n - 1] + (FlowGraph::d[s] + FlowGraph::h[s]);
for(;n<=N;n++) d[n] = INF;
for(int i=1;i<=Q;i++) {
int C, ans = INF; scanf("%d", &C);
for(int j=0;j<=N;j++)
ans = min(ans, C*(N - j) + d[j]);
printf("%d\n", ans);
}
}
@details@
想起「东方文花帖DS-游行圣」
一开始想的是二分找每个 k*C + b 对应的区间 [Cl, Cr],然后发现跑一次费用流都慢得要死 = =。
然后花了一个上午研究费用流怎么写 = =。最后发现思路有问题 = =。
养成好习惯,没事儿别写已死的 spfa。能用势函数转化为 dijkstra 为什么不用呢。