(Description)
对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。
在可以选择的课程中,有 (2n) 节课程安排在 (n) 个时间段上。在第 (i(1≤i≤n))个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 (c_{i}) 上课,而另一节课程在教室 (d_{i}) 进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 (n) 节安排好的课程。如果学生想更换第 (i) 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 (i) 个时间段去教室 (d_{i}) 上课,否则仍然在教室 (c_{i}) 上课。
由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 (i) 节课程的教室时,申请被通过的概率是一个已知的实数 (k_{i}),并且对于不同课程的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 (m) 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 (m) 门课程,也可以不用完这 (m) 个申请的机会,甚至可以一门课程都不申请。
(真正的题目才刚刚开始,上面的都是浮云)
因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。某天,当牛牛上完课来到下一间教室时,发现教室里空无一人,黑板上用白粉笔写着(N)个数字(0)和(M)个数字(1),旁边贴着一个纸条,纸条上写着另一个整数(K)。
牛牛观察了一会儿,发现(K−1)竟然整除(N+M−1。)于是,好奇的牛牛想知道,如果称“在黑板上随机选择(K)个数擦掉,并把这(K)个数的平均数作为一个新的数写在黑板上”为一次操作,在他一直重复操作直到黑板上只剩一个数后,这个数有多少种可能的取值。显然,在这个过程中,黑板上的每个数都是有理数,牛牛是大学生,学过关于分数的知识,所以你不用关心他怎么如何把一个有理数写在黑板上。请你能帮助他统计这个数值,由于该数值过大,你只需要求出它对(10^9+7)取模的值就行了。
(Input)
输入第一行包含三个整数(N,M,K)。
(Output)
输出一个整数,表示黑板上最后的数字可能的取值数量对(10^9+7)取模的值。
(Sample Input 1)
2 2 2
(Sample Input 2)
3 4 3
(Sample Input 3)
150 150 14
(Sample Output 1)
5
(Sample Output 2)
9
(Sample Output 3)
937426930
(HINT)
在第一个样例中,(5)个可能的取值分别是(frac{1}{4}),(frac{3}{8}),(frac{1}{2}),(frac{5}{8}),(frac{3}{4})。其中,(frac{3}{8})可以通过以下方式得到:
- 擦去(0)和(1),写上(frac{1}{2})。
- 擦去(frac{1}{2})和(1),写上(frac{3}{4})。
- 擦去(0)和(frac{3}{4}),写上(frac{3}{8})。
数据范围:
(1≤N,M≤2000,)
(2≤K≤2000,)
(K−1)整除(N+M−1)。
存在10分的子任务,满足(1≤N,M,K≤10。)
存在30分的子任务(独立于上一个子任务),满足(K=2。)
吐槽
有没有看到标题和体面很激动的小盆友,没错,我跟你们一样:这不就是(2016)年(tg)的原题吗?!!!
但这次终于见识到一句话入主题的精髓了
那个改题面的人,站出来,保证不盘死你
TMD之前的题面跟题目有什么关系啊啊
思路
按照习惯,看到小学生最擅长的数数题,并且需要取模,我们首先考虑(dp)
我们观察每次取(k)个数的平均数,是不是很像一棵(k)叉树?!
于是我们考虑一棵(k)叉树,它的叶子节点为(0)或(1),每个非叶子节点的值就是它的儿子的平均值
于是我们可以得到:这棵(k)叉树有(n+m)个叶子节点,(n)个(0),(m)个(1)
设(n)个(0)叶子节点的深度分别为(d0_{i}),(m)个(1)叶子节点的深度分别为(d1_{i})
那么我们容易得到根节点的值就为(sum k^{-d1_{i}})
我们考虑,当全部叶子节点的值都为(1)时,那么根节点的值就为(1)
于是,我们可以得到
(sum k^{-d1_{i}}+sum k^{-d0_{i}}=1)
现在,我们所要求的东西就转换成了:
有多少个(x)可以写成(sum k^{-d1_{i}})的形式,且(1-x)可以写成(sum k^{-d0_{i}})的形式(虽然我但代码里求的是(1-x)的个数,但都一样)
我们发现这些(sum)很烦人,于是我们将(x)表示成一个(k)进制小数 (0.a_{1}a_{2}a_{3}...a_{len}),这个小数的长度为(len)。
因为这小数部分都是由叶子节点为(1)的值构成的,于是(x)的每位小数的和为(sum_{i=1}^{len}a_{i}=m),当然,这是在没有进位之前的情况。一旦在第(p)位进位,那么(a_{p}-=1,a_{p+1}+=k),令(val=sum_{i=1}^{len}a_{i})
所以可以得到 (valequiv m(mod) (k+1))
我们再来看(1-x),同理,(1-x)的长度也一定是(len),并且它的位数和可以表示为((len-1)(k-1)+k-val =len(k-1)-val+1),易得(len(k-1)-val+1)需要满足(<=n)
我们的结论已经推完啦,接下来就是(dp)了
我们设(dp[i][j])表示到小数点后第(i)位,之前的位数和为(j)的方案数
又因为小数不能有后缀(0),所以我们要加一个状态(k)表示是否为(0)
我们在(dp)时可以考虑用前缀和(sum)优化
(p.s:)记得开(long) (long)
代码:
#include<bits/stdc++.h>
using namespace std;
const long long N=2010,mod=1e9+7;
long long sum[N];
long long f[N<<1][N][2];
long long n,m,k;
int main()
{
scanf("%lld %lld %lld",&n,&m,&k);
f[0][0][0]=1;
long long ans=0ll;
for(long long i=1;i<=max(n,m)*2;i++)
{
sum[0]=(f[i-1][0][0]+f[i-1][0][1])%mod;
for(long long j=1;j<=n;j++)
sum[j]=(sum[j-1]+(f[i-1][j][0]+f[i-1][j][1])%mod)%mod;
for(long long j=0;j<=n;j++)
{
f[i][j][0]=(sum[j]-sum[j-1])%mod;
if(j)
{
if(j>=k)f[i][j][1]=(sum[j-1]-sum[j-k]+mod)%mod;
else f[i][j][1]=(sum[j-1]+mod)%mod;
}
}
for(long long j=0;j<=n;j++)
{
if(j%(k-1)==n%(k-1)&&(i*(k-1)-j+1)%(k-1)==m%(k-1)&&i*(k-1)-j+1<=m)ans=(ans+f[i][j][1])%mod;
}
}
printf("%lld",ans);
return 0;
}