南昌不翻车 G - Longest Chain 三维偏序(线段树 + CDQ分治)

题目大意:

    给3e5的三元组,问最长上升子序列为多长,上升子序列要求三个元素严格比之前大。

题目思路:

       首先肯定的是,排序降维,按照 x 升序排序,那么就转换成了二维偏序问题,(x相等的话 y , z降序排列,因为要三个元素都严格大于,当x相等了之后,已经不可能有偏序关系了,为了消除 y 对分治过程中的影响,直接降序排列)

       那么现在的问题就是二维的偏序问题怎么做?

CDQ分治

       我们把已经按照 x 排列好的分成两半,正常分治的话,一定是以下三步。

1 .  递归左区间  --->  2.  递归右区间  --->  3.  合并区间,计算两区间的相互影响。

      但是现在我们就不可以这么整,为啥呢,因为我们要统计的是最长上升子序列的长度,如果左半个内部处理好了,右半个内部处理好了,按照普通分治,合并的话右边去左边找最大 还比 z 大的,但是这样是错的,因为左区间影响了右区间的前半部分,而这前半部分也会影响右区间的后半部分 ,所以无论如何都要重新递归右区间,不如直接改成中序遍历。

1. 递归左区间  --->  2.计算左区间对右区间的影响  ---> 3 .  递归右区间

     此时所有的问题集中在如何计算左区间对右区间的影响。

        对左区间,右区间分别对 y 排序,然后定义两根指针分别指向两个区间开始,只要当前左区间的指向的y比右区间指向的y 要小,就把他当前的z值加入到权值线段树中,权值线段树维护区间最大值。(注意!加入到线段树中的意思是和线段树原来这个位置的值取max,并不是真的加法)。

       对于右区间,查询比当前指向的z,【1,z-1】区间的最大值+1即可。

(注意!! 因为采用的是中序遍历,所以线段树只能对当前区间使用,每次用完必须清空)

(线段树的清空也耗费了好多时间,网上有篇大佬的带时间戳树状数组做法可以完全忽略清空操作)

(线段树做法:10000ms                树状数组(普通):TLE          带时间戳树状数组:3000ms        mznb!!!!) 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int MAXN = 4e5+5;
int n,m,A,B;
struct nn
{
    int x,y,z,id;
}C[MAXN],li[MAXN];
int a = A, b = B, Cc = ~(1<<31), M = (1<<16)-1;
int r() {
    a = 36969 * (a & M) + (a >> 16);
    b = 18000 * (b & M) + (b >> 16);
    return (Cc & ((a << 16) + b)) % 1000000;
}
int f[MAXN];
bool cmp(nn a,nn b){
    if(a.x == b.x){
        if(a.y == b.y){
            return a.z > b.z;
        }
        return a.y>b.y;
    }
    return a.x<b.x;
}
bool cmp2(nn a,nn b)
{
    if(a.y == b.y){
        return a.z>b.z;
    }
    return a.y<b.y;
}
const int MAXM = 1e6+5;
int Max[4*MAXM];

void add(int p,int l,int r,int x,int d)
{
    if(l == r){
        Max[p] = max(Max[p],d);
        return ;
    }
    int mid = (l+r)/2;
    if(x<=mid) add(2*p,l,mid,x,d);
    else add(2*p+1,mid+1,r,x,d);
    Max[p] = max(Max[2*p],Max[2*p+1]);
}

void Clear(int p,int l,int r,int x)
{
    if(l == r){
        Max[p] = 0;
        return ;
    }
    int mid = (l+r)/2;
    if(x<=mid) Clear(2*p,l,mid,x);
    else Clear(2*p+1,mid+1,r,x);
    Max[p] = max(Max[2*p],Max[2*p+1]);
}
int ask(int p,int l,int r,int L,int R)
{
    if(L > R)return 0;

    if(L<=l&&r<=R)return Max[p];
    int mid = (l+r)/2;
    int ret = 0;
    if(L <= mid){
        ret = max(ret, ask(2*p,l,mid,L,R));
    }
    if(R > mid){
        ret = max(ret, ask(2*p+1,mid+1,r,L,R));
    }
    return ret;
}

int rk[MAXM];
void cdq(int l,int r)
{
    if(l == r)return ;
    int mid = (l+r)>>1;
    cdq(l,mid);
    for(int i=l;i<=r;i++){
        li[i] = C[i];
    }
    sort(li+l,li+mid+1,cmp2);
    sort(li+mid+1,li+r+1,cmp2);

    vector<int>v;
    v.clear();
    for(int i=l,j=mid+1;j<=r;j++){
        while(li[i].y<li[j].y && i<=mid){
            add(1,1,m,li[i].z,f[li[i].id]);
            v.push_back(i);
            i++;
        }
        f[li[j].id] = max(f[li[j].id],ask(1,1,m,1,li[j].z-1)+1);
    }
    for(int i=0;i<v.size();i++){
        int x = v[i];
        Clear(1,1,m,li[x].z);
    }

    cdq(mid+1,r);
}
int main()
{
    while(~scanf("%d%d%d%d",&m,&n,&A,&B)&&(n+m+A+B)){
        memset(Max,0,sizeof(Max));
        rep(i,1,m){
            C[i].id = i;
            scanf("%d%d%d",&C[i].x,&C[i].y,&C[i].z);
        }
        a=A, b = B, Cc = ~(1<<31), M = (1<<16)-1;
        rep(i,m+1,m+n){
            C[i].x=r();  C[i].y=r();  C[i].z=r();
            C[i].id = i;
        }
        n = n+m;  m = 0;
        for(int i=1;i<=n;i++){
            C[i].z+=2;f[i] = 1;
            m = max(m,C[i].z);
        }
        sort(C+1,C+1+n,cmp);
        cdq(1,n);
        int ans = 0;
        for(int i=1;i<=n;i++){
            ans = max(ans,f[i]);
        }
        printf("%d\n",ans);
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值