排列问题(DP)

题意

长度为N的排列是一个序列 (a,a,...,a) ,恰好包含从 1 N 的每一个数字。例如, (3,1,4,5,2)
一个长度为5的排列。
对于两个排列a和b,定义
magic(a,b)=max(a,b)+max(a,b)+...+max(a,b)
给定整数 N K ,求有多少对排列 ab 满足 a b 的长度都为 N ,并且 magic(a,b)K

输入格式
2 个整数 N K
输出格式
1 个整数,表示答案,答案模 1,000,000,007

输入样例
2 4
输出样例
2

题解

感谢氧化钠大佬的讲解。

举个例子:
132231
最后结果为 3+2+3=8 ,数列中总是大的数覆盖小的数。

考虑从大到小把数放进去,有3种情况:
( x 表示填过的其它更大的数)
1.后面需要被覆盖的数增加一个(如添加 y , (y<x)
   yy xxxx......
需要被覆盖的数 +1 ,总的权值 +2y

2.所填的数一个被覆盖,另一个覆盖后面的数;或两个数在同一位置
xyy  xxxxx......

x yy xxxxx......
需要被覆盖的数不变,总权值 +y

3.所填的数全部被更大的数覆盖
xy  yxxxxx......
需要被覆盖的数 1 ,总的权值不变

DP定义状态当前从大到小处理到 k ,总和为 j ,有 p 个数被覆盖的方案数
转移时要利用可以摆放的位置个数,如:
当前数 k 被大的数覆盖: p 个位置;
当前数 k 随便放一个空位: k 个位置;
当前数 k 要覆盖更小的数: kp 个位置;

于是情况1方案数需 ×(kp)×(kp1) ,(-1表示减去两个数放同一位置)
情况2方案数需 ×(p×(kp)+(kp)×(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);
}

猜你喜欢

转载自blog.csdn.net/can919/article/details/79520027
今日推荐