[LuoguP3769][CH【弱省互测】Round #1]TATT

同步与 Luogu blog

http://noi-test.zzstep.com/contest/%E3%80%90%E5%BC%B1%E7%9C%81%E8%83%A1%E7%AD%96%E3%80%91Round%20%231/TATT

https://www.luogu.com.cn/problem/P3769

Description

给定 \(n\) 个四维点,第 \(i\) 个点为 \((x_i,y_i,z_i,t_i)\)

对于一个合法的路径,满足经过的点的四个坐标全部单调不降。

路径的长度定义为该路径所经过点的个数。

求合法路径长度的最大值。

Hint

\(1\le n\le 5\times 10^4, x_i,y_i,z_i,t_i\in[0,10^9)\)

Soluiton

最简单的,可以动规。设 \(f_i\) 为点 \(1\cdots i\) 中的答案。那么有:

\[f_i = \max\{f_j|x_i\ge x_j,y_i\ge y_j,z_i\ge z_j,t_i\ge t_j\} +1\]

然而这样一看就超时,一是因为这是平方级别的,二是不好优化处理。

很容易想到先按 \(x\) 升序排序(直接白给一维),那么方程变成了这样:

\[f_i = \max\{f_j|i>j,y_i\ge y_j,z_i\ge z_j,t_i\ge t_j\} +1\]

但这样还是需要平方级别的时间。

我们发现现在这个其实就是一个经典的 三维偏序 (有一位排序优化掉了)问题,而且必须在线处理。

显然,K-D Tree 可以用来优化。

我们在 KDT 的结点中存以下信息:

  • 子树矩形边界,子树 \(\operatorname{size}\)

  • 结点对应点的信息,包含后三个维度的信息,以及该点的点权。这个点权又是怎么回事呢?详见下面。

  • 子树点权和。

其实,我们只要把上面的那个 \(f\) 作为点的点权插入到 KDT 中就行了。具体地:

初始树为空。对于一个将要查询的点 \((a_i,b_i,c_i)\),我们先找出所有满足点的三个坐标分别 \(\le\) 该点的三个坐标的点,然后算出这些点的点权的最大值\(+1\),记作 \(\operatorname{tmp}_i\) 。然后把点 \((a_i,b_i,c_i)\)\(\operatorname{tmp}_i\) 为点权插入到 KDT 中。

如此做 \(n\) 次,最终答案即为 \(\max\limits_{i=1}^{n} \operatorname{tmp}_i\) 的值。

Code Ver.1

#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;

const int N=5e4+5;
struct Point{
    int dat[3],val;
    Point(){}
    Point(int x,int y,int z,int v=0){dat[0]=x,dat[1]=y,dat[2]=z,val=v;}
}pnt[N];
namespace KDT
{
    const int alpha=0.75;
    #define lc(x) (t[x].ch[0])
    #define rc(x) (t[x].ch[1])
    
    struct Node{
        int ch[2];
        int Max[3],Min[3];
        int maxv;
        int size;
        Point p;
    }t[N];
    int total=0,root=0;
    
    inline void push_up(const int &u)
    {
        for(register int i=0;i<3;i++)
        {
            t[u].Max[i]=t[u].Min[i]=t[u].p.dat[i];
            if(lc(u))
            {
                t[u].Max[i]=max(t[u].Max[i],t[lc(u)].Max[i]);
                t[u].Min[i]=min(t[u].Min[i],t[lc(u)].Min[i]);
            }
            if(rc(u))
            {
                t[u].Max[i]=max(t[u].Max[i],t[rc(u)].Max[i]);
                t[u].Min[i]=min(t[u].Min[i],t[rc(u)].Min[i]);
            }
        }
        t[u].size=t[lc(u)].size+t[rc(u)].size+1;
        t[u].maxv=max(max(t[lc(u)].maxv,t[rc(u)].maxv),t[u].p.val);
    }
    
    namespace Rebuild
    {
        Point buf[N];
        stack<int> pool;
        int len=0,dim;
        
        inline bool imbalanced(const int &u)
        {
            if((double)t[u].size*alpha>(double)t[lc(u)].size) return true;
            if((double)t[u].size*alpha>(double)t[rc(u)].size) return true;
            return false;
        }
        inline bool cmp(const Point &a,const Point &b)
        {
            return a.dat[dim]<b.dat[dim];
        }
        void travel(const int &u)
        {
            if(!u) return;
            pool.push(u);
            buf[++len]=t[u].p;
            travel(lc(u));
            travel(rc(u));
        }
        int build(int l,int r,int d)
        {
            if(l>r) return 0;
            int mid=(l+r)>>1,u=pool.top();
            pool.pop(),dim=d;
            nth_element(buf+l,buf+mid,buf+r+1,cmp);
            t[u].p=buf[mid];
            lc(u)=build(l,mid-1,(d+1)%3);
            rc(u)=build(mid+1,r,(d+1)%3);
            return push_up(u),u;
        }
        void work(int &u,const int &d)
        {
            if(!imbalanced(u)) return;
            len=0,travel(u);
            u=build(1,len,d);
        }
    }
    
    void insert(const Point &x,int &u=root,int d=0)
    {
        if(!u)
        {
            u=++total;
            t[u].p=x;
            lc(u)=rc(u)=0;
            push_up(u);
            return;
        }
        if(x.dat[d]<=t[u].p.dat[d])
            insert(x,lc(u),(d+1)%3);
        else
            insert(x,rc(u),(d+1)%3);
        push_up(u);
        Rebuild::work(u,d);
    }
    int query(const int &x,const int &y,const int &z,const int &u=root)
    {
        if(!u) return 0;
        if(x<t[u].Min[0]||y<t[u].Min[1]||z<t[u].Min[2])
            return 0;
        if(x>=t[u].Max[0]&&y>=t[u].Max[1]&&z>=t[u].Max[2])
            return t[u].maxv;
        int ret=0;
        if(x>=t[u].p.dat[0]&&y>=t[u].p.dat[1]&&z>=t[u].p.dat[2])
            ret=t[u].p.val;
        ret=max(ret,query(x,y,z,lc(u)));
        ret=max(ret,query(x,y,z,rc(u)));
        return ret;
    }
    
    #undef lc
    #undef rc
}

struct Item{
    int i,j,k,l;
    bool operator <(const Item &t) const{
        return (i<t.i)||(i==t.i&&j<t.j)||(i==t.i&&j==t.j&&k<t.k)||(i==t.i&&j==t.j&&k==t.k&&l<t.l);
    }
}rec[N];
int n;

signed main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
        scanf("%d%d%d%d",&rec[i].i,&rec[i].j,&rec[i].k,&rec[i].l);
    
    sort(rec+1,rec+1+n);
    for(register int i=1;i<=n;i++)
        pnt[i]=Point(rec[i].j,rec[i].k,rec[i].l);
    int ans=0;
    for(register int i=1;i<=n;i++)
    {
        int tmp=KDT::query(pnt[i].dat[0],pnt[i].dat[1],pnt[i].dat[2])+1;
        ans=max(ans,tmp);
        pnt[i].val=tmp;
        KDT::insert(pnt[i]);
    }
    
    printf("%d\n",ans);
    return 0;
}

