题解 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];
}
posted @ 2019-10-31 10:54  Schwarzkopf_Henkal  阅读(129)  评论(0编辑  收藏  举报