题目
点这里看题目。
分析
先考虑枚举一个骨牌并将它取下来。这样,一个空格就可以通过周围的骨牌来向各个方向移动。
注意到,我们可以选取最终局面上的一个空格,并找出它原先在哪里——看一下初始平板上这个空格对应的字符,就可以确定现在这块骨牌向哪个方向移动了,我们就可以逆向操作;一直循环直到这个空格属于取下的那一块骨牌。
容易发现,每个空格只会对应一个逆向操作的结果,在图上考虑,也就是只会连出一条边。最终我们会得到两片基环树森林(我们可以通过二染色来划分某一棵树属于哪一片森林),不妨设为 (F_1,F_2)。
但是,我们真的可以得到环吗?如果尝试去画出一个带环的平板,你会发现这出奇地困难——甚至是不可能的。下面我们会加以证明:
假设每一小格内部都有一个格点。如果说平板上面出现了一个环,我们可以用 Pick 定理计算一下这个环内部格点的数量:
[b=S-frac{a}{2}+1 ]其中 (a) 为边上的格点数目,(S) 为面积,(b) 为内部的格点数目。
不难发现 (frac a 2) 一定是偶数。从环上任选一个起点,则相对于它,最终环上的 (sum Delta x=sumDelta y=0)。又由于每经过一条边,(x) 或者 (y) 的变化量是一定的,所以在 (x,y) 分量上走过的步数一定是偶数;每走一步,边上的格点增加两个,因此 (a) 为 4 的倍数。
也不难发现 (S) 一定是偶数。使用向量外积计算 (S),由于每条边长度为偶数,则 (2S) 为 4 的倍数。
所以 (S-frac{a}{2}) 为偶数,则 (b) 为奇数。
因此,我们得到的 (F_1,F_2) 是实实在在的森林,而不是变异的基环树森林。
显然,如果某个方案中的两个空格为 (c_1) 和 (c_2),那么二染色后两者一定颜色不同,我们不妨设 (c_1in F_1,c_2in F_2)。此时,我们尝试让 (c_1) 和 (c_2) 分别去移动(显然它们只能跳向根),如果它们经过的结点中,不存在一对结点属于同一骨牌,那么 ((c_1,c_2)) 这一对也一定不可能被生成。反之,则一定可以生成:在所有在两条路径上均有结点出现的骨牌中,取出其中在 (c_1) 的路径上第一次出现的那一块骨牌,则 (c_1) 和 (c_2) 可以互不干扰地移动到这块骨牌的位置。
因此,需要求的就是到根的路径上存在一对结点属于同一骨牌的 ((c_1,c_2))。简单容斥一下可以求那些不存在一对结点属于同一骨牌的 ((c_1,c_2))。这就简单多了,DFS (F_1),并在 (F_2) 的子树上打标记即可。
复杂度为 (O(nmlog nm))。
小结:
- 学会转化题目的条件。此题中如果没有转化好 ((c_1,c_2)) 的合法条件,就会很难下手;
- 证明的思路挺有意思的。
代码
#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 -- )
typedef long long LL;
#ifdef _DEBUG
const int MAXN = 105;
#else
const int MAXN = 2e5 + 5;
#endif
template<typename _T>
void read( _T &x )/*{{{*/
{
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}/*}}}*/
template<typename _T>
void write( _T x )/*{{{*/
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}/*}}}*/
struct MyGraph/*{{{*/
{
struct Edge/*{{{*/
{
int to, nxt;
Edge(): to( 0 ), nxt( 0 ) {}
}Graph[MAXN];/*}}}*/
int head[MAXN], cnt;
int DFN[MAXN], siz[MAXN], ID;
MyGraph(): head{}, cnt( 0 ), DFN{}, siz{}, ID( 0 ) {}
Edge operator [] ( const int &idx ) const { return Graph[idx]; }
void AddEdge( const int from, const int to )/*{{{*/
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}/*}}}*/
void DFS( const int u )/*{{{*/
{
DFN[u] = ++ ID, siz[u] = 1;
for( int i = head[u] ; i ; i = Graph[i].nxt )
DFS( Graph[i].to ), siz[u] += siz[Graph[i].to];
}/*}}}*/
};/*}}}*/
MyGraph tre[2];
char grid[MAXN], str[MAXN];
int cmpn[MAXN];
int su[MAXN << 2], cov[MAXN << 2];
LL ans = 0;
int N, M;
#define ID( x, y ) ( ( (x) - 1 ) * M + (y) )
inline bool Inside( const int x, const int y )
{ return 1 <= x && x <= N && 1 <= y && y <= M; }
inline void Cover( const int x, const int delt, const int leaf )/*{{{*/
{
if( cov[x] += delt ) su[x] = 0;
else su[x] = leaf ? 1 : su[x << 1] + su[x << 1 | 1];
}/*}}}*/
inline void Upt( const int x ) { su[x] = cov[x] ? 0 : su[x << 1] + su[x << 1 | 1]; }
void Build( const int x, const int l, const int r )/*{{{*/
{
if( l > r ) return ; su[x] = cov[x] = 0;
if( l == r ) { su[x] = l > 1; return ; }
int mid = ( l + r ) >> 1;
Build( x << 1, l, mid );
Build( x << 1 | 1, mid + 1, r );
Upt( x );
}/*}}}*/
inline void Cover( const int x, const int l, const int r, const int segL, const int segR, const int delt )/*{{{*/
{
if( segL > segR ) return ;
if( segL <= l && r <= segR ) { Cover( x, delt, l == r && l > 1 ); return ; }
int mid = ( l + r ) >> 1;
if( segL <= mid ) Cover( x << 1, l, mid, segL, segR, delt );
if( mid < segR ) Cover( x << 1 | 1, mid + 1, r, segL, segR, delt );
Upt( x );
}/*}}}*/
int Query( const int x, const int l, const int r, const int segL, const int segR )/*{{{*/
{
if( segL > segR ) return 0;
if( segL <= segR ) return su[x];
int mid = ( l + r ) >> 1, ret = 0;
if( segL <= mid ) ret += Query( x << 1, l, mid, segL, segR );
if( mid < segR ) ret += Query( x << 1 | 1, mid + 1, r, segL, segR );
return ret;
}/*}}}*/
void DFS( const int u )/*{{{*/
{
if( u )
{
int d = tre[1].DFN[cmpn[u]], s = tre[1].siz[cmpn[u]];
Cover( 1, 1, tre[1].ID, d, d + s - 1, 1 );
ans -= su[1];
}
for( int i = tre[0].head[u] ; i ; i = tre[0][i].nxt )
DFS( tre[0][i].to );
if( u )
{
int d = tre[1].DFN[cmpn[u]], s = tre[1].siz[cmpn[u]];
Cover( 1, 1, tre[1].ID, d, d + s - 1, -1 );
}
}/*}}}*/
int main()
{
read( N ), read( M );
rep( i, 1, N )
{
scanf( "%s", str + 1 );
rep( j, 1, M ) grid[ID( i, j )] = str[j];
}
rep( i, 1, N ) rep( j, 1, M )
{
int col = ( i + j ) & 1, tx, ty;
if( grid[ID( i, j )] == 'L' ) tx = i, ty = j + 2, cmpn[ID( i, j )] = ID( i, j + 1 );
if( grid[ID( i, j )] == 'R' ) tx = i, ty = j - 2, cmpn[ID( i, j )] = ID( i, j - 1 );
if( grid[ID( i, j )] == 'U' ) tx = i + 2, ty = j, cmpn[ID( i, j )] = ID( i + 1, j );
if( grid[ID( i, j )] == 'D' ) tx = i - 2, ty = j, cmpn[ID( i, j )] = ID( i - 1, j );
if( ! Inside( tx, ty ) ) tre[col].AddEdge( 0, ID( i, j ) );
else tre[col].AddEdge( ID( tx, ty ), ID( i, j ) );
}
ans = 1ll * ( N * M / 2 ) * ( N * M / 2 );
tre[1].DFS( 0 );
Build( 1, 1, tre[1].ID );
DFS( 0 );
write( ans ), putchar( '
' );
return 0;
}