「SCOI2016」萌萌哒
题目
点这里看题目。
分析
暴力:对应地合并取值必然相同的位置,可以用并查集维护。由于最终最高位非 0,所以的答案为 \(9\times 10^{\text{连通块个数}-1}\)。
自然,我们需要优化这个过程。注意到我们总是对两段区间对应地合并,并且不存在在线的询问,这意味着进行标记的处理,将一些合并信息储存在较大的块内部延后处理。
由于块表示“对应合并”,这不可避免地要求合并的块大小相等,因此我们可以使用倍增来构造所需的块。也即,我们用 \((i,j)\) 来表示 \([i,i+2^j)\) 这个区间,并对 \((x,j),1\le x\le n-2^j+1\) 维护并查集。合并的时候对长度进行二进制拆分,对应的块用并查集合起来即可。
回归块的作用,其实用块表示“区间对应合并”的标记,因此我们需要在最后做下方标记的操作:对于第 \(j\) 层的每对父子,将它们劈成前后两半,两半在第 \(j-1\) 层对应合并即可。由于并查集维护的是图的生成树,因此这样可以保证连通性,不必担心标记丢失的问题。
小结:
- 注意标记思想的延伸运用,并非只能在数据结构内使用;
- 如果觉得难以处理,不妨回归本质的定义、目的。此处对于“块”作用的理解可以帮助我们处理它。
代码
#include <cmath>
#include <cstdio>
#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 mod = 1e9 + 7;
const int MAXN = 1e5 + 5, MAXLOG = 17;
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' );
}
struct UFS
{
int fa[MAXN];
UFS(): fa{} {}
void MakeSet( const int n ) { rep( i, 1, n ) fa[i] = i; }
int FindSet( const int u ) { return fa[u] = ( fa[u] == u ? u : FindSet( fa[u] ) ); }
void UnionSet( const int u, const int v ) { fa[FindSet( u )] = FindSet( v ); }
};
UFS conn[MAXLOG];
int N, M, lg2;
int main()
{
read( N ), read( M ), lg2 = log2( N );
rep( i, 0, lg2 ) conn[i].MakeSet( N - ( 1 << i ) + 1 );
while( M -- )
{
int l1, r1, l2, r2;
read( l1 ), read( r1 ), read( l2 ), read( r2 );
int len = r1 - l1 + 1;
per( i, lg2, 0 ) if( len >> i & 1 )
{
conn[i].UnionSet( l1, l2 );
l1 += 1 << i, l2 += 1 << i;
}
}
per( i, lg2, 1 )
rep( j, 1, N - ( 1 << i ) + 1 )
{
if( conn[i].fa[j] == j ) continue;
int u = j, v = conn[i].fa[j];
conn[i - 1].UnionSet( u, v );
conn[i - 1].UnionSet( u + ( 1 << i - 1 ), v + ( 1 << i - 1 ) );
}
int cnt = 0;
rep( i, 1, N ) cnt += conn[0].fa[i] == i;
int ans = 9;
rep( i, 2, cnt ) ans = 10ll * ans % mod;
write( ans ), putchar( '\n' );
return 0;
}