Day 7

来验这套题的起因是考 chino 造的 Day4 的时候觉得坐牢了就往后看题,然后看到嘉然的两个数学题。觉得 T1 明显很不可做,T2 看起来很舒适就开 T2。为啥不开 T3?数据结构。

然后现在这俩数学题(大体上是)都基本整明白了,来写个题解。

原来的密码是 nsdddsyssymhyzzyfdykqxkfsjmxyx。可以手动拼读一下是什么意思。

T1

原题是 ABC288EX。然而数据史诗级加强。

首先令 \(M=M+1\),答案是

\[[z^{X}]\prod_{i=0}^{M-1}\frac 1{1-xz^i} \]

\(1-N\) 次项系数。\(x\) 乘法是加法卷积,\(z\) 乘法是异或卷积。

异或卷积考虑 FWT。先复习一下 FWT 干的是什么:\(z^i\) 项变成所有 \(\text{popcount}(i\wedge j)\equiv 0\pmod 2\)\(z^j\) 项减掉 \(\text{popcount}(i\wedge j)\equiv 1\pmod 2\)\(z^j\) 项。IFWT 就是所有项再乘个 \(2^{-n}\)。以下设上边那个运算为 \(i\otimes j\)。(\(n\)\(M\) 的位长)

按照黎明前的巧克力那题的做法,手动展开 FWT:

\[\begin{aligned} &[z^{X}]\frac 1{1-xz^i}\\ =&[z^{X}]\sum_{j=0}x^{2j}+\sum_{j=0}z^ix^{2j+1} \end{aligned} \]

然后分类讨论:

  1. \(X\otimes i=0\):原式为

\[\sum_{j=0}x^j=\frac 1{1-x} \]

  1. \(X\otimes i=1\):原式为

\[\sum_{j=0}(-1)^jx^j=\frac 1{1+x} \]

然后 IFWT 回去:

\[2^{-n}\sum_{i=0}^{2^n-1}(1-2(X\otimes i))\left(\frac 1{1+x}\right)^{cnt_i}\left(\frac 1{1-x}\right)^{M-cnt_i} \]

其中 \(cnt_i=\sum_{j=0}^{M-1}i\otimes j\)
然后变成这么两个问题:

  1. \(\left(\dfrac 1{1+x}\right)^a\left(\dfrac 1{1-x}\right)^b\):ODE 手动机一下,求导表示自己。设这个东西是 \(F\),那么

