PS:
我真的想喷一下,这NOI Online提高组全是蓝题,入门组还有两个都是紫题QwQ
原题链接
简洁题意:
构造一个非严格单调递减的序列使得和等于 n n n,要求方案数。两个序列不一样就是不同的方案。
分析:
因为要求单调递减,则可以设一个当前分的数
i
i
i,从
1
1
1到
n
n
n。于是我们可以给每一种结束时间分配一个最后一次是这个时间的方案。设
d
j
d_j
dj为
j
j
j结束的方案,
d
j
=
d
j
+
d
j
−
i
d_j=d_j+d_{j-i}
dj=dj+dj−i,这样就
80
80
80分了。说实话,考试的时候这样就够了,虽然我最开始只打了一个
30
30
30分的暴力QwQ。
好的,考虑如何拿下最后的二十分。我们可以发现,时间其实再优化一点就可以了。那么考虑少处理一点第一个
n
2
n^2
n2的
d
p
dp
dp,把整个问题分成两半,先处理一个中间值
m
m
m,求一下每一位小于
m
m
m的方案,再求大于的等于。小于的很好求,直接上前面的代码。那么大于等于
m
m
m的呢?设
f
(
i
,
j
)
f(i,j)
f(i,j)是和为
j
j
j拆出
i
i
i个的方案数。可以给两种操作:操作一,拆出一个数
m
m
m,
f
(
i
,
j
)
+
=
f
(
i
−
1
,
j
−
m
)
f(i,j)+=f(i-1,j-m)
f(i,j)+=f(i−1,j−m),操作二,给每个数加
1
1
1,
f
(
i
,
j
)
+
=
f
(
i
,
j
−
i
)
f(i,j)+=f(i,j-i)
f(i,j)+=f(i,j−i),则这样我们可以构造出一个任意一位大于
m
m
m的序列。最后,把这两个结合起来,只要前面的和后面的时间和是
n
n
n,则这两个可以结合起来。枚举后面分出序列的个数累加起来就是后面大于
m
m
m的序列的总和。两边的方案每个必不可少,乘法原理,乘起来即可。
那么
m
m
m设为多少呢?第二部分最多分出
n
÷
m
n\div m
n÷m个数。所以时间复杂度
Θ
(
n
n
m
+
n
m
)
\Theta(n\frac n m+nm)
Θ(nmn+nm),
m
=
n
m=\sqrt n
m=n左右最划算。
n
÷
n
n\div\sqrt n
n÷n为
n
n
n,为了少写一点代码,第二部分直接写m。但是要处理非整除的情况,
m
=
n
+
1
m=\sqrt n+1
m=n+1即可。
时间复杂度
Θ
(
n
n
)
\Theta(n\sqrt n)
Θ(nn)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int NN=100004;
int d[NN],f[500][NN];
int main()
{
int n,p;
scanf("%d%d",&n,&p);
int m=sqrt(n)+1;
d[0]=f[0][0]=1;
for(int i=1;i<m;i++)
for(int j=i;j<=n;j++)
d[j]=(d[j]+d[j-i])%p;
for(int i=1;i<m;i++)
for(int j=i;j<=n;j++)
{
f[i][j]=f[i][j-i];
if(j>=m)
f[i][j]=(f[i][j]+f[i-1][j-m])%p;
}
int ans=0;
for(int i=0;i<=n;i++)
{
int res=0;
for(int j=0;j<m;j++)
res=(res+f[j][n-i])%p;
ans=(ans+1ll*res*d[i]%p)%p;
}
printf("%d",ans);
return 0;
}