「BZOJ3691」游行

题目

点这里看题目。


每年春季,在某岛屿上都会举行游行活动。

在这个岛屿上有 \(N\) 个城市,\(M\) 条连接着城市的有向道路,保证无自环,但可以有重边

你要安排英雄们的巡游。一个英雄从城市 \(s\)​​​​​​​ 出发,经过若干个城市,到城市 \(t\)​​​​​​ 结束,需要特别注意的是,每个英雄的巡游的 \(s\)​​​​​ 可以和 \(t\)​​​​ 相同,但是必须至少途径 \(2\)​​​ 个城市
每次游行你的花费将由 \(3\) 部分构成:

  1. 每个英雄游行经过的距离之和,需要特别注意的是,假如一条边被途径了 \(k\)​ 次,那么它对答案的贡献是 \(k\times c_i\)​,\(c_i\) 表示这条边的边权。

  2. 如果一个英雄的巡游的 \(s\)​​ 不等于 \(t\)​,那么会额外增加 \(C\) 的费用。因为英雄要打的回到起点。

  3. 如果一个城市没有任何一个英雄途经,那么这个城市会很不高兴,需要 \(C\) 费用的补偿。

你有无数个的英雄。你要合理安排游行方案,使得费用最小。

由于每年 \(C\)​​ 值都是不一样的。所以你要回答 \(Q\)​ 个询问,每个询问都是,当 \(C\) 为当前输入数值的时候的答案。

所有数据满足 \(1\le N\le 250,1\le M,Q\le 3\times 10^4,1\le c_i,C\le 10^4\)

分析

尝试了两个方向:

  1. 尝试化简路径,比如规约问题使得路径可以不交。然而这个想法既无法证明,也没有前途。
  2. 尝试转化路径。比如可以将所有路径都规约成环,因为如果 \(s\neq t\),则我们相当于连了一条 \(t\rightarrow s\) 且边权为 \(C\) 的边来构成一个环。

第二个方向继续延伸下去马上有了一个新的想法:能不能叠加一个边权为 \(C\)​ 的完全图?看起来这会产生不合法路径,但是仔细想想我们发现可以把路上边权为 \(C\) 的附加边拆掉,这样就变成了若干条实际路径了。所以这样的确是合法的。

最后的问题出在“至少途径 \(2\)​ 个城市”和“未被途径额外补偿”两部分上。如果只经过 \(1\)​​ 个城市,则它是一个自环,而自环恰好对应了“未被途径”的情况。因此,在新图上的最小可相交环覆盖完美地符合了条件。

Remark.

能够根据限制进行构造转化,这样的思考方式是很好的。如果题目的限制分非常怪异,一般都可以猜测其中有刻意构造的成分。

但是另一方面,思考仍然需要深入一些,需要抓住瞬时的灵感!


最小可相交环覆盖可以直接上下界跑不多说,但是我们还有多组询问,怎么处理?

注意到,现在的网络流形如一个二分图,而且最大流量必然为 \(N\)。我们叠加的图是一个完全图,这意味着我们始终有费用为 \(C\) 的增广路可选。根据费用流增广路长度的单调性,我们仅需找出所有的费用 \(<C\)​ 的增广路即可;而这些增广路上必然只包含原图的边,所以我们对于仅包含原图边的网络流,找出它的所有增广路,查询时保留 \(<C\) 的部分即可。复杂度为 \(O(N^2M+Q\log N)\)

代码

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int INF = 1e9;
const int MAXV = 1e5 + 5, MAXE = 1e6 + 5;
const int MAXN = 255;

template<typename _T>
inline void Read( _T &x ) {
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) Write( x / 10 );
	putchar( x % 10 + '0' );
}

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

template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
	return a > b ? a : b;
}

struct Edge {
	int to, nxt, c, w;
} Graph[MAXE << 1];

std :: queue<int> q;

int pref[MAXN];
int wei[MAXN], tot = 0;

int dist[MAXV]; bool vis[MAXV];
int head[MAXV], cur[MAXV], pre[MAXV];
int cnt = 1, ntot = 0;

int N, M, Q;

inline void AddEdge( const int &from, const int &to, const int &C, const int &W ) {
	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
	Graph[cnt].c = C, Graph[cnt].w = W, head[from] = cnt;
}

inline void AddE( const int &from, const int &to, const int &C, const int &W ) {
	AddEdge( from, to, C, W ), AddEdge( to, from, 0, -W );
}

inline bool SPFA( const int &S, const int &T ) {
	while( ! q.empty() ) q.pop();
	rep( i, 1, ntot ) dist[i] = INF;
	dist[S] = 0, q.push( S ), vis[S] = true;
	while( ! q.empty() ) {
		int u = q.front(); q.pop(), vis[u] = false;
		for( int i = head[u], v ; i ; i = Graph[i].nxt )
			if( Graph[i].c && dist[v = Graph[i].to] > dist[u] + Graph[i].w ) {
				dist[v] = dist[u] + Graph[i].w, pre[v] = i;
				if( ! vis[v] ) q.push( v ), vis[v] = true;
			}
	}
	return dist[T] < INF;
}

inline void EK( const int &S, const int &T ) {
	while( SPFA( S, T ) ) {
		int mn = INF;
		for( int u = T ; u ^ S ; u = Graph[pre[u] ^ 1].to ) 
			mn = std :: min( mn, Graph[pre[u]].c );
		for( int u = T ; u ^ S ; u = Graph[pre[u] ^ 1].to )
			Graph[pre[u]].c -= mn, Graph[pre[u] ^ 1].c += mn;
		rep( j, 1, mn ) wei[++ tot] = dist[T];
	}
}

int main() {
	Read( N ), Read( M ), Read( Q );
	ntot = N << 1; const int s = ++ ntot, t = ++ ntot;
	rep( i, 1, M ) {
		int u, v, c;
		Read( u ), Read( v ), Read( c );
		AddE( u + N, v, INF, c );
	}
	rep( i, 1, N ) {
		AddE( i, t, 1, 0 );
		AddE( i, i + N, INF, 0 );
		AddE( s, i + N, 1, 0 );
	}
	EK( s, t );
	rep( i, tot + 1, N ) wei[i] = INF;
	rep( i, 1, N ) pref[i] = pref[i - 1] + wei[i];
	while( Q -- ) {
		int C; Read( C );
		int p = std :: upper_bound( wei + 1, wei + 1 + N, C ) - wei;
		Write( pref[p - 1] + ( N - p + 1 ) * C ), putchar( '\n' );
	}
	return 0;
}
posted @ 2022-11-16 21:40  crashed  阅读(68)  评论(1编辑  收藏  举报