ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

TC14069 - BearDestroys 题解

2022-04-08 20:34:20  阅读:118  来源: 互联网

标签:int 题解 TC14069 alr a1 a0 type flg BearDestroys


考虑轮廓线 DP。注意到题意就是要求按照以横坐标为第一关键字、纵坐标为第二关键字进行模拟,而我们按这个顺序 DP 恰好是合适的。

需要记录什么?显然要知道当前位置是否能往右倒、往下倒。根据顺序,后者显然如果下面还有位置的话(并且当前位置没有被覆盖)那么一定可以。而是否能往右倒,还取决于 \((x-1,y+1)\) 是否往下倒。所以记录已决定区域的轮廓是否倒、往哪儿倒?这样有三种状态,是 \(3^m\),寄。注意到其实我们只关心未决定区域的轮廓是否被占领,这样就 \(2^m\) 了。

这题的 DP 转移要求求所有局面的倒的树的数量之和,非常适合定义一个组合对象(也就是维护低次和的套路)的结构体转移,定义加法、乘法、数乘,写的很爽。

然而可以发现这题最大的难点是 \(n\leq 13,m\leq 30\)……把 \(n,m\) 颠倒过来?sorry,做不到,因为必须按照题意中的顺序模拟,不然就不一定能知道当前位置是否能往右倒。

不过我们考虑轮廓线 DP 的本质:就是转移只与相邻位置有关,所以任意时刻只需要保留已决定区域与未决定区域的交界处的状态。而并没有规定 DP 顺序,只是一般使用从上到下、从左到右的顺序可以使得任意时刻的轮廓线上的位置数量达到了下限——\(\min(n,m)\)。而如今不能翻转棋盘,不妨考虑有没有其它 DP 顺序既使得无后效性(DP 能按照题意正常进行),轮廓线又比较小。

考虑为了满足无后效性需要怎样。如果要知道一个位置是否能往右倒,需要知道 \((x,y)\) 和 \((x,y+1)\) 分别是否被占领,而这只与 \((x,y-1),(x-1,y),(x-1,y+1)\) 这三个格子有关。也就是,这三个格子的决定时间一定要在 \((x,y)\) 前。而如果我们每次都选能决定的最低位置决定,手动演绎一波发现决定区域初期是一个三角形,轮廓线始终是副对角线方向的。而这样轮廓线显然时刻不超过 \(\min(n,m)\)。就可以做了,复杂度 \(\mathrm O(2^nnm)\)。

然而这样直接写实在是太复杂了,要对 mask 的变换进行一大大大大堆分类讨论。我采取的策略是:先预处理每一步决定的位置、轮廓线上元素的集合,转移时就维护一个 bool 数组,在里面修改,然后暴力获得新的 mask。这样写是真的很爽,虽然复杂度大了一点(\(\mathrm O\!\left(2^nnm^2\right)\)),但还是能过。

code
constexpr int N = 20, M = 35;

int n, m, nm;
int sx[N * M], sy[N * M];
vii sur[N * M];

struct type {
	int a0, a1;
	type(int a0 = 0, int a1 = 0) : a0(a0), a1(a1) {}
	friend type operator*(int f, type g) { return type((ll)f * g.a0 % P, (ll)f * g.a1 % P); }
	friend type operator+(type f, type g) { return type(add(f.a0, g.a0), add(f.a1, g.a1)); }
	friend void operator+=(type &f, type g) { f = f + g; }
	friend type operator*(type f, type g) { return type((ll)f.a0 * g.a0 % P, ((ll)f.a0 * g.a1 + (ll)f.a1 * g.a0) % P); }
} dp[N * M][1 << 14 | 10];
void trans(int s, int msk) {
	static bool a[N][M]; memset(a, 0, sizeof(a));
	auto nxtmsk = [&]() {
		int res = 0, now = 0;
		for(tii t : sur[s + 1]) if(++now, a[X(t)][Y(t)]) res |= 1 << now - 1;
		return res;
	};
	auto trs = [&](int v, type t) { dp[s + 1][nxtmsk()] += v * t * dp[s][msk]; /* 组合对象的乘法有线性性,所以可以不要括号 */ };
	REP(i, 0, SZ(sur[s]) - 1) if(msk >> i & 1) a[X(sur[s][i])][Y(sur[s][i])] = true;
	int x = sx[s + 1], y = sy[s + 1];
	if(a[x][y]) {
		trs(2, type(1, 0));
	} else {
		bool ok[2] = {y < m && !a[x][y + 1], x < n && !a[x + 1][y]};
		if(ok[0] && ok[1]) {
			a[x][y] = a[x][y + 1] = true, trs(1, type(1, 1)), a[x][y] = a[x][y + 1] = false;
			a[x][y] = a[x + 1][y] = true, trs(1, type(1, 1)), a[x][y] = a[x + 1][y] = false;
		} else if(ok[0]) {
			a[x][y] = a[x][y + 1] = true, trs(2, type(1, 1));
		} else if(ok[1]) {
			a[x][y] = a[x + 1][y] = true, trs(2, type(1, 1));
		} else trs(2, type(1, 0));
	}
}

struct BearDestroys {
int sumUp(int W, int H, int MOD) {
	m = W, n = H, P = MOD, nm = n * m;
	static bool alr[N][M];
	REP(i, 1, nm) {
		PER(x, n, 1) REP(y, 1, m) if(!alr[x][y]) {
			bool flg = true;
			if(y > 1) flg &= alr[x][y - 1];
			if(x > 1) flg &= alr[x - 1][y];
			if(x > 1 && y < m) flg &= alr[x - 1][y + 1];
			if(flg) {
				sx[i] = x, sy[i] = y;
				alr[x][y] = true;
				REP(x, 1, n) REP(y, 1, m) if(!alr[x][y]) {
					bool flg = false;
					flg |= x > 1 && alr[x - 1][y];
					flg |= x < n && alr[x + 1][y];
					flg |= y > 1 && alr[x][y - 1];
					flg |= y < m && alr[x][y + 1];
					if(flg) sur[i].pb(x, y);
				}
				goto edlp;
			}
		} edlp:;
	}
	dp[0][0] = type(1, 0);
	REP(s, 0, nm - 1) REP(msk, 0, (1 << SZ(sur[s])) - 1) trans(s, msk);
	int ans = dp[nm][0].a1;
	return ans;
}
};

标签:int,题解,TC14069,alr,a1,a0,type,flg,BearDestroys
来源: https://www.cnblogs.com/ycx-akioi/p/solution-tc14069.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有