[多校赛20210621] 网络流
题目
给定一个图,包含 \(n\times k\) 个结点,结点分成 \(n\) 层,每层 \(k\) 个;对于任意的 \(k\),图上仅有从第 \(k\) 层出发到达第 \(k+1\) 层的有向边。
对于参数 \(l,r\),定义路径合法为该路径的起点在第 \(l\) 层,终点在第 \(r\) 层,且途中不逆向走过某条边。
设 \(f(l,r)\) 为 \(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)\)。
小结:
- 善用最大流最小割定理;
- 对于值域小且具有一定单调性的 DP,对分界点进行 DP 是非常常用的方法!
解法二
还是考虑网络流。我们在计算 \(f(1,r)\) 的时候,可以从第 1 层中的每个点出发,向尽量远的层增广,最终就可以在每一层上计算出答案。
那么我们在计算 \(f(2,r)\) 的时候可以利用已有信息吗?可以!我们可以直接在残余网络上,从第 2 层的每个点出发,继续向尽量远的层增广。为了理解正确性,我们可以考虑在该层新出现的流:如果它劣于第一层的流,那么本就不应该在之前就被增广;否则如果它由于第一层的流但现在才被增广,就说明它被阻塞了,此时再被增广也符合顺序。
我们还可以优化此情况下的网络流算法:模仿 EK,我们可以 BFS 所有能到达的点,并选取最远的层作为流的终点增广。这里我们可以利用 \(k\) 较小的范围做位运算从而节省时间。(实际上是模拟”费用流“的过程)
小结:
- 利用残余网络连续增广的思路!
- 网络流不一定必须用相应的算法,利用好性质来简化过程。
代码
解法一
咕咕咕,咕咕咕
解法二
#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;
}