cf 1257 E. The Contest(好题)(LIS/思维)

题意:

有三个序列,a、b、c,每次操作可以把一个序列中的一个数移动到另一个序列中,

问,最少操作几次后,可以使得 a 序列里的所有数 小于 b 里面的所有数,b 里面的小于 c 里面的。

数字不重复,总共2e5的数据量。

思路:

做法一:(LIS)

这个做法是网上看到的,确实挺巧妙的,用这个方法,即使以后来个100个序列的,也不用怕了。

分别对a b c 排序,然后合并,

求最大上升子序列,然后上升子序列里的数不动,只移动非序列里的,答案就是 n - len,len最大,答案就最小。

求LIS的时候,要用树状数组优化一下,这样复杂度才能是 O(n*logn )。

扫描二维码关注公众号,回复: 10039090 查看本文章

做法二:(乱搞)

这个是自己瞎想想出来的,复杂度O(n),不过只适用于三个序列的。

三个序列里的,看做三种颜色记作颜色c1、c2、c3,然后混在一起排序,

然后每次固定一个左端点 l,表示移动到最后 1~l 为序列a里的数,查找一个可以使操作最小的右端点 r ,表示到最后 l+1~r 为序列b里的数,那么剩下的 r+1~n 就是序列c里的数。

怎么找操作最小的右端点r呢,先假设l不存在,也就是我们现在只把序列分为两段,

当右端点为 i 时,显然,操作数就是 1~i 的c3的数量 加上 i+1~n的 c2的数量,(这个就是需要对序列b c操作的次数)(对a操作的次数很好求,就是不在1~l 里的c1的数量)

为什么可以假设 l 不存在,因为当 l 移动的时候,如果吞掉一个c3,那么,r 在 l + 1 ~ n 每个点时的操作数就是全部减一,所以最小值的位置不变,只是值减一而已。

只分两段的时候,我们就很容易求 r 在 j ~ n时的最小值,这个就从后往前更新一下就好了。

所以,简而言之,就是预处理 j~n 的 r 的最小的位置在哪里,然后枚举 l ,更新答案。

(每次思维题我思路都讲得乱七八糟的。。。)

代码:

做法一:(LIS)

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <queue>
#include <functional>
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int dp[maxn],mx[maxn];
int lowbit(int i){
    return i & (-i);
}
void insert(int i,int x,int n){
    while(i <= n){
        mx[i] = max(mx[i],x);
        i += lowbit(i);
    }
}
int _find(int i){
    int res = 0;
    while(i > 0){
        res = max(mx[i],res);
        i -= lowbit(i);
    }
    return res;
}

int LIS(int a[],int n){
    int res = 0;
    memset(mx,0,sizeof(mx));
    memset(dp,0,sizeof(dp));
    for(int i = 1;i <= n;i++){
        dp[a[i]] = _find(a[i]) + 1;
        res = max(res,dp[a[i]]);
        insert(a[i],dp[a[i]],n);
    }
    return res;
}
int a[maxn],b[maxn];
int main(){
    int _n[3],n;
    while(scanf("%d%d%d",&_n[0],&_n[1],&_n[2]) != EOF){
        n = 0;
        int cnt = 0;
        for(int k = 0;k < 3;k++){
            for(int i = 1;i <= _n[k];i++)
                scanf("%d",&b[i]);
            sort(b + 1,b + _n[k] + 1);
            for(int i = 1;i <= _n[k];i++)
                a[++cnt] = b[i];
            n += _n[k];
        }
        
        int ans = LIS(a,n);
        printf("%d\n",n - ans);
    }
    return 0;
}
View Code

做法二:(乱搞)

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <cmath>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int sum2[maxn],sum3[maxn],mi_p[maxn],mi[maxn];
int vis[maxn];
int main(){
    int n1,n2,n3,x,n;
    while(scanf("%d%d%d",&n1,&n2,&n3) != EOF){
        memset(vis,0,sizeof(vis));
        n = n1 + n2 + n3;
        for(int i = 1;i <= n1;i++){
            scanf("%d",&x);
            vis[x] = 1;
        }
        for(int i = 1;i <= n2;i++){
            scanf("%d",&x);
            vis[x] = 2;
        }
        for(int i = 1;i <= n3;i++){
            scanf("%d",&x);
            vis[x] = 3;
        }

        sum3[0] = 0;
        for(int i = 1;i <= n;i++){
            sum3[i] = sum3[i - 1];
            if(vis[i] == 3)
                sum3[i]++;
        }
        sum2[n + 1] = 0;
        for(int i = n;i >= 1;i--){
            sum2[i] = sum2[i + 1];
            if(vis[i] == 2)
                sum2[i]++;
        }

        int mival = sum3[n],ans = 1e9;
        mi[n] = sum3[n];
        for(int i = n - 1;i >= 1;i--){
            mi[i] = mi[i + 1];
            if(mival > sum3[i] + sum2[i + 1]){
                mival = sum3[i] + sum2[i + 1];
                mi[i] = mival;
            }
        }
        mi[0] = min(sum2[1],mi[1]);
        
        int one = 0,sub = 0,res;
        ans = n2 + n3;
        for(int i = 0;i < n;i++){
            if(vis[i] == 1)
                one++;
            if(vis[i] == 3)
                sub++;
            res = (i - one) + mi[i] - sub + (n1 - one);
            if(res < ans)
                ans = res;
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/InitRain/p/12543640.html