「BZOJ2431」 [HAOI2009]逆序对数列 DP

problem

Description

对于一个数列\({ai}\),如果有\(i<j\)\(a_i>a_j\),那么我们称\(a_i\)\(a_j\)为一对逆序对数。若对于任意一个由\(1...n\)自然数组成的数列,可以很容易求出有多少个逆序对数。那么逆序对数为k的这样自然数数列到底有多少个?

Input

第一行为两个整数\(n\)\(k\)

Output

写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对\(10000\)求余数后的结果。

Sample Input

4 1

Sample Output

3

Hint

\(n<=1000,k<=1000\)

Solution

考虑一个\(1...i\)的排列,当要插入\(i+1\)时,由于它是最大的,所以无论在何处插入,其后方的所有数都会与它产生一对新的逆序对

于是有方程\(dp[i][j]\)表示\(1...i\)的排列中,有\(j\)个逆序对的方案数

转移方程为:
\[dp[i][j]=\sum_{k=0}^{min(i-1,j)}dp[i-1][j-k]\]

边界为\(dp[1][0]=1\)

复杂度是\(O(nk^2)\),注意到可以利用前缀和优化,于是使得复杂度进一步下降为\(O(nk)\),复杂度正确

Code

实际实现过程中发现状态数组可以优化掉第一维

#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 1005
using namespace std;

const int mod=10000;
int n,K;
int dp[maxn];
int sum[maxn];

int main()
{
    scanf("%d%d",&n,&K);
    dp[0]=sum[0]=1;
//  for(register int i=1;i<=n;++i)
//      for(register int j=0;j<=K;++j)
//          for(register int k=min(j,i-1);k>=0;--k)
//              dp[i][j]=(dp[i][j]+dp[i-1][j-k])%mod;
    for(register int i=1;i<=K;++i)
        sum[i]=sum[i-1];
    for(register int i=2;i<=n;++i)
    {
        for(register int j=0;j<=K;++j)
        {
            dp[j]=sum[j]%mod;
            if(j>i-1)
                dp[j]=(dp[j]-sum[j-i]+mod)%mod;
        }
        sum[0]=dp[0];
        for(register int j=1;j<=K;++j)
            sum[j]=(sum[j-1]+dp[j])%mod;
    }
    printf("%d",dp[K]);
}

这大概是处理类似的排列或者计数一类题目的套路?
似乎很值得推广啊

转载于:https://www.cnblogs.com/lizbaka/p/10292116.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值