• CF1368G Shifting Dominoes


    CF 1368 G

    题目链接

    点击打开

    题目叙述

    给出一个(n imes m)的棋盘,布满了 (1 imes2) 的多米诺骨牌,告诉你多米诺骨牌的排列情况。每次去掉一个股派,然后可以将其他骨牌挪动。每个骨牌都会被去掉一次。挪动后必须与最开始的位置至少公用一个位置。并且骨牌挪动方向必须平行于骨牌原本的方向(即不能“拐弯”)。问挪动若干次后剩下的两个空格的方案数。

    题解

    问题转化

    考虑将骨牌移动想象成空格移动。对于一个空位置 ((r,c)) (第 (r) 行第 (c) 列),如果 ((r,c)) 下面 ((r+1,c),(r+2,c)) 是一个多米诺骨牌,那么 ((r,c)) 这个空格可以移动到 ((r+2,c)) 。这样连出若干条边,构成一个图。容易知道每个点的入读均为 1.

    下面证明这是一个树。只要证明不存在一个环即可。如果存在一个环,那么一定形如这样:

    考虑下面这样的形状:

    可以将这样的东西向外翻,一次将会添加 4 个格子,这样不断操作一定能操作成一个长方形。然后可以发现中间空出来一个长宽均为奇数的长方形,很明显这样多米诺骨牌并不能将这个长方形填满。

    解决

    所以像上面那样连边将会连出一个森林。只要一对格子可以被两个组成一个多米诺骨牌的格子移动到,那么就算 1 的贡献。即对于两个结点,存在两个祖先满足他们组成多米诺骨牌的两个格子,那么就算 1 的贡献。

    这个问题可以转化为一个矩阵面积并的问题。考虑将每一对点 ((x,y)) ,将其对应到平面上的点 ((dfn_x,dfn_y))(dfn_i)(i) 结点的 (dfs) 序)。现在枚举每一块骨牌,如果是由编号为 (x)(y) 的树上的结点所对应的方格组成,那么就将 (x) 的子树所对应的区间作为 (x) 轴的范围, (y) 的子树对应的区间最为 (y) 轴的范围,给这样组成的矩形中的所有点覆盖。通过一共覆盖的点数量即可算出答案。覆盖点数只需要使用扫描线求解即可。

    实现

    会发现树上结点 (x)(y) 组成的矩形与 (y)(x) 组成的矩形不一样。仔细思考可以发现都覆盖上,再将答案/2即可。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int NTOT = 2e5 + 5;
    int N, M, deg[NTOT], root[NTOT], totr;
    int in[NTOT], out[NTOT], dfstime;
    char str[NTOT];
    inline int dw(int r, int c) { return (r-1)*M+c; }
    int head[NTOT];
    struct E {
    	int v, nxt;
    	E(int _v, int _n) : v(_v), nxt(_n) {}
    	E() {}
    } edge[NTOT*2];
    int totE;
    void AddEdge(int u, int v) {
    	edge[++totE] = E(v, head[u]);
    	head[u] = totE;
    }
    void Dfs(int u) {
    	in[u] = ++dfstime;
    	for (int p = head[u]; p; p = edge[p].nxt) {
    		int v = edge[p].v;
    		Dfs(v);
    	}
    	out[u] = dfstime;
    }
    int tsqr;
    struct Square {
    	int u, d, l, r;
    	Square(int _u, int _d, int _l, int _r) : u(_u), d(_d), l(_l), r(_r) {}
    	Square() {}
    } sqr[NTOT];
    int tqz;
    struct smx {
    	int u, d, ps, ad;
    	smx(int _u, int _d, int _p, int _a) : u(_u), d(_d), ps(_p), ad(_a) {}
    	smx() {}
    } qz[NTOT*2];
    bool cmp(smx &a, smx &b) { return a.ps < b.ps; }
    int tag[NTOT*4], cnt[NTOT*4];
    int Len(int L, int R) { return R - L + 1; }
    int Cal(int p, int L, int R) {
    	if (!tag[p]) return cnt[p];
    	else return Len(L, R);
    }
    void AddSeg(int p, int L, int R, int ql, int qr, int a) {
    	if (qr < L || R < ql)
    		return ;
    	if (ql <= L && R <= qr) {
    		tag[p] += a;
    		return ;
    	}
    	int m = (L + R) >> 1;
    	AddSeg(p<<1, L, m, ql, qr, a);
    	AddSeg(p<<1|1, m+1, R, ql, qr, a);
    	cnt[p] = Cal(p<<1, L, m) + Cal(p<<1|1, m+1, R);
    }
    int main() {
    	scanf("%d%d", &N, &M);
    	for (int i = 1; i <= N; ++i)
    		scanf("%s", str + (i-1) * M + 1);
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j) {
    			if (i-2 >= 1 && str[dw(i-2, j)] == 'U') {
    				AddEdge(dw(i, j), dw(i-2, j));
    				++deg[dw(i-2, j)];
    			} 
    				
    			if (j-2 >= 1 && str[dw(i, j-2)] == 'L') {
    				AddEdge(dw(i, j), dw(i, j-2));
    				++deg[dw(i, j-2)];
    			}
    				
    			if (i+2 <= N && str[dw(i+2, j)] == 'D') {
    				AddEdge(dw(i, j), dw(i+2, j));
    				++deg[dw(i+2, j)];
    			}
    				
    			if (j+2 <= M && str[dw(i, j+2)] == 'R') {
    				AddEdge(dw(i, j), dw(i, j+2));
    				++deg[dw(i, j+2)];
    			}
    		}
    	for (int i = 1; i <= N*M; ++i)
    		if (!deg[i])
    			root[++totr] = i;
    	for (int i = 1; i <= totr; ++i)
    		Dfs(root[i]);
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j) {
    			if (str[dw(i, j)] == 'U' && str[dw(i+1, j)] == 'D') {
    				sqr[++tsqr] = Square(in[dw(i, j)], out[dw(i, j)], in[dw(i+1, j)], out[dw(i+1, j)]);
    				sqr[++tsqr] = Square(in[dw(i+1, j)], out[dw(i+1, j)], in[dw(i, j)], out[dw(i, j)]);
    			} else if (str[dw(i, j)] == 'L' && str[dw(i, j+1)] == 'R') {
    				sqr[++tsqr] = Square(in[dw(i, j)], out[dw(i, j)], in[dw(i, j+1)], out[dw(i, j+1)]);
    				sqr[++tsqr] = Square(in[dw(i, j+1)], out[dw(i, j+1)], in[dw(i, j)], out[dw(i, j)]);
    			}
    		}
    	for (int i = 1; i <= tsqr; ++i) {
    		qz[++tqz] = smx(sqr[i].u, sqr[i].d, sqr[i].l, 1);
    		qz[++tqz] = smx(sqr[i].u, sqr[i].d, sqr[i].r+1, -1);
    	}
    	sort(qz + 1, qz + tqz + 1, cmp);
    	long long ans = 0;
    	for (int i = 1; i <= tqz; ++i) {
    		AddSeg(1, 1, N*M+1, qz[i].u, qz[i].d, qz[i].ad);
    		if (i != tqz) ans += (long long)Cal(1, 1, N) * (qz[i+1].ps - qz[i].ps);
    //		for (int j = 1; j <= 8; ++j)
    //			printf("tag[j] : %d cnt[j] : %d
    ", tag[j], cnt[j]);
    	}
    	printf("%lld
    ", ans / 2);
    	return 0;
    }
    

    总结

    • 点对数量转化为平面上点的数量
    • 一个点是另一个点的子孙可以转化为 dfs 序的区间限制,可以在计数题上用到
  • 相关阅读:
    浅析堆与垃圾回收
    再探JVM内存模型
    索引使用的基本原则
    常见的索引模型浅析
    初识InnoDB体系架构和逻辑存储结构
    一条update SQL语句是如何执行的
    MySQL一条查询语句是如何执行的
    堆与优先队列
    ibatis BindingException Parameter 'status' not found. Available parameters are [arg1, arg0, param1, param2] 解决方法
    Mysql通过MHA实现高可用
  • 原文地址:https://www.cnblogs.com/YouthRhythms/p/13788040.html
Copyright © 2020-2023  润新知