Luogu7740 [NOI2021]机器人游戏 做题记录

link 一道炸裂的题目。

首先样例解释已经告诉我们可以容斥。考虑枚举可行的位置集合 S,我们需要统计 pS,纸条初始状态和目标状态都相同的方案数。

显然每个机器人独立,可以分开考虑。对于一个机器人,他的行动对纸条的每个格子要么赋值为 0/1,要么不变,要么取反,我们可以用 0/1/2/3 表示四种状态。

对于一个机器人和一个格子 i,我们统计出格子 i 能被修改成哪几种状态,即统计对于 0/1/2/3 四种状态,是否存在 pS,满足机器人从格子 p 出发能令格子 i 变为该状态。

若一个格子同时存在状态 0,1 或者同时存在状态 2,3,那么这只能是空格子,有 1 种方案;若一个格子同时存在 0/12/3,那么可以是 0/1 其中一个数字,或者空格子,有 2 中方案;若一个格子只存在一种状态,那么 3 种方案都可以。

n>16 时会超时。发现 nmax32,可以考虑折半算法。又发现机器人走出纸条时会爆炸,容易想到对于步数 16 的机器人,如果其纸条非空,那么 S 只能枚举前 16 个格子,这就可以直接暴力做了。当然,需要减掉所有步数 16 的机器人的纸条全是空的的方案数。

舍弃掉步数 16 的机器人,剩下的机器人最多走 15 步。从前往后考虑每个格子,其可以加入 S 或否,而该格子贡献的方案数只和前 15 个格子的状态、这 15 个格子之前是否有格子加入了 S、之后是否会有格子加入 S 三个信息有关。其中后两个信息决定了是否有除了这 15 个格子的其他格子对其的 2 状态的影响。

考虑枚举最后一个处于 S 中的格子,设 f[i,S,0/1] 表示考虑了前 i 个格子,其中前 15 个格子的状态为 S,在这 15 个格子之前是否有格子加入了集合,此时前 i 个格子的方案数。

综上时间复杂度为 O(2n2n(n+m)),难以通过。

唯一能优化的地方在于 n,考虑是否能去掉这个 n,我们统计每个格子的状态时,将 0/1/2/3 分开统计,四个状态各用一个 n 位二进制数表示每个格子是否存在该状态。

