计蒜之道 百度AI小课堂-上升子序列

计蒜之道 百度AI小课堂-上升子序列

题目描述

给一个长度为 \(n\) 的数组 \(a\) 。试将其划分为两个严格上升子序列,并使其长度差最小。

输入格式

输入包含多组数据。

数据的第一行为一个正整数 \(T\) ,表示数据组数。

每组数据包括两行:

第一行包括一个正整数 \(n\)

第二行包括一个长度为 \(n\) 的数组 \(a\)

输出格式

对于每组数据输出一行一个整数,表示两个子序列的最小长度差。若不存在划分方案则输出\(-1\)

数据范围

\(T <= 10\)
简单: \(n <= 10^5, 0 <= a_i <= 2^{30}\), 最多只存在一种合法的划分方案
中等: \(n <= 10^3, 0 <= a_i <= 2^{30}\), 划分方案不超过\(10^{18}\)
困难: \(n <= 10^5, 0 <= a_i <= 2^{30}\), 划分方案不超过\(10^{18}\)

思路:直接上题解

如果一个位置\(k\)满足对于\(∀_i∈[1,k], ∀_j∈[k+1,n], a_i < a_j\)恒成立,那么称\(k\)为一个分界点。找出所有分界点后可将序列分为若干段,每段之间相互独立,同时每段只存在不超过一种合法的方案数。求出每段的划分\(O(n)\),再进行背包dp(\(O(nlogK)\)(\(K\)为可能的划分方案数))

其实就是说对于某个\(i\),满足\(max(1,i) < min(i + 1,n)\),则序列\([1,i]和[i+1,n]\)是相互独立的。
同理再来一个\(j > i\) 满足\(max(1,j) < min(j + 1,n)\), 则序列\([1,j]和[j+1,n]\)相互独立, 序列\([1,i]\)已经被划分出去,所以左边剩下\([i+1,j]\)
简单证明这样的每段如果合法,只存在一种划分。
即证明对于合法的一段\([i,j](j > i + 1)\), 若不存在\(k\),满足\(i<=k<j, max(i,k) < min(k+1,j)\),那么段\([i,j]\)只有一种划分方案。
反证不存在的情况有多种划分成立, 则存在的情况下只有一种划分,存在的情况下可以分成不可再分的几段,每段有多种划分,拼起来也会有多种划分, 与只有一种划分矛盾。
每段只有一种划分, \(m\)段组合起来的方案数为\(2^m = K\), 所以\(m = logK\)

最后问题变成一个背包,从每段中选择一个序列长度组合起来,最后是否出现长度为\(j\)的序列(\(dp[j]\))

#include<bits/stdc++.h>
#define LL long long 
using namespace std;
const int INF = 1e9;
const int N = 1e5 + 10;
int a[N];
int mi[N], mx[N];
int dp[2][N];
int T, n, o;
bool gao(int l, int r){
    int first = 0, second = 0, cnt = 0;
    for(int i = l;i <= r;i++){
        if(first < a[i]) first = a[i], cnt++;
        else if(second < a[i]) second = a[i];
        else return false;
    }
    int d[2] = {cnt, r - l + 1 - cnt};
    for(int i = 0;i <= n;i++) {
        dp[o ^ 1][i] = 0;
        for(int k = 0;k < 2;k++){
            if(i >= d[k]) dp[o ^ 1][i] |= dp[o][i - d[k]];
        }
    }
    o ^= 1;
    return true;
}
int main(){
    cin>>T;
    while(T--){
        cin>>n;
        for(int i = 1;i <= n;i++) cin>>a[i];
        if(n == 1){
            cout<<-1<<endl;
            continue;
        }
        mx[0] = -1, mi[n + 1] = (1<<30) + 1;
        for(int i = 1;i <= n;i++) mx[i] = max(mx[i - 1], a[i]);
        for(int i = n;i >= 1;i--) mi[i] = min(mi[i + 1], a[i]);
        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;
        int last = 1;
        bool flag = true;
        o = 0;
        for(int i = 1;i <= n;i++){
            if(mx[i] < mi[i + 1]){
                if(!gao(last, i)){
                    flag = false;
                    break; 
                }
                last = i + 1;
            }
        }
        int ans = -1;
        if(flag){
            for(int i = n / 2;i >= 0;i--) if(dp[o][i]){
                ans = n - 2 * i;
                break;
            }
        }
        cout<<ans<<endl;
    }
}

转载于:https://www.cnblogs.com/jiachinzhao/p/10939332.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值