LGP8340题解
太弱小了,没有力量。。。/ll
如果正着做只有一 \(O(n^2)\) 的 DP:\(dp[i][j]\) 表示 \([1,i]\) 能凑出 \([1,j]\) 中所有数的方案数。
这太麻烦了,而且看上去就不可优,考虑计算不合法的。
枚举第一个不合法的位置,设 \(f_i\) 表示在 \([1,i]\) 中选能凑出 \([1,i]\) 中的所有数但是凑不出 \(i+1\) 的方案数,然后强制不选 \(i+1\),答案就是 \(2^n-\sum_{i=1}^{n-1}f_i2^{n-i-1}\)。
那么很显然 \(f_i\) 一定是一个关于 \(i\) 的拆分数。先设 \(g_i\) 表示 \(i\) 的互异拆分数。
然后发现直接算 \(f\) 又很麻烦,于是考虑再次算不满足的。
设 \(w(i,j)\) 表示在 \([i,j]\) 中的数选择一部分,使其和为 \(j-i+1\) 的方案数。
那么有 \(f_n=g_n-\sum_{i=1}^{n-1}f_i\times w(i+2,n)\)。越写越复杂
考虑 \(w(i,j)=[x^{j-i+1}]\prod_{k=i}^{j}(1+x^i)\)。对于一个互异拆分数,枚举其左上角的三角形,可以将剩下部分变成拆分数。这里只是所有数都不小于 \(j\),所以在枚举三角形时顺便带掉即可。
需要注意的是相当于令拆分数的大小不能超过 \(k\),所以还需要通过共轭转化成最大元素为 \(k\) 的拆分数。
所以有:
然后就有:
注意到一件事,对于 \(f_i\),下一个要选的元素一定大于 \(i+1\),所以对 \(f_n\) 有贡献的 \(f_i\) 满足 \(i<\lfloor\frac{n}{2}\rfloor\)。
于是考虑类似倍增的方法,每次计算 \([1,\lfloor\frac{n}{2}\rfloor]\) 的 \(f\) 然后再来计算后半段。
设:
那么有:
复杂度 \(T(n)=T(\frac{n}{2})+O(n\sqrt{n})=O(n\sqrt{n})\),常数不大,可以通过。
做完这题之后就感觉自己根本不会数学。。。被打自闭了。。。
#include<cstdio>
#include<cmath>
const int M=5e5+5;
int n,P,f[M],g[M];
inline int Add(const int&a,const int&b){
return a+b>=P?a+b-P:a+b;
}
inline int Del(const int&a,const int&b){
return b>a?a-b+P:a-b;
}
inline void Solve(const int&n){
if(n<2)return;Solve(n>>1);for(int i=0;i<=n;++i)g[i]=0;
for(int x=sqrt(n*2),y=x*(x+1)>>1;x>=1;y-=x--){
for(int i=x+y,j=0;i<=n;i+=x+1,++j)g[i]=Add(g[i],f[j]);
for(int i=x+x+y,j=x+y;i<=n;++i,++j)g[i]=Add(g[i],g[j]);
}
for(int i=n/2+1;i<=n;++i)f[i]=Del(f[i],g[i]);
}
signed main(){
int ans(1);scanf("%d%d",&n,&P);f[0]=g[0]=1;
for(int x=1,y=1;y<=n;y+=++x){
for(int i=x,j=0;i<=n-y;++i,++j)g[i]=Add(g[i],g[j]);
for(int i=y,j=0;i<=n;++i,++j)f[i]=Add(f[i],g[j]);
}
Solve(n-1);for(int i=0;i<n;++i)ans=Add(ans,ans),ans=Del(ans,f[i]);printf("%d",ans);
}