【学习笔记】[AHOI2022] 山河重整
运算真是太强大辣!!!
这题的切入点非常有意思。记 a k + 1 a_{k+1} ak+1表示和为 k k k,且能表达出 1 ∼ k 1\sim k 1∼k的集合的数量,那么我们得出一个非常巧妙的方程
x ( 1 + x ) ( 1 + x 2 ) . . . . ( 1 + x n ) = ∑ k a k x k ( 1 + x k + 1 ) . . . ( 1 + x n ) x(1+x)(1+x^2)....(1+x^n)=\sum_k a_{k}x^k(1+x^{k+1})...(1+x^n) x(1+x)(1+x2)....(1+xn)=∑kakxk(1+xk+1)...(1+xn)
注意左右两边的 x x x不能抄掉了。这样我们就将组合关系抽象成了一个代数式。注意,我们只需要求出 { a i } \{a_i\} {ai}中所有 ≤ n \le n ≤n的项的值然后用总方案数减去即可。
注意,在生成函数的运算中形式非常重要。借助杨表的组合意义,我们知道 ( 1 + x k ) . . . ( 1 + x n ) = ∑ i ≤ n x i ( i − 1 ) 2 + i k ∏ j ≤ i 1 1 − x j (1+x^k)...(1+x^n)=\sum_{i\le n}x^{\frac{i(i-1)}{2}+ik}\prod_{j\le i}\frac{1}{1-x^j} (1+xk)...(1+xn)=∑i≤nx2i(i−1)+ik∏j≤i1−xj1,因此可以将右边的式子写成 ∑ k a k x k ∑ i ≤ n x i ( i − 1 ) 2 + i k ∏ j ≤ i 1 1 − x j \sum_ka_kx^k\sum_{i\le n}x^{\frac{i(i-1)}{2}+ik}\prod_{j\le i}\frac{1}{1-x^j} ∑kakxk∑i≤nx2i(i−1)+ik∏j≤i1−xj1。右边的封闭形式不是我们关注的重点,因为再不济也可以直接暴力卷积求出。我们重点关注 x x x的次数。
{ a i } \{a_i\} {ai}从何解出?注意,我们要求的不是 { a i } \{a_i\} {ai}的某一项,而是 A ( x ) = ∑ x k a k A(x)=\sum x^ka_k A(x)=∑xkak这个多项式。 x x x的次数是自由的,那么我们想到将 x x x的次数合并到一起,并且看到 x i k x^{ik} xik这一项,很容易让我们想到是将 x i x^i xi这个点值带入到 A ( x ) A(x) A(x)后的结果,基于这个想法,我们得到:
x ( 1 + x ) ( 1 + x 2 ) . . . ( 1 + x n ) = ∑ i ≤ n x i ( i + 1 ) 2 ∏ j ≤ i 1 1 − x j ∑ k a k x ( i + 1 ) k x(1+x)(1+x^2)...(1+x^n)=\sum_{i\le n}x^{\frac{i(i+1)}{2}}\prod_{j\le i}\frac{1}{1-x^j}\sum_ka_kx^{(i+1)k} x(1+x)(1+x2)...(1+xn)=i≤n∑x2i(i+1)j≤i∏1−xj1k∑akx(i+1)k
后面那个写成点值表示:
x ( 1 + x ) ( 1 + x 2 ) . . . ( 1 + x n ) = ∑ i ≤ n x i ( i + 1 ) 2 ∏ j ≤ i 1 1 − x j A ( x i + 1 ) x(1+x)(1+x^2)...(1+x^n)=\sum_{i\le n}x^{\frac{i(i+1)}{2}}\prod_{j\le i}\frac{1}{1-x^j}A(x^{i+1}) x(1+x)(1+x2)...(1+xn)=i≤n∑x2i(i+1)j≤i∏1−xj1A(xi+1)
这样整个式子只有 A ( x ) A(x) A(x)这一个变量了。将 A ( x ) A(x) A(x)单独提出来,我们得到:
A ( x ) = x ( 1 + x ) ( 1 + x 2 ) . . . ( 1 + x n ) − ∑ i ≥ 1 x i ( i + 1 ) 2 ∏ j ≤ i 1 1 − x j A ( x i + 1 ) A(x)=x(1+x)(1+x^2)...(1+x^n)-\sum_{i\ge 1}x^{\frac{i(i+1)}{2}}\prod_{j\le i}\frac{1}{1-x^j}A(x^{i+1}) A(x)=x(1+x)(1+x2)...(1+xn)−i≥1∑x2i(i+1)j≤i∏1−xj1A(xi+1)
好了,终于可以开始比较系数了!右边的式子看着很复杂,但是我们应该注意到左边式子的次数为
n
n
n,并且 系数是可以进行递推的,具体的,我们先处理出前
n
2
\frac{n}{2}
2n项,然后对于后
n
2
\frac{n}{2}
2n项,我们发现都可以通过前
n
2
\frac{n}{2}
2n的值暴力卷积算出,并且观察右边
x
x
x的次数发现最多只需要做
n
\sqrt{n}
n次卷积,这样就做完了。唯一需要注意的是按
j
j
j从大到小合并。这个计算方式也挺妙的。
变量下标推错了一堆。还是太菜了。
复杂度 O ( n n ) O(n\sqrt{n}) O(nn)。
生成函数 n b nb nb啊!!!
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define db double
using namespace std;
const int N=5e5+5;
int n,mod;
ll f[N],a[N],fac2[N],res;
void add(ll &x,ll y){if((x+=y)>=mod)x-=mod;}
void solve(int n){
if(n==1)return;
int m=n>>1;solve(m);
for(int j=0;j<=n;j++)f[j]=0;
for(int i=n;i;i--){
if((ll)i*(i+1)/2<=n){
for(int j=1;j<=m&&j*(i+1)<=n;j++)add(f[j*(i+1)],a[j]);
for(int j=n;j>=i;j--)f[j]=f[j-i];
for(int j=1;j<i;j++)f[j]=0;
for(int j=i;j<=n;j++)add(f[j],f[j-i]);
}
}
for(int j=m+1;j<=n;j++)add(a[j],mod-f[j]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>mod,f[1]=a[1]=1;
for(int i=1;i*(i+1)/2<=n;i++){
for(int j=i;j<=n;j++)add(f[j],f[j-i]);
for(int j=1;j+i*(i+1)/2<=n;j++)add(a[j+i*(i+1)/2],f[j]);
}
assert(a[1]==1);
assert(a[0]==0);
solve(n);
assert(a[1]==1);
fac2[0]=1;for(int i=1;i<=n;i++)fac2[i]=fac2[i-1]*2%mod;
res=fac2[n];
for(int i=1;i<=n;i++)add(res,mod-a[i]*fac2[n-i]%mod);
cout<<(res+mod)%mod;
}

浙公网安备 33010602011771号