[ARC147F] Again ABC String 题解
[ARC147F] Again ABC String
题目大意
现在有 A,B,C 三种字母构成了 \(S\) 串。
对于 \(S\) 的前 \(i\) 个字符组成的字符串 \(S_i\),记 \(S_i\) 中 A、B、C 的个数分别为 \(A_i, B_i, C_i\)。对于任意满足 \(1 \le i \le N\) 的整数 \(i\),都满足以下条件:
- \(A_i - B_i \le X\)
- \(B_i - C_i \le Y\)
- \(C_i - A_i \le Z\)
现在询问有多少个满足上述条件的 \(S\) 串,对 ${\color{Red}2 } $ 取模。
条件翻译
现在设 \(\alpha_1=X-(A_i-B_i)\),\(\alpha_2=Y-(B_i-C_i)\),\(\alpha_3=Z-(C_i-A_i)\)。
那么考虑 \(i\rightarrow i+1\) 的过程中,一定会有一个 \(\alpha_i(i\in\{1,2,3\})\) 会 \(+1\),有一个会 \(-1\)。
将题目限制等价为现在有三个变量,初始值 \((a,b,c)=(X,Y,Z)\),现在对其做 \(N\) 次操作,每一次可以把一个变量的值 \(+1\) ,一个变量的值 \(-1\),且在任意时刻,三个变量值非负。
再进一步,现在给你一个 \(X+Y+Z+3\) 的环,初始有三个点分别在 \(0\) ,\(X+1\),\(X+Y+2\) 这三个点,现在可以对一个点向右移动 \(1\) 步。
但是不能有时刻点重叠。
条件再转化
考虑在 \(t\) 时刻两个点重叠,那么考虑接下来两个点的运动轨迹 \(path_1\) 和 \(path_2\) ,发现假设交换两个轨迹,是一种新的方案(这里不考虑 \(t=N\) 的情况)。
由于题目特性,对 \(2\) 取模,也就是说,两个点在途中重叠本身对于答案的贡献为 \(0\)。
也就是说,我们对于上述点重叠限制,只需要考虑 \(t=N\) 的情况即可。也就是最后停下时三个点中有两个点重叠(一号和二号,二号和三号,三号和一号)。
有聪明的人会问了,这里重复计算了三个点同时重叠的情况,但是由于这一部分被多算了 \(2\) 次,对于答案也是没有贡献的。
Solution
所有的情况为 \(3^N\) ,只需要减去两个点在最后重叠的情况即可。
然后现在我们关注其中的两个点,由于只关注两者是否重叠,也就是具体位置我们不关心,将其转化为差值。
Solution 1
考虑使用生成函数来刻画差值。
(下面以 \(1\) 号点和 \(2\) 号点重叠为例)
也就是我们要求 \([x^{X+1}]((x^{-1}+1+x)^N)\bmod 2\),当然,这里的卷积是循环卷积,对于 \(x^{X+Y+Z+3}\) 取模 。
引理
证明如下:
假设上述条件对于 \(1\sim k-1\) 成立,对于 \(k\) ,有:
然后成立了。
现在我们可以把 \(N\) 按照二进制分一下,就可以直接卷积了,时间复杂度是 \(O(M\log N)\) 的。
Solution 2
仍然使用生成函数,考虑先舍弃循环卷积这个东西。
也就是:
考虑到 \(r=qm+X+1 \leq n\),所以 \(q\) 的数量是 \(O(\frac{n}{m})\) 的。我们只要快速求出每一种 \(x^r\) 的系数,就能解决问题。
为了消除负指数的影响,我们乘一个 \(x^n\) ,得到:
然后组合意义,上式等价于每一次你都可以选择对于二进制上的 \(p_i\) 位或者 \(p_i+1\) 位 \(+1\) ,或者不变,问最后有多少种操作能凑出 \(r\) 。
考虑 dp 。设 \(dp_{i,0/1}\) 表示从低到高考虑到了 \(i\) 位,目前 \(i\) 向 \(i+1\) 进了 \(0/1\) 位的奇偶性。
考虑转移:
- 若 \(N\) 的第 \(i\) 位为 \(0\) ,那么在这个位上只能选 \(1\),此时 \(j+0=r_i+2\times nxt\)。
- 反之,这个位上可以选 \(1,2^i,2^{i+1}\) ,等价为有 \(c_i\in\{1,2,3\}\) 满足 \(j+c_i=r_i+2\times nxt\) 。
其中 \(r_i\) 表示 \(r\) 的第 \(i\) 位二进制位是多少。
Solution 3
然后现在就是非常简单的根号分治了,对于 \(m<B=\sqrt{n}\) ,使用我们在solution 1 里面的算法,对于 \(m>B=\sqrt{n}\) 使用 solution 2 里面的算法。
时间复杂度为 \(O(\sqrt{n}\log n)\),可以通过。
Code
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using ll = long long;
using pii = pair<int, int>;
int solve_dp(ll R, ll N) {
if (R < 0 || R > 2 * N) return 0;
int dp[2] = { 1, 0 };
for (int i = 0; i <= 61; ++i) {
int next_dp[2] = { 0, 0 };
int Ri = (R >> i) & 1;
int Ni = (N >> i) & 1;
for (int c = 0; c < 2; ++c) { // 遍历当前位进位的两种可能状态(0和1)
if (dp[c] == 0) continue;
if (Ni == 1) // 如果N的第i位为1,则有两种情况可以选择
for (int ci = 0; ci <= 2; ++ci) { // 遍历当前位的可能值(0, 1, 2)
int total = c + ci; // 计算总和
if ((total & 1) == Ri) // 如果总和的最低位与R的第i位匹配,则更新下一状态
next_dp[total >> 1] ^= 1;
}
else // 如果N的第i位为0,则只有一种选择
if ((c & 1) == Ri) // 如果当前位的最低位与R的第i位匹配,则更新下一状态
next_dp[c >> 1] ^= 1;
}
dp[0] = next_dp[0];
dp[1] = next_dp[1];
if (dp[0] == 0 && dp[1] == 0) return 0;
}
return dp[0];
}
int calc1(ll n, ll d, ll m) {
vector<int> poly(m, 0);
poly[0] = 1;
for (int i = 0; (1LL << i) <= n; ++i) {
if ((n >> i) & 1) {
vector<int> nxt(m, 0);
ll pos = (1LL << i) % m;
for (int j = 0; j < m; ++j) {
if (poly[j]) {
nxt[(j + pos) % m] ^= 1; // x^pos
nxt[j] ^= 1; // 1
nxt[(j - pos + m) % m] ^= 1; // x^(-pos)
}
}
poly = nxt;
}
}
return poly[(d % m + m) % m];
}
int calc2(ll n, ll d, ll m) {
ll R0 = (d + n) % m;
if (R0 < 0) R0 += m;
int res = 0;
for (ll R = R0; R <= 2 * n; R += m)
res ^= solve_dp(R, n);
return res;
}
ll calc(ll n, ll d, ll m) {
if (m * m <= n) // 根号分治
return calc1(n, d, m);
else
return calc2(n, d, m);
}
int main() {
FASTIO;
int t;
ll n, x, y, z;
cin >> t;
while (t--) {
cin >> n >> x >> y >> z;
ll m = x + y + z + 3;
int ans = 1; // 初始化答案为1,因为 3^n 一定是奇数
ans ^= calc(n, x + 1, m);
ans ^= calc(n, y + 1, m);
ans ^= calc(n, z + 1, m);
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号