题解 P2513 【[HAOI2009]逆序对数列】

大概就是P1527的加强版?

由于这个问题里面是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)\)

我们发现,当前状态可以被一个连续的状态段摸到,也就是说,对于当前状态的更新需要加上一个连续的状态段的和,对于在\(O(1)\)时间里读取连续段的和,非常自然的,利用前缀和保存\(i-1\)的状态的前缀和。

不过,由于这个更新方程的特殊性,如果只使用一条滚动数组,更新前缀和值的时候会覆盖掉后面可能还需使用的值,如果倒序推导状态的话又对前缀和的导出极其不友好,可能需要另外循环一遍来导出前缀和,于是我采用了利用两个滚动数组反复横跳的方式来避免上述问题。(我考试的数据要是不用滚动数组会MLE)

代码如下。

#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];
}

猜你喜欢

转载自www.cnblogs.com/Schwarzkopf-Henkal/p/11775580.html
今日推荐