[Turing Cup #3] 欧拉回路

题目

prob.pnglimits.png

分析

考虑某个子区间 \([l,r]\) 为“好”的限制:

  • 对于每个点,其度数必须偶数;
  • 所有的边连通

然后将它们转化到序列上:

  • 对于 \(b_i\),包含它的顺序对数量必须为偶数。这里的顺序对包括 \(b_j<b_i,j<i\)\(b_i<b_k,i<k\)​​ 两种;
  • 区间内不存在分界点 \(m\)​,使得 \(\min_{l\le j\le m}b_j\ge\max_{m<k\le r }b_k\)​ 且 \([l,m]\)​ 和 \((m,r]\)​​ 内均有边存在;

考虑到 \(n\le 8000\) 的数据范围,我们大可枚举每个区间,比如固定左端点之后移动右端点。

首先处理第一条限制。将顺序对数量模 2 之后,有且仅有全 0 的情况才是符合要求的;为了快速判断,我们最好将多个 0/1 数码压缩为一个数。

那么联想到一些经典的问题,我们可以对于每个数随机分配权值并进行运算;同时为了方便处理顺序对数量的改变,我们自然想到异或。于是,一种方法是,将所有顺序对数量模 2 为 1 的数的权值异或在一起,判断是否为 0。由于随机情况下,非全 0 的 01 串这般操作后还能得到 0 的概率很小,因此该方法简单且有效。

接着处理第二条限制。注意到一段区间内没有连边当且仅当区间单调不增,故可以预处理 \(lef_i,rig_i\) 分别表示最小的 \(j\) 和最大的 \(k\) 使得 \([j,i],[i,k]\) 内部没有连边。因此该限制可以被描述为:

\[\max_{rig_l\le m<lef_r-1}\{\min_{l\le j\le m}b_j-\max_{m<k\le r}b_k\}<0 \]

移动右端点的时候,\(\min_{l\le j\le m}b_j\)​ 可以预处理,而 \(\max_{m<k\le r}b_k\)​ 可以使用单调栈维护。注意到对于栈内元素 \(stk_k\)​,当 \(\max b=b_{stk_k}\)​ 的时候,此时最大的 \(\min b\)​ 在 \(m=stk_{k-1}\)​,因此栈内的一个元素仅会贡献一次。由于 \(rig_l\)​ 确定了,变化的只有 \(lef_r\)​,那么合法的 \(m\)​ 被转化为一段前缀,扫描时处理好单调栈内前缀最大值。询问时可以直接在栈内二分,得到常数较小的 \(O(n^2\log_2n)\)​;但注意到 \(lef\)​ 随 \(r\)​ 单调不降,因此还可以维护指针,做到 \(O(n^2)\)​ 的复杂度。

小结:

  1. 随机化权值用于判断的方法很巧妙,也较通用,思路困住的时候可以多尝试随机化的方向;
  2. 维护单调栈额外信息的方法值得注意。

代码

#include <ctime>
#include <cstdio>
#include <random>
#include <iostream>

#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 = 0x3f3f3f3f;
const int MAXN = 8005;

template<typename _T>
void read( _T &x )/*{{{*/
{
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { 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' );
}/*}}}*/

int stk[MAXN], mx[MAXN], top;
int lef[MAXN], rig[MAXN], pref[MAXN];

bool deg[MAXN][MAXN];
int coe[MAXN][MAXN];

int A[MAXN], B[MAXN];
int N;

unsigned GetSeed() { char *tmp = new char; return time( 0 ) * ( unsigned long long ) tmp; }

int main()
{
	read( N );
	rep( i, 1, N ) read( A[i] );
	static std :: default_random_engine rng( GetSeed() );
	static std :: uniform_int_distribution<> gen( 1, 1e9 );
	rep( i, 1, N ) B[i] = gen( rng );
	rep( i, 1, N ) per( j, i, 1 )
	{
		deg[i][j] = deg[i][j + 1], coe[i][j] = coe[i][j + 1];
		if( A[j] < A[i] ) deg[i][j] ^= 1, coe[i][j] ^= B[j];
	}
	rep( i, 1, N ) lef[i] = i > 1 && A[i - 1] >= A[i] ? lef[i - 1] : i;
	per( i, N, 1 ) rig[i] = i < N && A[i] >= A[i + 1] ? rig[i + 1] : i;
	int ans = 0;
	rep( i, 1, N )
	{
		int val = 0;
		stk[top = 0] = rig[i] + 1, mx[0] = - INF;
		rep( j, i, N )
		{
			val ^= coe[j][i];
			if( deg[j][i] ) val ^= B[j];
			bool flg = val == 0;
			pref[j] = j == i ? A[j] : std :: min( pref[j - 1], A[j] );
			if( j > rig[i] + 1 )
			{
				for( ; top && A[stk[top]] <= A[j] ; top -- );	
				top ++, stk[top] = j, mx[top] = std :: max( mx[top - 1], pref[stk[top - 1]] - A[stk[top]] );
				if( rig[i] + 1 <= lef[j] - 2 )
				{
					int p = std :: lower_bound( stk + 1, stk + 1 + top, lef[j] - 1 ) - stk;
					int v = std :: max( mx[p - 1], pref[stk[p - 1]] - A[stk[p]] );
					flg &= v <= 0;
				}
			}
			ans += flg;
		}
	}
	write( ans ), putchar( '\n' );
	return 0;
}
posted @ 2021-08-08 17:15  crashed  阅读(60)  评论(0编辑  收藏  举报