专栏文章

ARC147F

AT_arc147_f题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@miqo4l0g
此快照首次捕获于
2025/12/04 07:58
3 个月前
此快照最后确认于
2025/12/04 07:58
3 个月前
查看原文

Description

求长度为 nn 且字符集为 {A,B,C}\{\tt A,\tt B,\tt C\},满足如下条件的的字符串 SS 数量:
  • Ci,A,Ci,B,Ci,CC_{i,\tt A},C_{i,\tt B},C_{i,\tt C} 分别为字符串 S[1,i]S_{[1,i]}A,B,C\tt A,\tt B,\tt C 的出现次数,则对于任意 1in1\le i\le n 有:
    • Ci,ACi,BXC_{i,\tt A}-C_{i,\tt B}\le X
    • Ci,BCi,CYC_{i,\tt B}-C_{i,\tt C}\le Y
    • Ci,CCi,AZC_{i,\tt C}-C_{i,\tt A}\le Z
答案对 2\mathbf{2} 取模。
1n,X,Y,Z1091\le n,X,Y,Z\le 10^9

Solution

Part 1

这一部分的转换对于四次方做法并不是必要的,但对于后面是有极强的提示性的。
考虑 ii+1i\to i+1 的增量,也就是我最初有三个分别等于 X,Y,ZX,Y,Z 的变量,每次操作会有 一个加一,一个减一,且这三个变量 始终非负
更具象的刻画是:
  • 存在一个长度为 X+Y+Z+3X+Y+Z+3 的环,在坐标 0,X+1,X+Z+20,X+1,X+Z+2 处各有一个棋子,每次可以选取一个棋子往钦定的正方向移动一步,满足恰好移动 nn 次且 任意时刻棋子不重合 的方案数。
记录当前三个棋子的坐标进行 dp 即可做到 O(nm3)\mathcal{O}(nm^3),但这也太不牛了。

Part 2

发现我们并没有用到答案模 22 这个性质。
一个非常难受的部分是限制 移动过程中棋子不能重合,这导致我们要记录棋子坐标。
但是如果真的存在某个时刻 ii 两个棋子重合了,不妨设接下来两个棋子的移动路径分别为 A,BA,Bswap(A,B)\operatorname{swap}(A,B) 后仍然是一种合法的方案!且除非 A=B=A=B=\empty,否则这是一种新的方案。也就是除去 A=B=A=B=\empty 外,剩余方案总数恰为偶数,不对答案产生贡献
再来考虑 A=B=A=B=\empty,则在最终时刻这两个棋子仍是重合的,所以若考虑模 22 意义下的答案仅仅只需要拿总方案数减去 最终 某两颗棋子重合的方案数!
当然容易注意到我三个棋子全部重合的情况杯多记了两次,所以要减掉,但事实既然是 两倍 也就不对答案产生贡献了。
且总方案数为 3n3^n,这是个奇数,那答案也就是三种情况(一二棋子重合,一三棋子重合,二三棋子重合)的奇偶性异或和再异或上 11

Part 3

