逆序对数列
双倍经验题!
切掉这题就可以切掉P1521了
首先设立状态: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)
复杂度降为O(nk),轻松水过去
代码极短:
#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]);
}