• 「CF1368G」Shifting Dominoes


    题目

    点这里看题目。

    分析

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

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

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

    小结:

    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( '
    ' );
        return 0;
    }
    
  • 相关阅读:
    java 自定义表单 动态表单 表单设计器 工作流引擎 flowable 设计方案
    设置Springboot返回jackson数据序列化
    SpringCloud Alibaba 报 AbstractMethodError 是版本兼容问题导致
    Springboot进阶JDBC、Druid、Mybatis、Swagger、SpringMVC、Mail
    java 自定义表单 动态表单 表单设计器 工作流引擎 flowable 项目源码
    springcloud Alibaba 微服务 flowable 工作流 自定义表单 vue.js前后分离
    ClassLoader读取文件,springboot打jar包后读取不到
    Nginx/Tomcat/Apache的优缺点和区别
    Swagger3 相比2配置变了
    webloginc配置项目根目录
  • 原文地址:https://www.cnblogs.com/crashed/p/15422041.html
Copyright © 2020-2023  润新知