题解 P1521 【求逆序对】
由于这道题中的序列只是1-n的全排列,所以对于动态规划的转移便变得十分方便。
我们发现,我们往已有的序列中添加一个数,这个数一定大于已有序列中的所有数,所以如果放在最后,逆序对数量增加了0,放在倒数第二的位置,逆序对增加了1,如果放在了最前面则逆序对数量增加原序列的长度。
所以我们简单地定义状态为长度为\(i\)的序列中,混乱度为\(j\)的序列个数。状态转移方程为:
\[dp[i][j]+=dp[i-1][j-k](0<=k<=i-1\&j-k>=0)
\]
最简单的版本很好想,这里不再放出代码,然后我们学习将原本\(O(n^2m)\)复杂度的算法利用前缀和加速成\(O(nm)\)。
很显然的,状态转移方程需要的状态是一个连续的段,所以我们开一个数组保存前缀和并在需要的时候调用即可。代码如下:
#include<bits/stdc++.h>
#define max(a,b) (a>b?a:b)
long long dp[2][5005],pre[2][5005],n,c;//滚动数组储存状态和前缀和。
bool p;
int main(){
std::cin>>n>>c;
dp[1][1]=1;//显然的,长度为1逆序对为0的个数为1。
for(int j=1;j<=c+1;j++)
pre[1][j]=1;//初始化
for(int i=2;i<=n;i++){
for(int j=1;j<=c+1;j++){ //为方便前缀和,我把逆序对数量都往上加了1
dp[p][j]=(pre[!p][j]-pre[!p][max(j-i,0)]+10000)%10000;//调用前缀和
pre[p][j]=(pre[p][j-1]+dp[p][j]+10000)%10000;//防止玄学模运算出现玄学错误。
}//记录前缀和
p=!p;
}
std::cout<<dp[!p][c+1];
}