[多校赛20210621] 网络流

题目

给定一个图,包含 \(n\times k\) 个结点,结点分成 \(n\) 层,每层 \(k\) 个;对于任意的 \(k\),图上仅有从第 \(k\) 层出发到达第 \(k+1\) 层的有向边

对于参数 \(l,r\),定义路径合法为该路径的起点在第 \(l\) 层,终点在第 \(r\) 层,且途中不逆向走过某条边。

\(f(l,r)\)\(l\)\(r\) 的某些合法路径,使得它们不会重复经过任何一个点能选出的最多条数。求:

\[\sum_{l=1}^{n-1}\sum_{r=l+1}^nf(l,r) \]

数据范围:对于 \(100\%\) 的数据,满足 \(1\le n\le 4\times 10^4, 1\le k\le 10\)

分析

解法一

很显然可以转化为最大流问题,接着就可以转化为最小割问题。

那么就可以想到一个 \(O(n^22^kk)\) 的做法:

设状态 \(f_{i,S}\) 表示从 \(l\) 出发,第 \(i\) 层中与第 \(l\) 层的点连通的点集为 \(S\) 的最小割。

转移首先需要根据 \(S\) 和跨层边来算出对应的下一层的连通情况;接着还需要计算割掉当前层的点情况,总之可以做到 \(O(2^kk)\) 甚至更优。枚举 \(l\) 并计算即可。

为了优化这个做法,我们注意到 \(f_{i,S}\) 的值域相当小。同时,可以发现对于 \(l_1<l_2\),在 \(l_1\) 计算得到的 \(f_{r,S}\) 一定不大于\(l_2\) 计算得到的 \(f_{r,S}\)。因此我们不难想到对分界点进行 DP,即设 \(g_{i,S,k}\) 表示使得 \(f_{i,S}=k\) 的最小的 \(l\)。这个可以做到 \(O(2^kk^2)\) 转移,所以时间复杂度优化为了 \(O(n2^kk^2)\)

小结:

  1. 善用最大流最小割定理;
  2. 对于值域小且具有一定单调性的 DP,对分界点进行 DP 是非常常用的方法!

解法二

还是考虑网络流。我们在计算 \(f(1,r)\) 的时候,可以从第 1 层中的每个点出发,向尽量远的层增广,最终就可以在每一层上计算出答案。

那么我们在计算 \(f(2,r)\) 的时候可以利用已有信息吗?可以!我们可以直接在残余网络上,从第 2 层的每个点出发,继续向尽量远的层增广。为了理解正确性,我们可以考虑在该层新出现的流:如果它劣于第一层的流,那么本就不应该在之前就被增广;否则如果它由于第一层的流但现在才被增广,就说明它被阻塞了,此时再被增广也符合顺序。

我们还可以优化此情况下的网络流算法:模仿 EK,我们可以 BFS 所有能到达的点,并选取最远的层作为流的终点增广。这里我们可以利用 \(k\) 较小的范围做位运算从而节省时间。(实际上是模拟”费用流“的过程)

小结:

  1. 利用残余网络连续增广的思路!
  2. 网络流不一定必须用相应的算法,利用好性质来简化过程。

代码

解法一

咕咕咕,咕咕咕

解法二

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

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

typedef long long LL;
typedef pair<int, int> Point;

#define xx first
#define yy second

const int MAXN = 8e4 + 5, MAXK = 15, MAXS = ( 1 << 10 ) + 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' );
}

queue<Point> q;

Point pref[MAXN][MAXK];

int vis[MAXN], contri[MAXN];
int back[MAXN][MAXK], forw[MAXN][MAXK];
int N, M, K; LL res = 0;

void Expand( const int px, const int py )
{
	vis[px] = 1 << py;
	int rig = px;
	Point ed( px, py );
	q.push( Point( px, py ) );
	while( ! q.empty() )
	{
		int x = q.front().xx, y = q.front().yy; q.pop();
		if( ( x & 1 ) && ( x >> 1 ) > ( ed.xx >> 1 ) ) ed = Point( x, y );
		if( x < M )
		{
			if( rig == x ) vis[++ rig] = 0;
			for( ; forw[x][y] & ( ~ vis[x + 1] ) ; )
			{
				int nxt = __builtin_ctz( forw[x][y] & ( ~ vis[x + 1] ) );
				vis[x + 1] |= 1 << nxt, pref[x + 1][nxt] = Point( x, y );
				q.push( Point( x + 1, nxt ) );
			}
		}
		if( x > px )
			for( ; back[x][y] & ( ~ vis[x - 1] ) ; )
			{
				int nxt = __builtin_ctz( back[x][y] & ( ~ vis[x - 1] ) );
				vis[x - 1] |= 1 << nxt, pref[x - 1][nxt] = Point( x, y );
				q.push( Point( x - 1, nxt ) );
			}
	}
	for( int i = ( px >> 1 ) ; i <= ( ed.xx >> 1 ) ; res ++, contri[i ++] ++ );
	for( int x, y, fx, fy ; ed != Point( px, py ) ; )
	{
		x = ed.xx, y = ed.yy;
		fx = pref[x][y].xx, fy = pref[x][y].yy;
		if( fx < x )
			forw[fx][fy] ^= 1 << y, back[x][y] ^= 1 << fy;
		else
			back[fx][fy] ^= 1 << y, forw[x][y] ^= 1 << fy;
		ed = Point( fx, fy );
	}
}

int main()
{
	read( N ), read( K ), M = ( N << 1 ) - 1;
	rep( i, 0, M )
	{
		if( i & 1 )
			for( int j = 0 ; j < K ; j ++ )
				for( int k = 0, v ; k < K ; k ++ )
				{
					scanf( "%1d", &v );
					forw[i][j] |= v << k;
				}
		else
			for( int j = 0 ; j < K ; j ++ )
				forw[i][j] |= 1 << j;
	}
	LL ans = 0;
	for( int i = 1 ; i < N ; i ++ )
	{
		for( int j = 0 ; j < K ; j ++ )
			Expand( i * 2 - 2, j );
		ans += ( res -= contri[i - 1] );
	}
	write( ans ), putchar( '\n' );
	return 0;
}
posted @ 2021-06-21 21:20  crashed  阅读(36)  评论(0编辑  收藏  举报