题意
长度为N的排列是一个序列
一个长度为5的排列。
对于两个排列a和b,定义
给定整数
输入格式
输出格式
输入样例
2 4
输出样例
2
题解
感谢氧化钠大佬的讲解。
举个例子:
最后结果为
考虑从大到小把数放进去,有3种情况:
(
1.后面需要被覆盖的数增加一个(如添加
需要被覆盖的数
2.所填的数一个被覆盖,另一个覆盖后面的数;或两个数在同一位置
或
需要被覆盖的数不变,总权值
3.所填的数全部被更大的数覆盖
需要被覆盖的数
DP定义状态当前从大到小处理到
转移时要利用可以摆放的位置个数,如:
当前数
当前数
当前数
于是情况1方案数需
情况2方案数需
(一个被覆盖时,另一个不能被覆盖+一个没有覆盖时,另一个被覆盖或重合)
情况3方案数需
注:这里的p指转移前状态的p,实际代码还需修改
dp转移即为:
if(j-2*k>=0&&p>0)
dp[k][j][p]+=dp[k+1][j-2*k][p-1]*(k-p+1)*(k-p);//情况1
if(j-k>=0)
dp[k][j][p]+=dp[k+1][j-k][p]*(p*(k-p)+(k-p)*(p+1));//情况2
dp[k][j][p]+=dp[k+1][j][p+1]*(p+1)*(p+1);//情况3
一些思考过程
首先想到的是把一个串固定为
于是dp[i][j]表示前i个位置magic值为j,显然无法转移,无法判断已经使用过的数,或者哪些被覆盖,max到底为哪个数。
于是转换方向,发现结果实际为
由于无法判断还需要几个数被前面的覆盖,于是添加状态p表示剩余几个数需要被覆盖;
为保证后面的数一定被之前的数覆盖,k变为倒序枚举,并将循环顺序提至第二位
dp变为dp[i][k][j][p]表示还要计算i次数,当前考虑k,和为j,剩余p个需要被覆盖。
显然还要超时,发现i可以通过k与p计算出,于是删除这个状态,变为dp[k][j][p]
转移时仍按照固定一个串的方式转移,发现无法分辨情况2中的两种情况,于是改为同时往两个串添加数,转移成功。
完整代码
#include<cstdio>
#include<cstring>
const long long MAXN=55,MAXK=2505,MOD=1000000007;
long long dp[2][MAXK][MAXN];//当前考虑k,总和为j,有p个数被覆盖
int main()
{
long long n,K;
scanf("%I64d%I64d",&n,&K);
dp[(n+1)%2][0][0]=1;
for(long long kk=n,k=n%2;kk>=1;kk--,k^=1)
{
memset(dp[k],0,sizeof(dp[k]));
for(long long j=0;j<MAXK;j++)
for(long long p=0;p<=kk;p++)
{
if(j-2*kk>=0&&p>0)
dp[k][j][p]=(dp[k][j][p]+(1LL*dp[k^1][j-2*kk][p-1]*(kk-p+1)*(kk-p))%MOD)%MOD;
if(j-kk>=0)
dp[k][j][p]=(dp[k][j][p]+(1LL*dp[k^1][j-kk][p]*(p*(kk-p)+(kk-p)*(p+1))))%MOD;
dp[k][j][p]=(dp[k][j][p]+(1LL*dp[k^1][j][p+1]*(p+1)*(p+1))%MOD)%MOD;
}
}
long long ans=0;
for(long long j=K;j<MAXK;j++)
ans=(ans+dp[1][j][0])%MOD;
printf("%I64d\n",ans);
}