题面

有一个

n

×

m

n\times m

n×m 的棋盘,被

1

×

2

1\times 2

1×2 的骨牌覆盖,保证

2

n

×

m

2|n\times m

2∣n×m。

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

移去恰好一个骨牌。
将其他骨牌沿着其长边进行移动。
你需要保证每张骨牌的最终位置与初始位置至少有一个交点。
求你通过若干次操作后可以得到的所有可能的局面的数量。

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

1

n

×

m

2

×

1

0

5

1\leq n\times m\leq 2\times10^5

1≤n×m≤2×105

题解

这个题要转化很久啊,

不同的局面,翻译过来就是最终两个空块的位置方案

因此我们考虑空块的移动,如果一个多米诺骨牌的一端面向一个空块,就可以通过移动将这个空块的位置变成该骨牌的另一端,于是把它抽象成两个位置之间的一条有向边。一个骨牌可以产生两条有向边,但是每个骨牌只能移动一步,所以对于不小心使用了该骨牌两条有向边的情况,可以等价于最初移去的是该骨牌

对于该有向图还有不错的性质。由于每次移动与上一次位置的曼哈顿距离都是 2,所以假使我们对棋盘进行黑白染色,那么最初的两个空块一定颜色不同,而且只有相同颜色的位置之间有边。我们可以发现,每个点的入度最多为 1,并且可以证明没有环:

如果存在一个环,那么(在原来的棋盘上)环的长度为 4 的倍数,除以 2 后是偶数,由该环组成的封闭图形面积也是偶数,所以由皮克公式

S

(

2

a

)

=

B

2

(

2

b

)

+

I

1

S(2a)=\frac{B}{2}(2b)+I-1

S(2a)=2B​(2b)+I−1 可得环内的面积(

I

I

I)为奇数,但是环内不会有空块(两个空块的来源必须得是同一个骨牌),所以环内必须能放满骨牌,这与面积为奇数矛盾,因此不存在环。

所以整个有向图是森林。

一种局面可达,当且仅当这两个位置跑反图分别可达的节点中,存在一对节点是原来某个骨牌的两端。为了方便,我们规定原来某个骨牌两端的点是朋友

我们遍历黑色点的树,然后对于每个点到根的链,我们处理出链上结点的所有朋友跑正图(往叶子方向)可达的节点数,就是这个黑点的贡献。

计算可达的节点数要去重,这不难。我们处理出白森林中每个点的 dfs 序(不同树点 dfs 序不同),放到线段树上去重就是了。

可以用可回退线段树(不好打),也可以用可持久化。

时间复杂度

O

(

n

m

log

(

n

m

)

)

O(nm\log(nm))

O(nmlog(nm)) 。

