首页 > 其他分享 >「清新题精讲」TwoConvexShapes

「清新题精讲」TwoConvexShapes

时间:2024-05-30 22:13:02浏览次数:8  
标签:颜色 int 精讲 sw grid sb TwoConvexShapes 清新

TwoConvexShapes

Statement

给定 \(n\times m\) 的网格,包含 ?BW 三种字符,其中 ? 表示可以填 BW。问在所有的填法中有多少种填法满足如下条件:

  1. BW 颜色均为连通的,即不存在一个颜色,四个方向中没有这个颜色(除了该颜色只有 \(1\) 个的情况)。
  2. 不存在一列或一行,同种颜色的两个位置之间存在其他颜色。

Solution

Hint 1: 考虑怎样的填法满足条件?

针对其中 \(1\) 个颜色讨论即可,剩余的位置即为另一个颜色。假设对于 B 讨论,则对于列的限制,不能有一列上存在两个 B 的连通块,也就是说在集中在上方,或集中在下方。对于行的限制,其实就是要么单调不降,要么单调不升。

注意:下图只是给出了 \(2\) 种情况,这样排列是不合法的,但是只保留蓝色部分或红色部分即是合法的。

Hint 2: 如何快速的统计方案数?

发现规律后,可以对于红色部分的答案和蓝色部分的答案分别计算。以红色为例,设 \(f_{j,i,0/1}\) 表示第 \(j\) 列,已经到达第 \(i\) 行,是上升趋势 / 下降趋势。转移进需要判断当前第 \(j\) 列能否填至第 \(i\) 行即可,这个可以提前预处理。

//预处理代码,pb[i][j] 表示第 j 列前 i 行能否全填 B,sb[i][j] 表示第 j 列前 i 行能否全填 B;对于 W 同理
for (int j = 1; j <= m; j ++) {
    pb[0][j] = pw[0][j] = 1;
    for (int i = 1; i <= n; i ++) {
        pb[i][j] = pb[i - 1][j] & (grid[i - 1][j - 1] != 'W');
        pw[i][j] = pw[i - 1][j] & (grid[i - 1][j - 1] != 'B');
    }
}
for (int j = 1; j <= m; j ++) {
    sb[n + 1][j] = sw[n + 1][j] = 1;
    for (int i = n; i >= 1; i --) {
        sb[i][j] = sb[i + 1][j] & (grid[i - 1][j - 1] != 'W');
        sw[i][j] = sw[i + 1][j] & (grid[i - 1][j - 1] != 'B');
    }
}

预处理出后,便可以转移:\(f_{j,i,0}=\sum_{k=0}^{i}f_{j-1,k,0}\)。对于 \(1\) 也是同理的,只需要更改 \(k\) 的上下界即可。对于蓝色部分,其实没必要再更改到下面,会不好写,所以其实等价于计算 W 颜色的红色部分,因此只需稍作更改转移的条件即可。

Hint 3: 检验是否算重?

都这么问了,那肯定是算重了!仅针对于 \(1\) 个部分而言,光单调不降和单调不升就会算重,即全部是一个高度(不升也不降)会算 \(2\) 次,所以减掉 \(1\) 次即可(不过,前提是能保证同一个高度是合法的)。

但是,对于蓝色和红色部分会算重吗?答案是肯定的。当都填满了若干列,剩余列均为填满的时候,会算重(如图)。

Ok,那么这样你就得到了一个 \(O(n^2m)\) 的做法,已经能通过此题。不过,作为完美主义者,我们要进一步优化,其实优化也很简单——只需要做一个前缀和即可,因为你发现上面的转移即为一个前缀和。这样,可以做到 \(O(nm)\) 了,所以 \(n\) 完全可以开到 \(10^4\) 的。

Code

注意:省略了取模板子和头文件,所以复制是 AC 不了的。

const int N = 55;

Mint f[N][N][2];
int pb[N][N], pw[N][N], sb[N][N], sw[N][N];