下面以计算一二两棋子重合的方案数为例。
经典的,判断重合我们也就不关心其具体坐标,仅仅只关心 坐标差值
每次挪动棋子,坐标差值 要么加一,要么减一,要么不变,这在草稿纸上画一下是显然的。
则将其刻画为生成函数的形式:
[xX+1]((x1+1+x)nmodxm1)[x^{X+1}]\left((x^{-1}+1+x)^n\bmod{x^m-1}\right)
意义显然。其中 mod xm1\bmod\ {x^m-1} 刻画为长度为 mm 的多项式做循环卷积。mm 即为环长也就是 X+Y+Z+3X+Y+Z+3
比较经典的,我们有:(用到这个 trick 的还有 ARC184E
(x1+1+x)2kx2k+1+x2k(mod2)(x^{-1}+1+x)^{2^k}\equiv x^{-2^k}+1+x^{2^k}\pmod{2}
还是写一下证明:
(x1+1+x)2ka+b+c=2k(2ka,b,c)xaxc0a+b2k(2ka)(2kab)xax2kab0a+b2k[a2k][b2ka]xax2kab\begin{aligned} (x^{-1}+1+x)^{2^k} & \equiv\sum\limits_{a+b+c=2^k}\binom{2^k}{a,b,c}x^{-a}x^{c}\\ & \equiv\sum\limits_{0\le a+b\le 2^k} \binom{2^k}{a}\binom{2^k-a}{b}x^{-a}x^{2^k-a-b}\\ & \equiv\sum\limits_{0\le a+b\le 2^k} \left[a\subseteq 2^k\right]\left[b \in 2^k-a\right]x^{-a}x^{2^k-a-b}\\ \end{aligned}
其中最后一步是 Lucas 定理推论。a2ka\subseteq 2^k 仅在 a=0a=0a=2ka=2^k 时成立,这样子 (a,b)(a,b) 有值的取值就只有 O(1)\mathcal{O}(1) 种了,进一步分讨即可得到,这里不再赘述。
则将 nn 写作 n=i=1k2pin=\sum\limits_{i=1}^k 2^{p_i} 也就得到:
answer=[xX+1](i=1k(x2pi+1+x2pi)modxm1)\text{answer}=[x^{X+1}]\left(\prod_{i=1}^k \left(x^{-2^{p_i}}+1+x^{2^{p_i}}\right)\bmod{x^m-1}\right)
我们有 klognk\le \log{n},且做单次循环卷积暴力就是 O(m)\mathcal{O}(m) 的,我们也就获得了一个 O(mlogn)\mathcal{O}(m\log{n}) 的做法。

Part 4

Part 3 得到的做法当 mm 较小时已经可以接受,但是 mm 较大时仍然无法通过。
考虑略去 mod xm1\bmod\ {x^m-1} 循环卷积的限制,我们则得到:
answer=rX+1(modm)[xr](i=1k(x2pi+1+x2pi))\text{answer}=\sum\limits_{r\equiv X+1\pmod{m}}[x^r]\left(\prod_{i=1}^k \left(x^{-2^{p_i}}+1+x^{2^{p_i}}\right)\right)
rr 写作 r=qm+(X+1)r=qm+(X+1) 的形式,注意到 rnnmqnmr\le n\Rightarrow-\left\lfloor\cfrac{n}{m}\right\rfloor\le q\le \left\lfloor\cfrac{n}{m}\right\rfloor这样子 rr 只有 O(nm)\mathcal{O}\left(\cfrac{n}{m}\right) 种取值,若我们能快速求解单个 xrx^r 次项的系数,那复杂度也是可以接受的。
为了消去负指数的影响,我们将多项式乘上 xnx^n
answer=r(X+1)+n(modm)[xr](i=1k(1+x2pi+x2pi+1))\text{answer}=\sum\limits_{r\equiv (X+1)+n\pmod{m}}[x^r]\left(\prod_{i=1}^k \left(1+x^{2^{p_i}}+x^{2^{p_i+1}}\right)\right)
若我们将连乘式暴力展开,实际上就是有 kk 个三元组 (0,2pi,2pi+1)\left(0,2^{p_i},2^{p_i+1}\right),每个三元组中恰选一个数,求和为 rr 的方案数。
进一步也将 rr 排成二进制,就变成了每个三元组 可以选择不变,将第 pip_i 位加一,或将第 pi+1p_i+1 位加一
记录 dp 状态为 fi,0/1f_{i,0/1} 表示考虑 ilogni\sim \log{n} 位,其中第 i+1logni+1\sim \log{n} 位已经完全符合要求,和 是否需要在第 i1i-1 位进位补充 的方案数。
转移可能看代码会很清晰?
答案即为 f0,0f_{0,0}
这样子我们做到了 O(logn)\mathcal{O}(\log{n}) 复杂度算一个 xrx^r 项系数,复杂度即为 O(nmlogn)\mathcal{O}\left(\cfrac{n}{m}\log{n}\right)

Part 5

经典的,我们阈值分治,钦定 mBm\le B 使用 Part 3,m>Bm>B 使用 Part 4。视 n,mn,m 同级,取 B=nB=\sqrt{n} 即可获得 O(nlogn)\mathcal{O}(\sqrt{n}\log{n}) 复杂度的做法。

Code

CPP
#include<bits/stdc++.h>
#define int long long
// #pragma GCC optimize(3,"Ofast","inline")
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ll long long
#define bint __int128
#define ull unsigned long long
#define uint unsigned int
#define ld double
#define PII pair<int,int>
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define rep(k,l,r) for(int k=l;k<=r;++k)
#define per(k,r,l) for(int k=r;k>=l;--k)
#define cl(f,x) memset(f,x,sizeof(f))
#define pcnt(x) __builtin_popcount(x)
#define lg(x) (31-__builtin_clz(x))
using namespace std;
void file_IO() {
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
}
bool M1;
const int INF=0x3f3f3f3f;
const ll INFLL=0x3f3f3f3f3f3f3f3f;
const ld eps=1e-9;
const int B=1e5,N=B+5,M=35;
int f[N],tmp[N],g[M][2];
int calc1(int x,int m,int n) {
    rep(i,0,m-1)
        f[i]=0;
    f[0]=1;
    int k=lg(n);
    rep(i,0,k) {
        if((n>>i)&1) {
            rep(j,0,m-1) {
                tmp[j]=f[j];
                f[j]=0;
            }
            rep(j,0,m-1) {
                f[((j-(1ll<<i))%m+m)%m]^=tmp[j];
                f[j]^=tmp[j];
                f[(j+(1ll<<i))%m]^=tmp[j];
            }
        }
    }
    return f[x%m];
}
int calc2(int x,int m,int n) {
    int k=lg(x);
    rep(i,0,k+1)
        g[i][0]=g[i][1]=0;
    g[k+1][0]=1;
    per(i,k,0) {
        if((n>>i)&1) {
            if((x>>i)&1) {
                g[i][0]^=g[i+1][0]; // choose 1
                g[i][1]^=g[i+1][0]; // choose 0
                g[i][1]^=g[i+1][1]; // choose 2
            } else {
                g[i][0]^=g[i+1][0]; // choose 0
                g[i][0]^=g[i+1][1]; // choose 2
                g[i][1]^=g[i+1][1]; // choose 1
            }
        } else
            g[i][(x>>i)&1]=g[i+1][0];
    }
    return g[0][0];
}
int calc(int x,int m,int n) {
    if(m<=B)
        return calc1(x,m,n);
    int t=(x+n)%m,res=0;
    rep(i,0,((n<<1)-t)/m)
        res^=calc2(t+i*m,m,n);
    return res;
}
void solve() {
    int n,x,y,z;
    scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
    int t=x+y+z+3;
    printf("%lld\n",1^calc(x+1,t,n)^calc(y+1,t,n)^calc(z+1,t,n));
}
bool M2;
// g++ ARC147F.cpp -std=c++14 -Wall -O2 -o ARC147F
signed main() {
    // file_IO();
    int testcase=1;
    scanf("%lld",&testcase);
    while(testcase--)
        solve();
    debug("used time = %dms\n",(signed)(1000*clock()/CLOCKS_PER_SEC));
    debug("used memory = %dMB\n",(signed)((&M1-&M2)/1024/1024));
    return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...