然后:https://www.luogu.com.cn/record/31807909

为什么TLE??


是因为没有剪枝!

optimizing 1

加了这个优化跑的飞快!

如果对于一次查询,做了一半,得到的返回值的最大值 \(\operatorname{cur}\) (下面代码称 ret) 。

若当前子树的点权最大值 \(\le \operatorname{cur}\) ,那么可以考虑停止往下计算。因为该子树的答案对结果无任何贡献,说白了就是算了白算。

Code Ver. 2

#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;

const int N=5e4+5;
struct Point{
    int dat[3],val;
    Point(){}
    Point(int x,int y,int z,int v=0){dat[0]=x,dat[1]=y,dat[2]=z,val=v;}
}pnt[N];
namespace KDT
{
    const int alpha=0.75;
    #define lc(x) (t[x].ch[0])
    #define rc(x) (t[x].ch[1])
    
    struct Node{
        int ch[2];
        int Max[3],Min[3];
        int maxv;
        int size;
        Point p;
    }t[N];
    int total=0,root=0;
    
    inline void push_up(const int &u)
    {
        for(register int i=0;i<3;i++)
        {
            t[u].Max[i]=t[u].Min[i]=t[u].p.dat[i];
            if(lc(u))
            {
                t[u].Max[i]=max(t[u].Max[i],t[lc(u)].Max[i]);
                t[u].Min[i]=min(t[u].Min[i],t[lc(u)].Min[i]);
            }
            if(rc(u))
            {
                t[u].Max[i]=max(t[u].Max[i],t[rc(u)].Max[i]);
                t[u].Min[i]=min(t[u].Min[i],t[rc(u)].Min[i]);
            }
        }
        t[u].size=t[lc(u)].size+t[rc(u)].size+1;
        t[u].maxv=max(max(t[lc(u)].maxv,t[rc(u)].maxv),t[u].p.val);
    }
    
