【XSY2576】【2017国家集训队】换教室(k叉树+DP)
\(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}\)
所以可以得到 \(val\equiv 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;
}