CODE

  1. #include<set>
  2. #include<map>
  3. #include<queue>
  4. #include<stack>
  5. #include<bitset>
  6. #include<random>
  7. #include<vector>
  8. #include<cstdio>
  9. #include<cstring>
  10. #include<iostream>
  11. #include<algorithm>
  12. using namespace std;
  13. #define MAXN 200005
  14. #define LL long long
  15. #define DB double
  16. #define ENDL putchar('\n')
  17. #define lowbit(x) (-(x) & (x))
  18. #define FI first
  19. #define SE second
  20. #define BI bitset<35>
  21. namespace{ // 良好封装习惯,不怕重名
  22. LL read() {
  23. LL f=1,x=0;int s = getchar();if(s < 0) return -1;
  24. while(s < '0' || s > '9') {if(s == '-')f=-f;s = getchar();}
  25. while(s >= '0' && s <= '9') {x=x*10+(s^48);s = getchar();}
  26. return f * x;
  27. }
  28. void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
  29. void putnum(LL x) {
  30. if(!x) {putchar('0');return ;}
  31. if(x<0) {putchar('-');x = -x;}
  32. return putpos(x);
  33. }
  34. void AIput(LL x,int c) {putnum(x);putchar(c);}
  35. int n,m,s,o,k;
  36. char ss[MAXN],a[MAXN];
  37. int id[MAXN],cn,fr[MAXN];
  38. int to[MAXN];
  39. int I(int x,int y) {return (x-1)*m+y;}
  40. int X(int i) {return (i-1)/m + 1;}
  41. int Y(int i) {return (i-1)%m + 1;}
  42. int ind[MAXN];
  43. struct it{
  44. int ls,rs;
  45. int nm,lz;
  46. it(){ls=rs=0;nm=0;lz=0;}
  47. }tre[MAXN*64];
  48. int CNT;
  49. int addtree(int a,int l,int r,int al,int ar) {
  50. if(l > r || al > r || ar < l || tre[a].lz) return a;
  51. tre[++ CNT] = tre[a]; a = CNT;
  52. if(al >= l && ar <= r) {tre[a].lz=1;tre[a].nm = ar-al+1;return a;}
  53. int md = (al + ar) >> 1;
  54. tre[a].ls = addtree(tre[a].ls,l,r,al,md);
  55. tre[a].rs = addtree(tre[a].rs,l,r,md+1,ar);
  56. tre[a].nm = tre[tre[a].ls].nm + tre[tre[a].rs].nm;
  57. return a;
  58. }
  59. int findtree(int a,int l,int r,int al,int ar) {
  60. if(l > r || al > r || ar < l || !a) return 0;
  61. if(tre[a].lz) return min(ar,r) - max(l,al) + 1;
  62. if(al >= l && ar <= r) return tre[a].nm;
  63. int md = (al + ar) >> 1;
  64. return findtree(tre[a].ls,l,r,al,md) + findtree(tre[a].rs,l,r,md+1,ar);
  65. }
  66. int hd[MAXN],nx[MAXN];
  67. void ins(int x,int y) {nx[y] = hd[x];hd[x] = y;}
  68. int dfn[MAXN],rr[MAXN],tim;
  69. void dfs0(int x) {
  70. dfn[x] = ++ tim; for(int i = hd[x];i;i = nx[i]) dfs0(i); rr[x] = tim;
  71. }
  72. int rt[MAXN];
  73. LL dfs(int x,int ff) {
  74. rt[x] = addtree(rt[ff],dfn[fr[x]],rr[fr[x]],1,tim);
  75. LL rs = tre[rt[x]].nm;
  76. for(int i = hd[x];i;i = nx[i]) {
  77. rs += dfs(i,x);
  78. }return rs;
  79. }
  80. }
  81. int main() {
  82. n = read();m = read();
  83. for(int i = 1;i <= n;i ++) {
  84. scanf("%s",ss + 1);
  85. for(int j = 1;j <= m;j ++) {
  86. a[I(i,j)] = ss[j];
  87. if(a[I(i,j)] == 'R') id[I(i,j)] = id[I(i,j-1)] = ++ cn,fr[I(i,j)] = I(i,j-1),fr[I(i,j-1)] = I(i,j);
  88. else if(a[I(i,j)] == 'D') id[I(i,j)] = id[I(i-1,j)] = ++ cn,fr[I(i,j)] = I(i-1,j),fr[I(i-1,j)] = I(i,j);
  89. }
  90. }
  91. for(int i = 1;i <= n;i ++) {
  92. for(int j = 1;j <= m;j ++) {
  93. if(a[I(i,j)] == 'U') {
  94. if(i+2 <= n) to[I(i,j)] = I(i+2,j);
  95. }
  96. else if(a[I(i,j)] == 'D') {
  97. if(i-2 > 0) to[I(i,j)] = I(i-2,j);
  98. }
  99. else if(a[I(i,j)] == 'L') {
  100. if(j+2 <= m) to[I(i,j)] = I(i,j+2);
  101. }
  102. else if(j-2 > 0) to[I(i,j)] = I(i,j-2);
  103. }
  104. }
  105. for(int i = 1;i <= n*m;i ++) {
  106. if(to[i]) ins(to[i],i);
  107. }
  108. for(int i = 1;i <= n*m;i ++) {
  109. if((X(i)+Y(i)) % 2 == 0) {
  110. if(!to[i]) dfs0(i);
  111. }
  112. }
  113. LL ans = 0;
  114. for(int i = 1;i <= n*m;i ++) {
  115. if((X(i)+Y(i)) & 1) {
  116. if(!to[i]) ans += dfs(i,0);
  117. }
  118. }
  119. AIput(ans,'\n');
  120. return 0;
  121. }

