携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
[HAOI2009]逆序对数列
题目描述
对于一个数列 ,如果有 且 ,那么我们称 与 为一对逆序对数。若对于任意一个由 自然数组成的数列,可以很容易求出有多少个逆序对数。那么逆序对数为 的这样自然数数列到底有多少个?
输入格式
第一行为两个整数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]);
}