\[F'=\frac b{1-x}F-\frac a{1+x}F \]

\[(i+1)f_{i+1}=\sum_{j=0}^ibf_{i-j}-(-1)^jaf_{i-j} \]

  1. 对于每个 \(i\)\(cnt_i\) 和个数。诚实的说这个我不会,是贺的。

先考虑怎么算一个:若 \(i\) 的二进制最低位为 \(low\),枚举 \(j\) 最高的和 \(M\) 不同的位 \(d\),若 \(d>low\),则除了 \(low\) 别的可以随便选,用 \(low\) 一位调整使得 \(i\otimes j=1\),贡献是 \(2^{d-1}\)。如果 \(d\le low\),则由于 \(d\) 及以下 \(i\) 全是 \(0\),即已经确定了 \(i\otimes j\),可以根据 \(i\otimes M\) 算出。这也说明了 \(cnt_i\) 只有 \(O(n)\) 组。

然后我们就只需要算是 \(cnt_i\)\(i\) 个数。这个可以数位 dp 一下:仍然外层枚举 \(low\)。设 \(dp_{x,0/1,0/1}\)\(i\) 确定了 \(x\) 位及以上,第二维是 \(i\otimes M\),第三维是 \(i\otimes X\) 的方案。枚举一下三维和这一维的 \(i\)\(0/1\) 就好了。

总复杂度 \(O(N\log M)\)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <map>
#define int long long
using namespace std;
const int mod=998244353;
int n,m,x,ans[100010],f[100010],inv[100010],dp[65][2][2],sum[2];
map<int,int>mp;
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*ans*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
void solve(pair<int,int>p){
    int cnt=p.second%mod,a=p.first%mod,b=(m-p.first)%mod;
    f[0]=cnt;sum[0]=f[0];sum[1]=0;
    for(int i=0;i<n;i++){
        f[i+1]=((sum[0]+sum[1])%mod*b%mod-(sum[i&1]-sum[(i&1)^1]+mod)%mod*a%mod+mod)%mod*inv[i+1]%mod;
        sum[(i&1)^1]=(sum[(i&1)^1]+f[i+1])%mod;
    }
    for(int i=0;i<=n;i++)ans[i]=(ans[i]+f[i])%mod;
}
signed main(){
    scanf("%lld%lld%lld",&n,&m,&x);inv[1]=1;m++;
    for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    solve(make_pair(0,1));
    for(int i=0;i<=60;i++){
        memset(dp,0,sizeof(dp));
        dp[61][0][0]=1;
        for(int j=60;j>=i;j--){
            for(int b=0;b<2;b++){
                for(int c=0;c<2;c++){
                    for(int w=(i==j);w<2;w++){
                        dp[j][b^(m>>j&w)][c^(x>>j&w)]=(dp[j][b^(m>>j&w)][c^(x>>j&w)]+dp[j+1][b][c])%mod;
                    }
                }
            }
        }
        mp.clear();
        for(int b=0;b<2;b++){
            for(int c=0;c<2;c++){
                int cnt=0;
                for(int j=60;j>i;j--)if(m>>j&1)cnt+=1ll<<(j-1);
                for(int j=i;j>=0;j--)if((m>>j&1))if(b^(i==j))cnt+=1ll<<j;
                if(c)mp[cnt]=(mp[cnt]-dp[i][b][c]+mod)%mod;
                else mp[cnt]=(mp[cnt]+dp[i][b][c])%mod;
            }
        }
        for(pair<int,int>p:mp)solve(p);
    }
    for(int i=1;i<=n;i++){
        ans[i]=1ll*ans[i]*qpow((1ll<<61)%mod,mod-2)%mod;
        printf("%lld\n",ans[i]);
    }
    return 0;
}

T2

\(O(n^3)\) 部分分:设 \(dp_{l,r,dep}\) 为当前笛卡尔树的区间是 \([l,r]\),深度为 \(dep\) 的方案数,转移随便写写(真的不想再说)。直接说正解是什么东西。

事实上 dp 为我们提供了一个方向:若 \(x\) 位置的数为 \(i\),则 \(x\) 节点一定满足两个条件:

  1. 深度 \(\le i\)
  2. 子树大小 \(\le n-i+1\)

容易发现这两个条件没有交集。于是用总数 \(Cat_n\) 减掉这两个就行。问题变成算 \(x\) 节点深度/子树大小为 \(i\) 的方案,然后后缀和一下。

先考虑算第二个:枚举左右子树 \(i,j\) 大小,总方案数是 \(\sum_{i=0}^{x-1}\sum_{j=0}^{n-x}Cat_iCat_j\)。外边还剩下 \(n-i-j-1\) 个点,那么确定了外边的二叉树之后一定有唯一的位置可以把 \(x\) 子树挂上去,因此方案还要乘个 \(Cat_{n-i-j-1}\)

然后是第一个。仍然枚举 \(x\) 的祖先中有 \(i\) 个左边的,\(j\) 个右边的。那么首先上边顺序随意所以乘个 \(\dbinom{i+j}i\)。然后考虑整棵树的形态。先只看左边,右边是一样的。我们知道 \(n\) 个节点的二叉树和长 \(2n\) 的括号序列一一对应:往儿子走是左括号,走回来是右括号。那么如果 \(x\) 上边有 \(i\) 个点那么就代表着总长度为 \(2n-i\) 的括号序列,左括号比右括号多 \(i\) 个的方案数,这个方案格路计数一下就行了,是 \(\dbinom{2n-i}n-\dbinom{2n-i}{n+1}\)

显然两个都可以跑 NTT。

T3

写的稀烂,我自己都看不懂。扔 latex 注释里了。

\[%奇异。以下是我对着代码大眼观察得到的逻辑,如果有不对的那是我的问题。如果您盯着代码看会了或者说场切了那欢迎 d 爆我。 %首先贪心是对的。然后大体思想是默认全都往最右边堆料,如果堆爆了就往左挪挪到右边不爆,然后用总的减掉左边爆了的就是答案。 %扫描线,开两个线段树分别记录后缀和前缀。每次加入区间的时候默认全都放到右端点上,然后每次找到后缀线段树中堆的最多的,在所有堆在这里的区间中找到左端点最靠左的,把它堆的部分尽量往左挪。具体实现的时候是把 $a_i$ 看做后缀减,堆的过程为后缀加,然后后/前缀线段树中大于 $0$ 的位置就是堆爆了。设这个位置为 $pos_1$,当前查询的是 $i$,那么右端点落在 $[i,pos_1]$ 的所有点都可以往左挪。贪心找到左端点最靠左的并计算能往左挪多少,同样的做后缀减和前缀加,并在这个区间上记录它被挪了多少。处理完 $i$ 之后把这个 $a_i$ 从后缀线段树里去掉,加入前缀线段树中。 %(真的语无伦次) \]

posted @ 2023-04-26 18:08  gtm1514  阅读(18)  评论(2编辑  收藏  举报