CF1368G Shifting Dominoes (线段树)的更多相关文章

  1. bzoj3932--可持久化线段树

    题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...

  2. codevs 1082 线段树练习 3(区间维护)

    codevs 1082 线段树练习 3  时间限制: 3 s  空间限制: 128000 KB  题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作: 1:给区 ...

  3. codevs 1576 最长上升子序列的线段树优化

    题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...

  4. codevs 1080 线段树点修改

    先来介绍一下线段树. 线段树是一个把线段,或者说一个区间储存在二叉树中.如图所示的就是一棵线段树,它维护一个区间的和. 蓝色数字的是线段树的节点在数组中的位置,它表示的区间已经在图上标出,它的值就是这 ...

  5. codevs 1082 线段树区间求和

    codevs 1082 线段树练习3 链接:http://codevs.cn/problem/1082/ sumv是维护求和的线段树,addv是标记这歌节点所在区间还需要加上的值. 我的线段树写法在运 ...

  6. PYOJ 44. 【HNSDFZ2016 #6】可持久化线段树

    #44. [HNSDFZ2016 #6]可持久化线段树 统计 描述 提交 自定义测试 题目描述 现有一序列 AA.您需要写一棵可持久化线段树,以实现如下操作: A v p x:对于版本v的序列,给 A ...

  7. CF719E(线段树+矩阵快速幂)

    题意:给你一个数列a,a[i]表示斐波那契数列的下标为a[i],求区间对应斐波那契数列数字的和,还要求能够维护对区间内所有下标加d的操作 分析:线段树 线段树的每个节点表示(f[i],f[i-1])这 ...

  8. 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序

    3779: 重组病毒 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 224  Solved: 95[Submit][Status][Discuss] ...

  9. 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status ...

随机推荐

  1. Java绘图基础

    <零基础学Java> Java绘图基础 绘图是高级程序设计中非常重要的技术,例如,应用程序需要绘制闪屏图像.背景图像.组件外观Web程序可以绘制统计图.数据库存储的图像资源等. Graph ...

  2. 聊聊 C# 和 C++ 中的 泛型模板 底层玩法

    最近在看 C++ 的方法和类模板,我就在想 C# 中也是有这个概念的,不过叫法不一样,人家叫模板,我们叫泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的? 一:C++ 中的模板玩法 毕竟 C+ ...

  3. 想写个小说,关于C#的,名字就叫《原Csharp》吧 (第一回 买书未成炁自生 惶惶回屋遇老翁)

    以前也有写过一些小说,但是总是写写停停的,因为忙于项目和其他事情,总是耽搁很久(真的是很久)才会继续动两笔,所以我想先在这里以随笔的方式写个关于C#异世界的小故事吧,更新随缘,也稍微能让自己轻松些. ...

  4. 软件成分分析(SCA)完全指南

    上一篇文章中,我们讨论了 DAST 的概念.重要性及其工作原理.那在开发过程中如何查找开源软件包中的漏洞并学习如何修复?本指南带你一起了解 SCA 工具及其最佳实践. 如今,绝大多数代码驱动的应用程序 ...

  5. CVPR2022 | 弱监督多标签分类中的损失问题

    前言 本文提出了一种新的弱监督多标签分类(WSML)方法,该方法拒绝或纠正大损失样本,以防止模型记忆有噪声的标签.由于没有繁重和复杂的组件,提出的方法在几个部分标签设置(包括Pascal VOC 20 ...

  6. MES 系统介绍

    MES系统是一套面向制造企业车间执行层的生产信息化管理系统.MES可以为企业提供包括制造数据管理.计划排程管理.生产调度管理.库存管理.质量管理.人力资源管理.工作中心/设备管理.工具工装管理.采购管 ...

  7. 02 java包装类型的缓存机制

    02 java包装类型的缓存机制 Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能. Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,12 ...

  8. 单片机 MCU 固件打包脚本软件

    ​ 1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...

  9. key可重复的Map

    在正常的map操作中,key是不能重复的,如果希望key的内容可以重复,可以用IdentityHashMap 举个栗子 输出结果: public static void main(String[] a ...

  10. 聊聊 C++ 中几类特殊成员函数

    一:背景 在 C# 中要说类默认给我们定义的特殊成员函数,莫过于 构造函数,但在 C++ 中这样的特殊函数高达 6 种,有必要整合一下聊一聊. 二:特殊成员函数 1. 默认构造函数 和 C# 一样,很 ...