    namespace Rebuild
    {
        Point buf[N];
        stack<int> pool;
        int len=0,dim;
        
        inline bool imbalanced(const int &u)
        {
            if((double)t[u].size*alpha>(double)t[lc(u)].size) return true;
            if((double)t[u].size*alpha>(double)t[rc(u)].size) return true;
            return false;
        }
        inline bool cmp(const Point &a,const Point &b)
        {
            return a.dat[dim]<b.dat[dim];
        }
        void travel(const int &u)
        {
            if(!u) return;
            pool.push(u);
            buf[++len]=t[u].p;
            travel(lc(u));
            travel(rc(u));
        }
        int build(int l,int r,int d)
        {
            if(l>r) return 0;
            int mid=(l+r)>>1,u=pool.top();
            pool.pop(),dim=d;
            nth_element(buf+l,buf+mid,buf+r+1,cmp);
            t[u].p=buf[mid];
            lc(u)=build(l,mid-1,(d+1)%3);
            rc(u)=build(mid+1,r,(d+1)%3);
            return push_up(u),u;
        }
        void work(int &u,const int &d)
        {
            if(!imbalanced(u)) return;
            len=0,travel(u);
            u=build(1,len,d);
        }
    }
    
    void insert(const Point &x,int &u=root,int d=0)
    {
        if(!u)
        {
            u=++total;
            t[u].p=x;
            lc(u)=rc(u)=0;
            push_up(u);
            return;
        }
        if(x.dat[d]<=t[u].p.dat[d])
            insert(x,lc(u),(d+1)%3);
        else
            insert(x,rc(u),(d+1)%3);
        push_up(u);
        Rebuild::work(u,d);
    }
    void query(const int &x,const int &y,const int &z,int &ret,const int &u=root)
    {
        if(!u) return;
        if(ret>=t[u].maxv)//剪枝在这里!!!
            return;
        if(x<t[u].Min[0]||y<t[u].Min[1]||z<t[u].Min[2])
            return;
        if(x>=t[u].Max[0]&&y>=t[u].Max[1]&&z>=t[u].Max[2])
            return void(ret=max(ret,t[u].maxv));
        if(x>=t[u].p.dat[0]&&y>=t[u].p.dat[1]&&z>=t[u].p.dat[2])
            ret=max(ret,t[u].p.val);
        query(x,y,z,ret,lc(u)),query(x,y,z,ret,rc(u));
    }
    
    #undef lc
    #undef rc
}

struct Item{
    int i,j,k,l;
    bool operator <(const Item &t) const{
        return (i<t.i)||(i==t.i&&j<t.j)||(i==t.i&&j==t.j&&k<t.k)||(i==t.i&&j==t.j&&k==t.k&&l<t.l);
    }
}rec[N];
int n;

signed main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
        scanf("%d%d%d%d",&rec[i].i,&rec[i].j,&rec[i].k,&rec[i].l);
    
    sort(rec+1,rec+1+n);
    for(register int i=1;i<=n;i++)
        pnt[i]=Point(rec[i].j,rec[i].k,rec[i].l);
    int ans=0;
    for(register int i=1;i<=n;i++)
    {
        int tmp=0;
        KDT::query(pnt[i].dat[0],pnt[i].dat[1],pnt[i].dat[2],tmp);
        ans=max(ans,++tmp);
        pnt[i].val=tmp;
        KDT::insert(pnt[i]);
    }
    
    printf("%d\n",ans);
    return 0;
}

这下就可以愉快的AC了,时间上毫无压力:https://www.luogu.com.cn/record/31808114

然而待我翻了翻 Luogu 的题解区之后,才发现还能优化,然而我没去写,先把思路讲一下。

optimizing 2

我们发现所有可能的点就这 \(n\) 个,没跑。

所以可以先把树建好,点权均为 0,然后用修改值的操作替换插入。

这样似乎也可以快一些。详见:https://www.luogu.com.cn/blog/zltttt/solution-p3769

猜你喜欢

转载自www.cnblogs.com/-Wallace-/p/12503187.html