「CF1368G」Shifting Dominoes

题目

点这里看题目。

分析

先考虑枚举一个骨牌并将它取下来。这样,一个空格就可以通过周围的骨牌来向各个方向移动。

注意到,我们可以选取最终局面上的一个空格,并找出它原先在哪里——看一下初始平板上这个空格对应的字符,就可以确定现在这块骨牌向哪个方向移动了,我们就可以逆向操作;一直循环直到这个空格属于取下的那一块骨牌。

容易发现,每个空格只会对应一个逆向操作的结果,在图上考虑,也就是只会连出一条边。最终我们会得到两片基环树森林(我们可以通过二染色来划分某一棵树属于哪一片森林),不妨设为 \(F_1,F_2\)。

但是,我们真的可以得到环吗?如果尝试去画出一个带环的平板,你会发现这出奇地困难——甚至是不可能的。下面我们会加以证明:

假设每一小格内部都有一个格点。如果说平板上面出现了一个环,我们可以用 Pick 定理计算一下这个环内部格点的数量:

\[b=S-\frac{a}{2}+1 \]

其中 \(a\) 为边上的格点数目,\(S\) 为面积,\(b\) 为内部的格点数目。

不难发现 \(\frac a 2\) 一定是偶数。从环上任选一个起点,则相对于它,最终环上的 \(\sum \Delta x=\sum\Delta 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_1\in F_1,c_2\in 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(nm\log nm)\)。

小结:

  1. 学会转化题目的条件。此题中如果没有转化好 \((c_1,c_2)\) 的合法条件,就会很难下手;
  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( '\n' );
    return 0;
}
上一篇:2. Git 进阶 --- 分支


下一篇:AllanCodeMaker 代码生成器 release0.9.0 下载 支持C#,Java,可自订模板