zoukankan      html  css  js  c++  java
  • 2018 ICPC Pacific Northwest Regional Contest I-Inversions 题解

    题目链接: 2018 ICPC Pacific Northwest Regional Contest - I-Inversions

    题意

    给出一个长度为(n)的序列,其中的数字介于0-k之间,为0表示这个位置是空的。现在可以在这些空的位置上任意填入1-k之间的数字(可以重复)。问最多可以总共有多少对逆序对。(如果(i<j,p_i>p_j),则称((i,j))是一对逆序对)
    (1leq nleq 2*10^5, 1leq kleq 100)


    思路

    • 第一步,先证明最优的填入的序列一定是非降序的。这里可以用反证法。假设(alt b),对于序列(seq_1,a,seq_2,b,seq_3),这里seq表示一段数字。如果我们交换a和b的位置,可以发现,a原本的贡献中:

      1. (seq_1)里比(a)大的,保留了下来。
      2. (seq_2)里比(a)小的,必定也比(b)小。
      3. (seq_3)里比(a)小的,保留了下来。

      同样地,在b原本的贡献中:

      1. (seq_1)里比(b)大的,保留了下来。
      2. (seq_2)里比(b)大的,必定也比(a)大。
      3. (seq_3)里比(b)大的,保留了下来。

      因此,用非降序的序列来填空,至少不会比这个序列的其他排列方式差,也就可以认为这是最优的了。

      这里稍微提一下,如果用相似的方法,无法证明非升序是最优的。

    • 第二步,明确算法过程需要什么数据。这里需要用到的有:

    1. 原序列的:
      • 对于所有的数字1-k,每个位置与前面可组成的逆序对数。
      • 已产生的所有逆序对数。
    2. 填入空位的数:
      • 可与原序列产生的逆序对数
      • 填入序列之间产生的逆序对数。(大概是一个等差数列求和再减去相同部分)
    • 然后是核心部分。观察到k很小,所以我们枚举从左到右,从大到小填入序列。具体方法如下:

      1. 枚举从(k到1)的每个数字(val)。然后枚举任意连续的空位填入一串val。这样直接做的复杂度是(O(n^2)),所以需要一些优化。
      2. 首先用前缀和、差分把计算连续串的复杂度压到(O(n))建立,(O(1))查询。
      3. 然后再额外维护一个dp数组,记录对于这个val的情况,这样在取到更优值的时候,这个首部以前的地方就可以保证后续也可用,否则,按照前面的推导,这个地方如果不能放入val,那后面也不能放了。这样首部随着枚举尾部向后迁移,更新dp数组的复杂度也从(O(n^2))降到了(O(n))
      4. 最后再用这个额外的dp数组去更新答案的dp数组,直接覆盖。算法执行完,就可以得到填空与原序列产生的总贡献,和填空序列里不变的子序列减少的贡献。
      5. 最终答案就是原序列的贡献+填空与原序列产生的贡献+填空之间产生的贡献(等差数列求和再减去不变的子序列减少的贡献,代码中为方便计算,减去的部分在填空与原序列产生的贡献中提前计算了)。

      这里因为题目比较复杂的原因,很难只靠文字讲清楚。具体需要看代码里的一些注释辅助理解。


    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=2e5+10;
    const int maxk=1e2+10;
    const ll inf=1e18;
    int n,k;
    int seq[maxn];
    int preGt[maxn][maxk];//i,j: 下标从1到i这么多个大于j的数
    int cnt[maxk];//至今有多少个数大于i
    ll dp[maxn];//在i之前填空的贡献,减去相等序列本应有的贡献
    ll curdp[maxn];
    ll presum[maxn];//前缀和
    ll getsum(ll num){
        //1...n等差数列求和
        return num*(num+1)/2;
    }
    int main(){
        // freopen("in.txt","r",stdin);
        ios::sync_with_stdio(false);
        cin>>n>>k;
        int empty=0;
        ll ori=0;//原序列的逆序对数
        for(int i=1;i<=n;++i){
            cin>>seq[i];
            if(seq[i]==0){
                ++empty;
            }
            else{
                for(int j=0;j<seq[i];++j)
                    ++cnt[j];
                ori+=cnt[seq[i]];
            }
            for(int j=0;j<=k;++j)
                preGt[i][j]=cnt[j];
        }
    //////////////////////
        for(int i=1;i<=n;++i)
            dp[i]=-inf;
        for(int val=k;val>=1;--val){
            //先用大的数填空
            int tot=0;
            ll sum=0;//要注意的是,这里的sum是前缀和
            for(int i=1;i<=n;++i){
                if(seq[i]) continue;
    
                //加上i左边比val大的数
                sum+=preGt[i][val];
                //再加上i右边比val小的数,这里用总数-比val-1大的数量=比val小的数量
                //然后再总的减掉左边的,就是右边的。
                sum+=(preGt[n][0]-preGt[n][val-1])-(preGt[i][0]-preGt[i][val-1]);
                presum[++tot]=sum;
            }
            int emptyR=1;
            int emptyL=1;
            int curL=1;
            int curst=1;
            //枚举这串val填空的尾部
            for(int ed=1;ed<=n;++ed){
                ll mx=-inf;
                if(seq[ed]) continue;
                curL=emptyL;
                for(int st=curst;st<=ed;++st){
                    if(seq[st]) continue;
                    //从上一个状态加上,L到R之间填空val的贡献,减去这串本该下降的空位产生的贡献。
                    //例如2,1贡献了一个逆序对,但2,2就不贡献了。然后对比已有状态看是否更优。
                    ll tmp=dp[curL-1]+(presum[emptyR]-presum[curL-1])-getsum(emptyR-curL);
                    if(tmp<mx) break;
                    if(tmp>mx){
                        mx=tmp;
                        emptyL=curL;
                        curst=st;
                    }
                    ++curL;
                }
                curdp[emptyR]=max(dp[emptyR],mx);
                ++emptyR;
            }
            swap(dp,curdp);
        }
        cout<<ori+dp[empty]+getsum(empty-1)<<'
    ';
        return 0;
    }
    
  • 相关阅读:
    hdu 5918(强行水过去..正解KMP)
    hdu 5914(斐波拉契数列)
    hdu 5912(迭代+gcd)
    bzoj 2819(DFS序+树状数组+博弈+lca)
    BestCoder #88(1001 1002)
    hdu 5468(dfs序+容斥原理)
    hdu 5692(dfs序+线段树,好题)
    dfs序题目练习
    csu 1806 & csu 1742 (simpson公式+最短路)
    LuoGuP3774:[CTSC2017]最长上升子序列
  • 原文地址:https://www.cnblogs.com/crazyfz/p/12814382.html
Copyright © 2011-2022 走看看