这样一来时间复杂度为 O(2n2(n2+m)),足以通过。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define pir pair <ll, ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn = 1010, mod = 1e9 + 7;
ll n, m, state[maxn][35], d[maxn], maxd, hh[1 << 16];
char str[maxn];
ll fas(ll x) {
	if((x & 3) == 3) return 1;
	if((x & 12) == 12) return 1;
	if((x & -x) == x) return 3;
	return 2;
}
ll _0[1 << 16], _1[1 << 16], _2[1 << 16], _3[1 << 16];
ll p0[1 << 16], p1[1 << 16], p2[1 << 16], p3[1 << 16];
void calc(ll q) {
	for(ll i = 0; i < 16; i++) {
		_0[1 << i] = p0[q] << i;
		_1[1 << i] = p1[q] << i;
		_2[1 << i] = (p2[q] << i) | ((1 << i) - 1);
		_3[1 << i] = p3[q] << i;
	}
	for(ll S = 1; S < (1 << 16); S++) {
		_0[S] = _0[S & (S - 1)] | _0[S & -S];
		_1[S] = _1[S & (S - 1)] | _1[S & -S];
		_2[S] = _2[S & (S - 1)] | _2[S & -S];
		_3[S] = _3[S & (S - 1)] | _3[S & -S];
	}
}
ll g(ll S, ll i) {
	return (((_3[S] >> i) & 1) << 3) | (((_2[S] >> i) & 1) << 2)
	| (((_1[S] >> i) & 1) << 1) | ((_0[S] >> i) & 1);
}
ll pw3[maxn], pw2[maxn];
namespace sub1 {
	ll prod[1 << 16], pf[1 << 16], id[maxn];
	ll solve() {
		ll ret = 0;
		for(ll S = 1; S < (1 << 16); S++) prod[S] = pf[S] = 1;
		for(ll i = 1; i <= m; i++) {
			calc(i); ll w = min(16ll, n - d[i]);
			for(ll S = 1; S < (1 << w); S++) {
				ll mask = (_0[S] & _1[S]) | (_2[S] & _3[S]);
				ll cc = (((1ll << n) - 1) ^ (_0[S] | _1[S])) |
				        (((1ll << n) - 1) ^ (_2[S] | _3[S]));
				cc &= ((1ll << n) - 1) ^ mask;
				ll x = __builtin_popcountll(cc), z = __builtin_popcountll(mask);
				ll tmp = pw3[x] * pw2[n - x - z] %mod;
				prod[S] = prod[S] * tmp %mod;
				if(d[i] < 16) pf[S] = pf[S] * tmp %mod;
			}
		}
		for(ll S = 1; S < (1 << 16); S++) {
			ll c = mod - 1;
			for(ll i = 0; i < 16; i++)
				if(S & (1 << i)) c = mod - c;
			ret = (ret + c * (prod[S] + mod - pf[S])) %mod;
		} return ret;
	}
}
namespace sub2 {
	ll dp[2][1 << 16][2], prod[1 << 16], prod_[1 << 16], rev[1 << 16];
	ll id[maxn];
	bool cmp(ll x, ll y) {return d[x] < d[y];}
	ll solve() {
		for(ll i = 1; i <= m; i++) id[i] = i;
		sort(id + 1, id + 1 + m, cmp);
		ll maxd = 0;
		for(ll i = 1; i <= m; i++)	
			if(d[i] < 16) maxd = max(maxd, d[i] + 1);
		if(maxd == 0) return 1;
		for(ll S = 0; S < (1 << 16); S++) {
			prod[S] = prod_[S] = 1;
			rev[S] = (rev[S >> 1] >> 1) | (S & 1? 1 << maxd - 1 : 0);
		} ll ret = 0;
		for(ll t = n - 1, r = 0; ~t; t--) {
			while(r < m && d[id[r + 1]] + t < n) {
				++r; if(d[id[r]] >= 16) continue;
				calc(id[r]);
				for(ll S = 0; S < (1 << maxd); S++) {
					ll x = hh[S & -S];
					prod[rev[S]] = prod[rev[S]] * fas(g(S, maxd - 1)) %mod;
					prod_[rev[S]] = prod_[rev[S]] * fas(g(S, maxd - 1) | 4) %mod;
				}
			}
			memset(dp[1], 0, sizeof dp[1]);
			dp[1][0][0] = mod - 1;
			for(ll i = 0; i < n; i++) {
				memset(dp[i & 1], 0, sizeof dp[i & 1]);
				for(ll S = 0; S < (1 << maxd - 1); S++) {
					for(ll z = 0; z < 2; z++) {
						ll T = S ^ (S & (maxd > 1? 1 << maxd - 2 : 0)), e = (S > T) | z;
						if(i ^ t)
							dp[i & 1][T << 1][e] = (dp[i & 1][T << 1][e] + dp[i & 1 ^ 1][S][z] * 
						                        (i < t || z? prod_[S << 1] : prod[S << 1])) %mod;
						if(i > t) continue;
						ll of = maxd > 1? T << 1|1 : 0;
						if(maxd == 1) e = 1;
						dp[i & 1][of][e] = (dp[i & 1][of][e] + mod - dp[i & 1 ^ 1][S][z] * 
						                      (i < t || z? prod_[S << 1|1] : prod[S << 1|1]) %mod) %mod;
					}
				}
//				for(ll S = 0; S < (1 << maxd - 1); S++)
//					printf("dp[%lld, %lld] = %lld, %lld\n", i, S, dp[i & 1][S][0], dp[i & 1][S][1]);
			}
			for(ll S = 0; S < (1 << maxd - 1); S++)
				ret = (ret + dp[n & 1 ^ 1][S][0] + dp[n & 1 ^ 1][S][1]) %mod; 
		}
		return ret;
	}
}
int main() {
	scanf("%lld%lld", &n, &m);
	pw2[0] = pw3[0] = 1;
	for(ll i = 1; i <= n; i++) {
		pw2[i] = pw2[i - 1] * 2 %mod;
		pw3[i] = pw3[i - 1] * 3 %mod;
	}
	for(ll i = 0; i < 16; i++) hh[1 << i] = i;
	for(ll i = 1; i <= m; i++) {
		scanf("%s", str + 1); ll len = strlen(str + 1);
		for(ll j = 0; j < n; j++) state[i][j] = 2;
		for(ll j = 1, x = 0; j <= len; j++) {
			if(str[j] == 'R') ++x;
			else if(str[j] == '*') state[i][x] ^= 1;
			else state[i][x] = str[j] - '0';
			d[i] = x;
		} maxd = max(maxd, d[i]);
		for(ll j = d[i] + 1; j < n; j++) state[i][j] = 2;
		for(ll j = 0; j < n; j++)
			if(state[i][j] == 0) p0[i] |= 1ll << j;
			else if(state[i][j] == 1) p1[i] |= 1ll << j;
			else if(state[i][j] == 2) p2[i] |= 1ll << j;
			else p3[i] |= 1ll << j;
			
	} ll ans = 0;
	ans += sub1::solve();
	ans += sub2::solve();
	printf("%lld", ans % mod);
	return 0;
}

出处:https://www.cnblogs.com/Sktn0089/p/18337659

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Lgx_Q  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示