【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时,前面状态被限制不可以得到正常转移时,可以新开数组单方面记录可以转移的状态

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