【Codeforces1367F2】Flying Sort (Hard Version) 思維dp

題目鏈接:https://codeforces.ml/contest/1367/problem/F2

題目大意:

給出一段序列,每次操作可以將一個數移到序列後方或者前方,問最少需要幾步使得序列單調不降。

題目思路:

網上思路看不懂,只能瞎推dp

單看F1,沒有重複數字的情況下

只需要找一段最長的連續上升子序列即可(經典題)了

F2,加入重複數字就不可以這麼考慮了

比如樣例:0 1 0 2 就可以選擇0 1 2 然後把0移動到前面

比如樣例:1 1 1 2 2 3 3 3 2 就選擇1 1 1 2 2 2使得3 3 3 靠後

這樣一看,對於每個數字來說,可以取滿也可以不取滿

用dp[i][0]表示當前數字出現了多少次

用dp[i][1]表示當前數字取滿的的答案

用pre[id]與dp[i][0]同理,不過怕暈而已

首先肯定性質1:

如果一個數字不取滿,它只能出現在末尾或者開頭

1.考慮如果第i個數字不取滿,以第i個數字結束:

(1)i-1個數字取滿

(2)i-1個數字不取滿(此時i-1只能取出現的次數)

解釋一下爲什麼dp[i][0]的狀態特別:

單方面考慮dp[i][0]:dp[i][0]可以繼承dp[i-1][1],dp[i-1][0],但是都是有侷限的,如果繼承了dp[i-1]][1]的狀態,那麼接下來dp[i+1][0]就不能繼承dp[i][0]了(性質1),所以dp[i][0]只是單方面的表示第i個數字目前爲止出現了多少次。

那怎麼統計未取滿結束的答案?

爲了防止以上情況,我們可以另開一個數組sc(用來輔助更新ans):sc[id] = max(sc[id],max(dp[id-1][0]-pre[id],dp[id-1][1]-pre[id]));

單看這個方程可能有點暈,把更新ans的方程也一列:

maxl = max(maxl,sc[id]+pre[id]+1)

此時就是更新當前id未取滿的最優答案。

sc[id]:

sc[id]的意思就是記錄之前dp[id-1][0],dp[id-1][1]取滿的最大值(都是合法的,不懂可以再看一下不取滿的倆狀態)

但是僅僅記錄這個不夠,因爲還不知道sc[id]記錄的位置到當前位置有多少個id。

我們更新sc[id]的時候更新爲sc[id]-pre[id](減去之前出現過的),這樣到達新位置時,只需要sc[id]+pre[id]+1。

當然主席樹也可以解決上述問題,不過小題大作了

這樣就可以保證性質1的正確的前提下進行轉移。

2.考慮第i個數字取滿

那麼非常簡單了,與不取滿同理

(1)i-1個數字取滿

(2)i-1個數字不取滿(此時i-1只能取出現的次數)

然後同理,設first爲第一次出現數字i時的最大值

if(pre[id]==1):first[id] = max(dp[id-1][0],dp[id-1][1]);

然後當pre[id]等於這個數字的次數vis[id]時,則有:

dp[id][1] = first[id]+vis[id]

Code:

/*** keep hungry and calm CoolGuang!***/
#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=2e18;
const int maxn=1e6+6;
const int mod=1e9+6;
const double eps=1e-15;
inline bool read(ll &num)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll num[maxn];
vector<ll>v;
int vis[maxn];
int getid(ll x){
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
ll dp[maxn][2];
ll pre[maxn],first[maxn],sc[maxn];
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        read(n);
        v.clear();
        for(int i=1;i<=n;i++){
            vis[i] = 0;
            dp[i][0] = dp[i][1] = 0;
            pre[i] = 0;
            first[i] = 0;sc[i] = -(1e9+7);
        }
        for(int i=1;i<=n;i++){
            read(num[i]);
            v.push_back(num[i]);
        }
        ll maxl = 0;
        sort(v.begin(),v.end());
        v.erase((unique(v.begin(),v.end())),v.end());
        for(int i=1;i<=n;i++) vis[getid(num[i])]++;
        for(int i=1;i<=n;i++){
            int id = getid(num[i]);
            pre[id]++;
            dp[id][0] = dp[id][0] + 1;

            if(pre[id] == 1) first[id] = max(dp[id-1][0],dp[id-1][1]);
            if(pre[id] == vis[id]) dp[id][1] = first[id] + vis[id];

            sc[id] = max(sc[id],max(dp[id-1][0]-pre[id],dp[id-1][1]-pre[id]));
            maxl = max(maxl,sc[id]+pre[id]+1);///10
            maxl = max(maxl,max(dp[id][0],dp[id][1]));
          ///  printf("%lld %lld\n",pre[id],sc[id]);
          ///  printf("%d :%lld %lld\n",id,dp[id][0],dp[id][1]);
        }
        printf("%lld\n",n-maxl);
    }
    return 0;
}

/**
1 2
1 2 3 4

4 3 2 1
**/

總結:

這個方法可以抽象:當進行dp時,前面狀態被限制不可以得到正常轉移時,可以新開數組單方面記錄可以轉移的狀態

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章