class TwoConvexShapes {
public:
	int countWays(std::vector<string> grid) {
		int n = grid.size(), m = grid[0].size();
		for (int j = 1; j <= m; j ++) {
			pb[0][j] = pw[0][j] = 1;
			for (int i = 1; i <= n; i ++) {
				pb[i][j] = pb[i - 1][j] & (grid[i - 1][j - 1] != 'W');
				pw[i][j] = pw[i - 1][j] & (grid[i - 1][j - 1] != 'B');
			}
		}
		for (int j = 1; j <= m; j ++) {
			sb[n + 1][j] = sw[n + 1][j] = 1;
			for (int i = n; i >= 1; i --) {
				sb[i][j] = sb[i + 1][j] & (grid[i - 1][j - 1] != 'W');
				sw[i][j] = sw[i + 1][j] & (grid[i - 1][j - 1] != 'B');
			}
		}
		std::vector<Mint> ics(n + 1, 1), dcs(n + 1, 0);
		f[0][0][0] = f[0][n][1] = 1, dcs[n] += 1;
		for (int j = 1; j <= m; j ++) {
			for (int i1 = 0; i1 <= n; i1 ++) {
				if (!pb[i1][j] || !sw[i1 + 1][j]) continue;
				f[j][i1][0] = ics[i1];
				f[j][i1][1] = dcs[n] - (!i1 ? 0 : dcs[i1 - 1]);
			}
			ics[0] = f[j][0][0], dcs[0] = f[j][0][1];
			for (int i1 = 1; i1 <= n; i1 ++)
				ics[i1] = ics[i1 - 1] + f[j][i1][0], dcs[i1] = dcs[i1 - 1] + f[j][i1][1];
		}
		Mint res = 0;
		for (int i = 0; i <= n; i ++)
			res += f[m][i][0] + f[m][i][1] - (min(f[m][i][0].v, f[m][i][1].v) > 0);
		
		memset(f, 0, sizeof f);
		for (int i = 0; i <= n; i ++) ics[i] = 1, dcs[i] = 0;
		f[0][0][0] = f[0][n][1] = 1, dcs[n] += 1;
		for (int j = 1; j <= m; j ++) {
			for (int i1 = 0; i1 <= n; i1 ++) {
				if (!pw[i1][j] || !sb[i1 + 1][j]) continue;
				f[j][i1][0] = ics[i1];
				f[j][i1][1] = dcs[n] - (!i1 ? 0 : dcs[i1 - 1]);
			}
			ics[0] = f[j][0][0], dcs[0] = f[j][0][1];
			for (int i1 = 1; i1 <= n; i1 ++)
				ics[i1] = ics[i1 - 1] + f[j][i1][0], dcs[i1] = dcs[i1 - 1] + f[j][i1][1];
		}

		for (int i = 0; i <= n; i ++)
			res += f[m][i][0] + f[m][i][1] - (min(f[m][i][0].v, f[m][i][1].v) > 0);

		memset(f, 0, sizeof f);
		f[0][0][0] = f[0][n][1] = 1;
		for (int j = 1; j <= m; j ++) {
			if (pw[0][j] && sb[1][j]) {
				f[j][0][0] += f[j - 1][0][0];
				f[j][0][1] += f[j - 1][0][1] + f[j - 1][n][1];
			}
			if (pw[n][j] && sb[n + 1][j]) {
				f[j][n][0] += f[j - 1][0][0] + f[j - 1][n][0];
				f[j][n][1] += f[j - 1][n][1];
			}
		}

		res -= (f[m][0][0] + f[m][0][1] - (min(f[m][0][0].v, f[m][0][1].v) > 0) + f[m][n][0] + f[m][n][1] - (min(f[m][n][0].v, f[m][n][1].v) > 0));

		return res.v;
	}
};

标签:颜色,int,精讲,sw,grid,sb,TwoConvexShapes,清新
From: https://www.cnblogs.com/Tiny-konnyaku/p/18223324

