题目大意:
给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);
}
}