宝同学,您太厉害啦!
updated on 2022/6/6: 改了一些下标细节,加了点基于 Ferrers 图像对式子的解释。
今年的独立命题除了福建都很一可赛艇啊!
首先有个经典结论是,如果选出的子集
那么可以得到一个
陷入困境。一方面这个 DP 的形式不好优化,另一方面容易发现这个 DP 的劣势在于第二维进行了压缩,我们无法得知准确的信息。
那么考虑算不合法的方案数。我们在每一个方案中第一个无法被表示的位置
那么定义
但是,注意到和为
容易发现我们选择了
那么考虑我们之前 DP 的实质,我们是每次加入一列。那么这里我们按行来考虑。发现这个图有如下特点:
- 行的最大值是
O(\sqrt n) 的,已经强调了很多次了; - 上面的行大小都不小于下面的行;
- 如果大小为
i 的行出现了,那么大小在[1,i-1] 内的行都出现了。
根据实际意义容易发现到上面的结论。
注意到我们直接计算
如何算这个不带限制的
// 此时 f[0] 不等于 1
for(int i=n;i;--i)
{
if(LL(i)*(i+1)/2>n) continue;
// 从大的开始选,要保证 i 一定要被选到。从小的开始选的话可能会缺。考虑实际意义,因为 1~p 都要被选到才合法。
for(int j=n;j>=i;--j) f[j]=f[j-i];
// 我先单选个 i,保证之前选择的方案合法
++f[i];
for(int j=i;j<=n;++j) add(f[j],f[j-i]);
// 做完全背包。
}
f[0]=1;
那么对每个
先考虑对
注意到这个点,我们在处理
那么我们已经知道了前面一半的
这个时候继续考虑加入每一行之类的想法就非常的没有前途(主要是我没找到)。注意到我们现在要的是,通过选择
假设没有选择的数限制(但是还是有选择的数不能重复的限制),不妨写作生成函数的形式:
再来考虑这个东西的意义……我们按上面的方格图的形式把它排好,然后第
那么,枚举选择的集合的大小:
先解释一下后面这个东西。可能有人会问后面这个东西并没有限制选择了至多
不难发现
但是现在带大小限制。首先上界我们不用管,可以考虑对
记
再记生成函数
把
从大到小枚举
注意到计算
可以采用其他的手段得到更优的做法。
Motivation: 正难则反,视角转换,生成函数。
int f[500005],g[500005];
void Solve(int n)
{
if(n<=1) return ;
Solve(n>>1);
for(int i=1;i<=n;++i) g[i]=0;
for(int i=n;i;--i)
{
if(LL(i)*(i+1)/2>n) continue;
for(int j=0,t=i+i*(i+1)/2;t<=n;++j,t+=i+1) add(g[t],f[j]);
for(int j=i;j<=n;++j) add(g[j],g[j-i]);
}
for(int i=n/2+1;i<=n;++i) sub(f[i],g[i]);
}
int n;
int main(){
n=read(),MOD=read();
for(int i=n;i;--i)
{
if(LL(i)*(i+1)/2>n) continue;
for(int j=n;j>=i;--j) f[j]=f[j-i];
f[i]=1;
for(int j=i;j<=n;++j) add(f[j],f[j-i]);
}
f[0]=1;
Solve(n);
int ans=1;
for(int i=0;i<n;++i) add(ans,ans),sub(ans,f[i]);
write(ans);
return 0;
}