[Turing Cup #3] 欧拉回路
题目
分析
考虑某个子区间 \([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]\) 内部没有连边。因此该限制可以被描述为:
移动右端点的时候,\(\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)\) 的复杂度。
小结:
- 随机化权值用于判断的方法很巧妙,也较通用,思路困住的时候可以多尝试随机化的方向;
- 维护单调栈额外信息的方法值得注意。
代码
#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;
}