相关文章

  • 学完《编辑器扩展精讲》总结
    学完《编辑器扩展精讲》总结思维导图思维导图pos下载结构POS文件下载代码仓库gitee......
  • 「清新题精讲」MagicalHats
    SRM549-MagicalHatsStatement给定\(n\timesm\)的地图,.表示空地,H表示帽子,帽子下会有不超过\(1\)个硬币。接下来有numGuesses次操作,每一次魔法师将会改变硬币的位置,然后你将选择\(1\)个帽子,获得的价值为帽子底下硬币的价值(如果无硬币,则价值为\(0\))魔法师希望你获......
  • 「清新题精讲」CheckerExpansion
    SRM550-500CheckerExpansionDescription起初\((0,0)\)点Alice填为了红色,接下来\(t\)次操作Alice和Bob轮流操作,如果\((x-1,y-1)\)位置被另一方填了且\((x-2,y)\)为空;或\((x-1,y-1)\)为空且\((x-2,y)\)被另一方填了,则当前方将填\((x,y)\)。给定\(x,y,h,w......
  • 「清新题精讲」Skiers
    SkiersDescription给定\(n\)个点的有向无环平面图,求最少多少条从\(1\)到\(n\)的路径能覆盖原图的所有边?\(1\len\le5\times10^3\)Solution考虑从\(1\)到\(n\)的路径其实是边的链覆盖,那么最小链覆盖即为求解的答案。通过Dilworth定理可知,最小链覆盖等于最大反链,......
  • Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——高性能的 gRPC
    远程过程调用RPC——高性能的gRPC gRPC,这一由Google推出的高性能、开源、通用RPC框架,凭借其众多引人注目的特性,已成为业界瞩目的焦点。它基于HTTP/2协议标准设计开发,并采用ProtocolBuffers作为默认的数据序列化协议,广泛支持多种编程语言。gRPC不仅简化了服务的精确定义,而且......
  • Bash脚本语法解析(典例精讲)
    参考资料:https://github.com/AUTOMATIC1111/stable-diffusion-webuihttps://razeen.me/posts/the-ultimate-programmers-guide-to-bash-scripting/众所周知.sh文件是Linux系统中的脚本文件。(与之相对的还有windows系统上对应cmd的bat文件,对应powershell的ps1文......
  • BSP视频教程第30期:UDS ISO14229统一诊断服务CAN总线专题,常用诊断执行流程精讲,干货分享
    视频教程汇总帖:https://www.armbbs.cn/forum.php?mod=viewthread&tid=110519 【前言】1、继前面分享了CANopen和J1939的专题后,这次继续为大家分享UDS专题视频第1期。2、统一诊断服务(UnifiedDiagnosticServices,简称UDS)是车用电子的通信协议,是电子控制器ECU中设备诊断用的网......
  • Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——优化RPC调用,缓解频繁请求
    远程过程调用RPC——优化RPC调用,缓解频繁请求导致的GC压力 在Go语言的高并发和微服务架构中,远程过程调用(RPC)是一种常用的通信机制。然而,当频繁发送RPC请求时,不断创建Request和Response结构体可能会带来额外的垃圾收集(GC)压力,进而影响应用的性能和响应时间。为了减......
  • Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——客户端处理RPC请求的原理
    远程过程调用RPC——客户端处理RPC请求的原理及源代码分析 客户端无论是同步调用还是异步调用,每次RPC请求都会生成一个Call对象,并使用seq作为key保存在map中,服务端返回响应值时再根据响应值中的seq从map中取出Call,进行相应处理。 客户端发起RPC调用的过程大致如下所示,我们......
  • Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——服务端注册实现原理分析
    远程过程调用RPC——服务端注册实现原理分析rpcserver代码参考我前一篇博文:https://www.cnblogs.com/zuoyang/p/18146870RPCServer端的RPC代码架构主要由两大部分构成:第一部分是服务方法的注册过程。在这个过程中,我们首先通过调用rpc.Register接口将服......