题目描述:
思路以及想法:
(注:这是本人写的第一篇题解,如果有不足的地方,请多多请教)
一开始拿到这题,我其实跟很多人一样,是有点蒙的(也可能只有我这样,本人太菜了),这种蒙体现在可能你有很多想法,有很多思路去做这道题,但具体想想又好像不行,每种想法你自己都能否定掉,又或者是毫无思路,这都是很正常的,那么下面我讲一下我自己的思考过程(不多说废话了)
首先看题,有人数,水果种数,和要求K三个要素,要求你写出恰好有K人满足题中要求的所有方案数,第一想法我是dfs模拟,即构造出这样的满足条件的排列方式,但很快我就放弃了,根本不可能,因为你要考虑很多问题,例如水果种类你选哪种,你让哪个小朋友成为那K个人之一等等这些复杂的问题,并且我看到了这样的一句话 方案总数量对998244353取模 我就知道模拟是肯定不行的,因为既然要对它取模就意味着比这个数要大,那么不管大多少这个数字都是我们无法接受的时间复杂度(意思就是要模拟的次数太多,不可能1s跑完),但也就是这句话,让我知道了它大概是要用什么解决了——动态规划;
没错就是动态规划,要是用动规的话,这样大的数会出现就无可厚非了,每次取模即可,并且数据里给的 1<=n,m<=2000,0<=k<=n-1, 刚好,双重循环的话,也能跑完,所以我就踏上了漫长的寻找动态转移方程之旅,结果,没有找到。我来复盘一下,我一开始定义 f[i][j]意义为i个小朋友里有j个满足题目条件的小朋友的方案数,结果发现写不出来,毫无规律,并且还有m这个因素,每个人拿的水果种类不同,导致方案数也是有很多的,并不只与n和k有关,那么定义一个三维数组——f[i][j][k]进行动态规划?这肯定是不行的,先不说麻烦,就是三层循环这个时间复杂度我们也不能接收,太大了,至此,我进入了一段思考,肯定要用动态规划写,但怎么写呢?这时我重新回到题目再次看题,读题,有了不一样的发现!
题目要求恰好有K个小朋友拿到的水果和其左边相邻小朋友拿到的水果不同,恰好,不能多也不能少,说明左右水果种类不同的这种情况得谨慎出现,不能随意出现,其他时候时,都得是相同的。都是相同的,何以体现?例如我们有四个小朋友,要求满足k=1个上述条件,且小朋友编号是1~4,那么可以是2号,这样1号一个种类,2,3,4一个种类,也可以是3号,1,2一个种类,3,4一个种类,也可以是4号,1,2,3一个种类,4一个种类。那么我们发现,好像有连续的人的水果种类是相同的,我们可以把水果种类相同的点连成一条线段,这样我们能得到一共有k+1条线段,因为只有k+1条线段才能有k个满足条件的点,就好像种树,10棵树有9个空,那么现在是9个空,问有几棵树,那肯定是10棵树啊。
——。——。——。——。——。——。——
就像上面,7条线,只有6个点(句号)。
那么知道这个我们有什么用呢,我们可以全局看一下:
无论怎么安排小朋友,把小朋友看作割点进行切割总能得到k+1条线段
对,就是上面的这个结论,将成为本题突破口。那么,我们怎么利用这个结论?
我们可以求出把n个人分(切割)成k+1组的总方案数,再一 一 乘与每组所能获得的水果种类即可得到正确答案
那么接下来我们的问题就变成了求把n个人分为k+1组的总方案数,并保证每组至少有一个人,这是必要条件,好了,那么怎么求?
我一开始也是不会求的,因为你不知道每组要有多少人,好像多少都有可能,那这情况就多了去了,但我想到我们还有动态规划没有用,可能就得在这时候用了,所以我定义
f[i][j] 的意义为把i个人分为j组有多少种方案数(这里是包含所有可能的,没有删去重复的,每个组的位置不同,圈的人也不同,例如1,1,5和1,5,1,虽然分的组的大小一样,但一个5在后,一个5在中,人是不一样的,所以就不画等号)
然后我们先讨论特殊情况,当j=1时,把i个人分为1组,就只有1种分法,所以f[i][1]=1,而当i=1,j>=2时,1个人要分多组,肯定不可能,所以f[1][j]=0(j>=2),这样就完成了设初值,那么一般情况是什么呢,我在这里先给出答案
f[i][j]=f[i-1][j-1]+f[i-1][j]
怎么理解呢,就是最近的这一个人有两种去路,一是自己单独一组,二是加入之前的人们创建的组别,只有这两种大可能,所以这样写,要是真不会推导了可以测试测试,写一下f[i][j],f[i-1][j],f[i][j-1],f[i-1][j-1]找找之间的关系,大概率是这几个之间的关系
那么,这题,到这里也就要接近尾声了,最后的乘水果种类就是按高中学的排列组合来乘,就是经典的一横条图形涂色问题,相邻的颜色不一样。第一组没有限制,所以乘m,往后的都得跟左边不一样,所以要乘m-1,所以总共要乘一个m和k个m-1,乘K个m-1可以用快速幂算法简单点。
好了,题解到这里就完事了,第一次写题解总有好多话想说,想讲的更清楚一些,想把我的做题思路重现一边,所以写的就多了一些,这题解也写了很长时间,希望大家见谅,下面就是代码了,我会写上必要的注释,拜拜了,下次见!
#include <bits/stdc++.h>
using namespace std;
const int INF = 998244353;
typedef long long ll;
ll f[2010][2010];
ll sum=1;
ll fastpower(ll a,ll b,ll p) //经典快速幂模板 a为乘数,b为次数,c为取模值
{
ll result=1;
while(b)
{
if(b&1)
result=result*a%p;
b>>=1;
a=a*a%p;
}
return result;
}
int main ()
{
int n,m,k;
cin>>n>>m>>k;
if(k==0)
cout<<m; //这里直接特判就行,k=0时大家都一个种类,所以答案就是m(种类数)
else
{
for(int i=1;i<=n;i++)
f[i][1]=1; //设初始值
for(int i=2;i<=k+1;i++)
f[1][i]=0; //设初始值,其实这个没有,就上面的有用
for(int i=2;i<=n;i++)
{
for(int j=2;j<=k+1;j++)
{
if(j>i)
f[i][j]=0; //特判分不了的情况
else if(j==i)
f[i][j]=1; //特判一人一组的情况
else
f[i][j]=(f[i-1][j-1]%INF+f[i-1][j]%INF)%INF; //上面提到的状态转移方程
}
}
sum=f[n][k+1]*m%INF; //第一次乘m
sum*=fastpower(m-1,k,INF); //乘k次 m-1
sum%=INF; //记得取模
cout<<sum;
}
return 0;
}