题意
长度为N的排列是一个序列
(a,a,...,a)
,恰好包含从
1
到
一个长度为5的排列。
对于两个排列a和b,定义
magic(a,b)=max(a,b)+max(a,b)+...+max(a,b)
给定整数
N
和
输入格式
2
个整数
输出格式
输入样例
2 4
输出样例
2
题解
感谢氧化钠大佬的讲解。
举个例子:
132231
最后结果为
3+2+3=8
,数列中总是大的数覆盖小的数。
考虑从大到小把数放进去,有3种情况:
(
x
表示填过的其它更大的数)
1.后面需要被覆盖的数增加一个(如添加
yy xxxx......
需要被覆盖的数
+1
,总的权值
+2y
。
2.所填的数一个被覆盖,另一个覆盖后面的数;或两个数在同一位置
xyy xxxxx......
或
x yy xxxxx......
需要被覆盖的数不变,总权值
+y
。
3.所填的数全部被更大的数覆盖
xy yxxxxx......
需要被覆盖的数
−1
,总的权值不变。
DP定义状态当前从大到小处理到
k
,总和为
转移时要利用可以摆放的位置个数,如:
当前数
当前数
当前数
于是情况1方案数需
×(k−p)×(k−p−1)
,(-1表示减去两个数放同一位置)
情况2方案数需
×(p×(k−p)+(k−p)×(p+1))
(一个被覆盖时,另一个不能被覆盖+一个没有覆盖时,另一个被覆盖或重合)
情况3方案数需
×p×p
注:这里的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
一些思考过程
首先想到的是把一个串固定为
1,2,3,4...n
,另一个串排列,答案
×n!
于是dp[i][j]表示前i个位置magic值为j,显然无法转移,无法判断已经使用过的数,或者哪些被覆盖,max到底为哪个数。
于是转换方向,发现结果实际为
1,2..n
中一些数计算两次,一些数计算一次,一些数计算零次,于是变为dp[i][j][k]表示还要计算i次数,和为j,当前考虑到数k;
由于无法判断还需要几个数被前面的覆盖,于是添加状态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);
}