题目大意
给你n个0,m个1,和一个k。每次操作你选择k个数,擦去这k个数并加入他们的平均数(1个),问最后会有多少种不同的实数。
n,m,k<=2000,(k-1)|(n+m-1)
解题思路
转化一下题意。
考虑一种方案是一颗k叉树,给每个0和1安排深度,作为树的叶子节点,这些点的权值为0/1,其他点权值为所有儿子的平均数。问最后有多少种根节点权值不同的树。
现在问题稍微转化了一下,看起来舒服了一点。
考虑根节点的权值。我们只用考虑1的深度,权值为
∑i=1..mk−dep1[i]
∑
i
=
1..
m
k
−
d
e
p
1
[
i
]
.
考虑合法的树的条件。不妨把把0都变成1,最后根节点的权值一定为1。
1=∑i=1..mk−dep1[i]+∑i=1..nk−dep0[i]
1
=
∑
i
=
1..
m
k
−
d
e
p
1
[
i
]
+
∑
i
=
1..
n
k
−
d
e
p
0
[
i
]
。
那么我们现在实际要求,有多少个不同的实数z,能够表示成m个k的次幂的和,且1-z能表示成n个k的次幂的和。
不妨把z搞成k进制数。那么原本同深度的点会进位,这就比较好看。
首先考虑z能够表示成m个数的和。
设
z=0.s1s2...sl
z
=
0.
s
1
s
2
.
.
.
s
l
,其中s[l]≠0,那么
∑s[i]≤m且∑s[i]%(k−1)=m%(k−1)
∑
s
[
i
]
≤
m
且
∑
s
[
i
]
%
(
k
−
1
)
=
m
%
(
k
−
1
)
,其实就是把进位搞一搞。
还要考虑1-z。考虑1-z的长度肯定也是l,那么我们把n-=1,这样1-z的每一位和s[]加起来都是k-1了。类似的写出一样的式子,那么我们发现我们要求的东西是一个序列s[]的方案数,其中满足:对于所有
1≤i≤l,s[i]∈[0,k−1]
1
≤
i
≤
l
,
s
[
i
]
∈
[
0
,
k
−
1
]
,且
s[l]≠0
s
[
l
]
≠
0
,且满足上面写的关于n,m的式子。
然后用一个简单的dp做就可以了。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=1e6+5,mo=1e9+7;
int n,m,K,i,j,k,q1,q2;
ll f[2][2005][2],sum[2005];
int ans;
int main()
{
freopen("t12.in","r",stdin);
scanf("%d %d %d",&n,&m,&K);
m--;K--;
q1=0;
q2=1;
f[0][0][0]=1;
fo(i,1,max(n,m)*2)
{
fo(j,0,n) sum[j+1]=(sum[j-1+1]+f[q1][j][0]+f[q1][j][1])%mo;
fo(j,0,n)
{
f[q2][j][0]=(sum[j+1]-sum[j-1+1])%mo;
k=max(0,j-K);
f[q2][j][1]=(sum[j-1+1]-sum[k-1+1])%mo;
}
fo(j,0,n)
if (j%K==n%K&&(i*K-j)%K==m%K&&i*K-j<=m)
ans=(ans+f[q2][j][1])%mo;
q1^=1;q2^=1;
}
ans=(ans+mo)%mo;
printf("%d",ans);
}