• @bzoj



    @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
    ", 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
    ", ans);
    	}
    }
    

    @details@

    想起「东方文花帖DS-游行圣」

    一开始想的是二分找每个 k*C + b 对应的区间 [Cl, Cr],然后发现跑一次费用流都慢得要死 = =。
    然后花了一个上午研究费用流怎么写 = =。最后发现思路有问题 = =。

    养成好习惯,没事儿别写已死的 spfa。能用势函数转化为 dijkstra 为什么不用呢。

  • 相关阅读:
    发现pythonWin里面的一个地方挺别扭
    细节-质量-态度
    对于Borland出售IDE业务的一点感想
    ReView100遍?!
    代码生成原则Top10
    使用asp.net进行多关键字查询的例子
    代码生成FAQ(翻译)
    msdn中文上的几篇有用的sqlServer2000的文章
    RSS 阅读工具Omea Reader
    Ubuntu18.04 安装Postgresql12
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13024166.html
Copyright © 2020-2023  润新知