[BZOJ2163]复杂的大门

题目

点这里看题目。

BZOJ GG 了,所以链接在 dark 上面。

题面:

你去找某 bm 玩,到了门口才发现要打开他家的大门不是一件容易的事……
他家的大门外有 \(n\) 个站台,用 \(1\)\(n\) 的正整数编号。你需要对每个站台访问一定次数以后大门才能开启。站台之间有 \(m\) 个单向的传送门,通过传送门到达另一个站台不需要花费任何代价。而如果不通过传送门,你就需要乘坐公共汽车,并花费 \(1\) 单位的钱。值得庆幸的是,任意两个站台之间都有公共汽车直达。
现在给你每个站台必须访问的次数 \(F_i\),对于站台 \(i\),你必须恰好访问 \(F_i\) 次(不能超过)。
我们用 \((u,v,w)\) 三个参数描述一个传送门,表示从站台 \(u\) 到站台 \(v\) 有一个最多可以使用 \(w\) 次的传送门(不一定要使用 \(w\) 次)。值得注意的是,对于任意一对传送门 \((u_1,v_1)\)\((u_2,v_2)\),如果有 \(u_1<u_2\),则有 \(v_1\le v_2\);如果有 \(v_1<v_2\),则有 \(u_1≤u_2\);且 \(u_1=u_2\)\(v_1=v_2\) 不同时成立。
你可以从任意的站台开始,从任意的站台结束。出发去开始的站台需要花费 \(1\) 单位的钱。你需要求出打开大门最少需要花费多少单位的钱。

数据范围:

对于 \(100\%\) 的数据,满足 \(1\le n\le 10^4, 1\le m\le 10^5, 1\le w,F_i\le 5\times 10^4\)

分析

粗看可以想到费用流的大致方向。

事实上,我们可以将一种方案描述成 \(n\) 个站台的可重排列 \(P\) ,并且第 \(i\) 个站台恰好出现 \(F_i\) 次。此时从 \(P_i\) 到达 \(P_{i+1}\) 的费用仅与这两个点有关,因此我们可以构建二分图,左部表示 \(P_i\) ,右部表示 \(P_{i-1}\) ,那么匹配的费用就是从 \(P_{i-1}\) 到达 \(P_i\) 的费用。实际上我们建图干的事情就是给每个 \(P_i\) 找到它的前驱

设左部的点为 \(L_1,L_2,...,L_{n}\) ,右部为 \(R_1,R_2,...,R_n\) ,那么不难想到如下建图:

  • 连接 \(S\rightarrow L_u\) ,容量为 \(F_u\) ,费用为 \(0\) ,表示最多有 \(F_i\) 次作为 \(P_i\)
  • 连接 \(R_u\rightarrow T\) ,容量为 \(F_u\) ,费用为 \(0\) ,表示最多有 \(F_i\) 次作为 \(P_{i-1}\)
  • 对于题目中的一条边,连接 \(L_u\rightarrow R_v\) ,容量为 \(w\) ,费用为 \(0\),表示走传送门。
  • 剩下的花费 1​ 的坐公交边由于不能自己连自己,所以可以用前缀后缀进行优化。
  • 注意我们实际上只需要找 \(\sum F-1\) 次前驱,所以需要新建一个 \(s\) ,然后连接 \(s\rightarrow S\) ,容量为 \(\sum F-1\) ,费用为 0 。

答案就是 \(s\)\(T\) 的最大流,再加一。

小结:

通过将序列拆分为当前点 \(P_i\) 和前驱 \(P_{i-1}\) ,从而转化为了二分图,建图方法有参考价值。

代码

#include <queue>
#include <cstdio>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 5, MAXM = 1e6 + 5;

template<typename _T>
void read( _T &x )
{
	x = 0; char s = getchar(); int f = 1;
	while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}

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

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

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

queue<int> q;

int A[MAXN];

int F[MAXN];
int head[MAXN], dist[MAXN], cur[MAXN];
int N, M, cnt = 1, tot;
bool vis[MAXN];

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;
}

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

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

int DFS( const int u, const int lin, const int T, int &cost )
{
	if( u == T ) return lin;
	int used = 0, ret, v, c, w; vis[u] = true;
	for( int &i = cur[u] ; i ; i = Graph[i].nxt )
	{
		v = Graph[i].to, c = Graph[i].c, w = Graph[i].w;
		if( dist[v] == dist[u] + w && c && ! vis[v] && ( ret = DFS( v, MIN( lin - used, c ), T, cost ) ) )
		{
			used += ret, Graph[i].c -= ret, Graph[i ^ 1].c += ret, cost += ret * w;
			if( used == lin ) break;
		}
	}
	if( used < lin ) dist[u] = INF;
	vis[u] = false; return used;
}

int Dinic( const int S, const int T )
{
	int f = 0, c = 0;
	while( SPFA( S, T ) )
	{
		for( int i = 1 ; i <= tot ; i ++ ) cur[i] = head[i];
		f += DFS( S, INF, T, c );
	}
	return c;
}

int main()
{
	read( N ), read( M );
	tot = 4 * N; int sig = 0;
	const int s = ++ tot, t = ++ tot, S = ++ tot;
	const int LEF = 0, RIG = N, PRE = 2 * N, SUF = 3 * N;
	for( int i = 1 ; i <= N ; i ++ )
		read( F[i] ), AddE( i + RIG, t, F[i], 0 ), AddE( s, i + LEF, F[i], 0 ), sig += F[i];
	for( int i = 1, a, b, w ; i <= M ; i ++ ) 
		read( a ), read( b ), read( w ), AddE( b, a + RIG, w, 0 );
	for( int i = 1 ; i <= N ; i ++ ) 
	{
		if( i > 1 ) AddE( i + PRE, i + PRE - 1, INF, 0 ); 
		if( i < N ) AddE( i + SUF, i + SUF + 1, INF, 0 );
		AddE( i + PRE, i + RIG, INF, 1 );
		AddE( i + SUF, i + RIG, INF, 1 );
	}
	for( int i = 1 ; i <= N ; i ++ )
	{
		if( i > 1 ) AddE( i + LEF, i + PRE - 1, INF, 0 );
		if( i < N ) AddE( i + LEF, i + SUF + 1, INF, 0 );
	}
	AddE( S, s, sig - 1, 0 );
	write( Dinic( S, t ) + 1 ), putchar( '\n' );
	return 0;
}
posted @ 2020-12-22 18:19  crashed  阅读(63)  评论(0编辑  收藏  举报