【8月刷题打卡】逆序对数列

91 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

[HAOI2009]逆序对数列

题目描述

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

输入格式

第一行为两个整数n,k。

输出格式

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

样例 #1

样例输入 #1

4 1

样例输出 #1

3

提示

样例说明:

下列3个数列逆序对数都为1;分别是1 2 4 3 ;1 3 2 4 ;2 1 3 4;

测试数据范围

30%的数据 n<=12

100%的数据 n<=1000,k<=1000

首先设立状态:f[i][j] 表示i个数逆序对数量为j的个数

转移方程:f[i][j]=∑(s=0,1,...,i-1) f[i-1][j-s]

解释一下这个方程:当有i-1个数时,有j-1个逆序对时,添加第i个数,必有一个位置刚好使逆序对数+1,即可转移到f[i][j]。同理j-2,j-3……j-(i-1),因为只有i-1个数,所以最多到j-(i-1),当然也可能不增加逆序对,即j-0;

但是这样转移需要O(k)的复杂度,总复杂度为O(nk^2),这是无法承受的。

下面用到前缀优化:

转移状态是连续的,我们可以记sum[i][j]=f[i][0]+f[i][1]+……+f[i][j];

转移方程就变为:f[i][j]=sum[i-1][j]-(j>i?sum[i-1][j-i]:0)

#include<bits/stdc++.h>
using namespace std;
int n,k,f[1005][1005],sum[10005]; //f[i][j] 表示i个数逆序对数量为j的个数,sum[i]记录前缀,这里优化了一下空间,减了一维
int main()
{
    scanf("%d%d",&n,&k);
    f[0][0]=1;  //初始状态
    for(int i=0;i<=k;i++) sum[i]=1;  //初始指为1
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=k;j++)
            if(j>=i) f[i][j]=(sum[j]-sum[j-i]+10000)%10000; //状态转移,要加10000再取模,不然可能小于0
            else f[i][j]=sum[j]%10000; 
        sum[0]=f[i][0]%10000;  
        for(int j=1;j<=k;j++)   
            sum[j]=(f[i][j]+sum[j-1])%10000; //更新sum,用于下一轮状态转移
    }
    printf("%d",f[n][k]);
}