• CF1368G Shifting Dominoes


    CF1368G

    有一个 (n imes m) 的棋盘,被 (1 imes 2) 的骨牌覆盖,保证 (2|n imes m)

    现在你需要执行以下操作:

    • 移去恰好一个骨牌。
    • 将其他骨牌沿着其长边进行移动。
    • 你需要保证每张骨牌的最终位置与初始位置至少有一个交点。

    求你通过若干次操作后可以得到的所有可能的局面的数量。

    两种局面不同,当且仅当在其中一者中某个位置被骨牌覆盖,而另一者没有。

    (ncdot mle 2cdot 10^5)

    输入格式为:输入一张大小为 (n imes m) 的矩阵,位置 ((i,j))(L,R,U,D) 表示其被一张骨牌的左/右/上/下端覆盖。

    Solution

    假设位置 ((i,j)) 没有骨牌,那么染色为红色,否则为蓝色。

    我们发现答案的上界是 ((n imes m)^2),因为红色格子只有两个。

    然后我们发现由于限制 (2)(3),相当于每张骨牌只能移动一格(假设移动了两格,不难推出矛盾,或者等效于一格)

    我们发现移动一张骨牌,相当于将一张红色格子进行移动,如下面这个例子:

    RBB 变成 BBR

    这样我们将 R 的起点与终点连一条边。

    我们将这张图进行二分染色,我们发现每个骨牌会连出去两条边,恰好一条边连接了两个黑点,一条边连接了两个白点。同时,我们的起始状态是两个相邻的白点和黑点,于是这两个点只会在黑色点和白色点中移动。

    接着我们来考虑,什么情况下这两个点的行动是合法的:

    我们假设两个点初始在的位置摆放是横着的,即形如 RR

    我们发现每次使用一张骨牌来移动点会使得他们的竖直或横向距离加 (0/2),所以他们的竖直距离至少为奇数,横向距离为偶数。

    考虑什么情况的行动是非法的,那么就有一张骨牌满足同时协助了两个点的移动。

    我们发现这张骨牌一定不会是竖直的,因为两个点的横向距离此时为 (0),所以这张骨牌一定是横向的,此时两个点移动到他身上时即为非法状态。

    然而此时可以视为我们以这张骨牌为起点的合法移动。

    于是所有可能的移动均可以被视为为合法的。

    我们只需要计算以所有骨牌为起点,两个起点任意进行移动,可以得到的本质不同的棋盘的数量。

    唯一需要注意的点是,从某个骨牌处开始出发,我们进行的移动是不能包含其自身所代表的边的。

    考虑对黑色点,白色点建出两张独立的图。

    我们发现由于只有完整的骨牌,所以这张图上不存在环。因为如果存在环,意味着原图上存在一个类似于闭环的结构。

    即形如:

    ULR
    D.U
    LRD
    

    此时我们发现其内部的点数必然为奇数。类似的(通过手玩+大胆猜测)可以说明对于更复杂的闭环结果,其内部的点数仍然为奇数。

    由于我们的图完整的放置了所有的骨牌,所以这类结构无法存在,于是这张图上无环。

    基于这样的考量我们假设补充一个点代表外界,那么此时这张图上有 (nm+1) 个点,(nm) 条边,没有环,那么一定是树。

    我们发现一对黑点和白点可以产生贡献的条件为:

    1. 他们是一张骨牌的两个点。
    2. 存在一张骨牌满足删去其代表的两条边后可以联通他们所在的连通块。

    由于我们已知树的形态,所以接着我们这样考虑:对于每张骨牌形如 xLRy,我们连接 (x o R)(y o L) 的两条有向边,假设出去了那么对外界建一个点,不难发现此时一对黑白点能够产生贡献当且仅当他们存在一对祖先是一组骨牌的 (L,R)

    我们考虑对这棵树进行 dfs。

    当我们在树上进行 dfs 的时候,我们每遇到一个 (R/L),就给对应的另一棵子树打上标记。

    不难发现我们只需要统计序列上非 (0) 的元素的个数。

    由于元素的非负性,我们统计最小值,和最小值的出现次数。

    然后用总数减去即可,然后我们发现每个对子会被计算两次,所以答案除以 (2) 即可。

    这样只需要线段树即可解决此题,复杂度 (mathcal O(nlog n))

    (Code:)

    #include<bits/stdc++.h>
    using namespace std ;
    #define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
    #define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
    #define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
    #define re register
    #define vi vector<int>
    #define pb push_back
    #define ls(x) (x << 1)
    #define rs(x) (x << 1 | 1)
    #define int long long
    int gi() {
    	char cc = getchar() ; int cn = 0, flus = 1 ;
    	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
    	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
    	return cn * flus ;
    }
    const int N = 2e5 + 5 ; 
    int n, m, lim, cnt, Ans, L[N], R[N], g[N] ; 
    vi a[N], G[N] ; 
    char s[N] ; 
    void add(int x, int y) { G[y].pb(x) ; }
    struct Tr {
    	int mi, w, tag ; 
    } tr[N << 2] ; 
    int Id(int x, int y) {
    	if( x < 1 || y < 1 || x > n || y > m ) return 0 ; 
    	return (x - 1) * m + y ; 
    }
    void pushup(int x) {
    	tr[x].mi = min(tr[ls(x)].mi, tr[rs(x)].mi), tr[x].w = 0;
    	if(tr[x].mi == tr[ls(x)].mi) tr[x].w += tr[ls(x)].w ;
    	if(tr[x].mi == tr[rs(x)].mi) tr[x].w += tr[rs(x)].w ; 
    }
    void pushmark(int x) {
    	int u = tr[x].tag ; tr[x].tag = 0 ; 
    	tr[ls(x)].mi += u, tr[rs(x)].mi += u, 
    	tr[ls(x)].tag += u, tr[rs(x)].tag += u ; 
    }
    void update(int x, int l, int r, int ql, int qr, int k) {
    	if( ql <= l && r <= qr ) return tr[x].mi += k, tr[x].tag += k, void() ;
    	if( l > qr || r < ql ) return ; 
    	int mid = (l + r) >> 1 ; pushmark(x) ; 
    	update(ls(x), l, mid, ql, qr, k), update(rs(x), mid + 1, r, ql, qr, k),
    	pushup(x) ; 
    }
    void dfs(int x) {
    	L[x] = ++ cnt ; 
    	for(int v: G[x]) dfs(v) ;
    	R[x] = cnt ; 
    }
    void build(int x, int l, int r) {
    	tr[x].w = r - l + 1 ; 
    	if( l == r ) return ;
    	int mid = (l + r) >> 1 ; 
    	build(ls(x), l, mid), build(rs(x), mid + 1, r) ;  
    }
    void Dfs(int x) {
    	if(x) {
    		update(1, 1, cnt, L[g[x]], R[g[x]], 1) ; 
    		Ans += (cnt - tr[1].w) ;
    	}
    	for(int v : G[x]) Dfs(v) ;
    	if(x) update(1, 1, cnt, L[g[x]], R[g[x]], -1) ;
    }
    signed main()
    {
    	n = gi(), m = gi(), lim = n * m ; 
    	rep( i, 1, n ) {
    		scanf("%s", s + 1 ), a[i].pb(0) ; 
    		rep( j, 1, m ) {
    			if(s[j] == 'L') a[i].pb(1) ;
    			if(s[j] == 'R') a[i].pb(2) ;
    			if(s[j] == 'U') a[i].pb(3) ;
    			if(s[j] == 'D') a[i].pb(4) ; 
    		}
    	}
    	rep( i, 1, n ) rep( j, 1, m ) {
    		int u = Id(i, j) ; 
    		if(a[i][j] == 1) add(u, Id(i, j + 2)), g[u] = Id(i, j + 1) ;
    		if(a[i][j] == 2) add(u, Id(i, j - 2)), g[u] = Id(i, j - 1) ;
    		if(a[i][j] == 3) add(u, Id(i + 2, j)), g[u] = Id(i + 1, j) ;
    		if(a[i][j] == 4) add(u, Id(i - 2, j)), g[u] = Id(i - 1, j) ;
    	}
    	dfs(0), build(1, 1, cnt), Dfs(0) ;
    	cout << Ans / 2 << endl ; 
    	return 0 ;
    }
    
  • 相关阅读:
    localX mouseX stageX
    帮陈云庆做的手机报
    另一种换行排列方块的方法
    换行排列(思路源自陈勇源代码)
    网上摘的
    ASP.NET页面间数据传递(转)
    数据库连接字符串大全 之 SQL服务器篇
    保存一个免费的在线的图片转换工具网站,支持BMP,JPG,IOC,PNG和GIF
    关于IE6和IE7以及多个版本IE共存的问题
    如何测试sql语句性能,提高执行效率
  • 原文地址:https://www.cnblogs.com/Soulist/p/13773269.html
Copyright © 2020-2023  润新知