Luogu7740 [NOI2021]机器人游戏 做题记录
link 一道炸裂的题目。
首先样例解释已经告诉我们可以容斥。考虑枚举可行的位置集合 \(S\),我们需要统计 \(\forall p\in S\),纸条初始状态和目标状态都相同的方案数。
显然每个机器人独立,可以分开考虑。对于一个机器人,他的行动对纸条的每个格子要么赋值为 \(0/1\),要么不变,要么取反,我们可以用 \(0/1/2/3\) 表示四种状态。
对于一个机器人和一个格子 \(i\),我们统计出格子 \(i\) 能被修改成哪几种状态,即统计对于 \(0/1/2/3\) 四种状态,是否存在 \(p\in S\),满足机器人从格子 \(p\) 出发能令格子 \(i\) 变为该状态。
若一个格子同时存在状态 \(0,1\) 或者同时存在状态 \(2,3\),那么这只能是空格子,有 \(1\) 种方案;若一个格子同时存在 \(0/1\) 和 \(2/3\),那么可以是 \(0/1\) 其中一个数字,或者空格子,有 \(2\) 中方案;若一个格子只存在一种状态,那么 \(3\) 种方案都可以。
当 \(n > 16\) 时会超时。发现 \(n_{max}\le 32\),可以考虑折半算法。又发现机器人走出纸条时会爆炸,容易想到对于步数 \(\ge 16\) 的机器人,如果其纸条非空,那么 \(S\) 只能枚举前 \(16\) 个格子,这就可以直接暴力做了。当然,需要减掉所有步数 \(\ge 16\) 的机器人的纸条全是空的的方案数。
舍弃掉步数 \(\ge 16\) 的机器人,剩下的机器人最多走 \(15\) 步。从前往后考虑每个格子,其可以加入 \(S\) 或否,而该格子贡献的方案数只和前 \(15\) 个格子的状态、这 \(15\) 个格子之前是否有格子加入了 \(S\)、之后是否会有格子加入 \(S\) 三个信息有关。其中后两个信息决定了是否有除了这 \(15\) 个格子的其他格子对其的 \(2\) 状态的影响。
考虑枚举最后一个处于 \(S\) 中的格子,设 \(f[i,S,0/1]\) 表示考虑了前 \(i\) 个格子,其中前 \(15\) 个格子的状态为 \(S\),在这 \(15\) 个格子之前是否有格子加入了集合,此时前 \(i\) 个格子的方案数。
综上时间复杂度为 \(O(2^{\frac n2}n(n+m))\),难以通过。
唯一能优化的地方在于 \(n\),考虑是否能去掉这个 \(n\),我们统计每个格子的状态时,将 \(0/1/2/3\) 分开统计,四个状态各用一个 \(n\) 位二进制数表示每个格子是否存在该状态。
这样一来时间复杂度为 \(O(2^{\frac n2} (n^2 + 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;
}