「天使玩偶」· 题解 (点分治)

Posted heoitys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「天使玩偶」· 题解 (点分治)相关的知识,希望对你有一定的参考价值。

仍然是我自己的做法(不要走,这可是我目前见过的时间最快的了,只有17150ms)

一开始没仔细看题我想成了所谓翻转坐标系是因为坐标有正有负,整了一堆没用的(这句话好像在哪儿听过~)

然后发现x,y均为非负整数,然后就按照学长讲的思路翻转坐标系求四遍CDQ,但是我打出了一遍CDQ的方法(虽说里边算是夹了两个)

对于一个点(x,y)来说

1. 左上有一个天使玩偶(x1,y1), 距离为 (x-x1)+(y1-y)

2. 左下有一个天使玩偶(x1,y1), 距离为(x-x1)+(y-y1)

3. 右上有一个天使玩偶(x1,y1), 距离为(x1-x)+(y1-y)

4. 右下有一个天使玩偶(x1,y1), 距离为(x1-x)+(y-y1)

没有错吧,那么接下来把式子变一变

1. 左上  (x-y)-(x1-y1)

2.左下   (x+y)-(x1+y1)

3.右上   -(x+y)+(x1+y1)    <=>  -(x+y) - (-(x1+y1))

4.右下   (y-x) -(y1-x1)

就酱紫 我们会发现 后面的 关于x1,y1的式子越大,距离越小,我们可以开 4个树状数组+4个CDQ,干他

但是那太暴力了,要优雅

 当我们用CDQ的时候,是将时间排序,然后把 x归并排序,然后只是把y存进树状数组中查询,对于y并没有实际的单调,而时间全都已经排好了不去想他,所以考虑x

可以自己手 %一下,会发现,同在左的左上和左下在CDQ的时候,都是把x小于待查询的点的x值的点的y插入树状数组里(额,有点儿绕),也就是都进行这种操作

技术图片

那么可以把这两个CDQ合成一个,右上右下同理,按x升序排列一遍,再按x降序排列一遍,共两遍CDQ,开两个树状使用两遍,

继续考虑,还是这个图,把它翻转一下,方便理解我加上了L,R(表示上图中升序排列的L,R)

 技术图片

  那么到这里,就可以想明白了,一个CDQ里夹两个那样的for循环,一遍i=L,j=mid,升序遍历并排序(用一个tmp记录),一遍i=mid,j=r,反向降序遍历,同时在这两遍遍历中用两个树状数组分别维护上和下的最大值。

  而我们在找y的时候,左上右上是找大于待查询点y值的y中的最大值,这里我用了一个把树状数组反着用的操作。

  原来普通的是插入时从x到末尾,查询从x到1,现在我插入时从x到1,查询从x到末尾(当然是对于上的操作,下还是一般的树状数组)

  提醒一点,树状数组中存的数可能一定有负数,所以一开始要把树状数组置为-0x3fffffff,同时清空的时候也要这么做

  总结一下,我们只需要走一遍CDQ用一个序列跑两遍循环,加上两个树状数组记录即可得到答案,甚至因为时间是递增的,时间那一维不需要排序,连cmp,sort都不用打,远优于排序,排序,排序再4遍CDQ的做法

  PS:对于for循环以及清空等操作,建议自己一个个打,不要复制粘贴,Lockey就是因为粘了却忘记改细节,卡了一下午……

  感觉写的好的话,麻烦顶一下,谢谢关照!

  上代码(关键处有解释)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,num,tot,ans[1100000],maxy;
int tr1[1100000],tr2[1100000];
struct node
    int id,x,y;
    int cnt;
q[1100000],tmp[1100000];
int cmp(node a,node b)
    return a.id==b.id?((a.x==b.x)?(a.y<b.y):(a.x<b.x)):a.id<b.id;

int lowbit(int x)
    return x&(-x);

void add1(int x,int k)
    while(x)
        tr1[x]=max(tr1[x],k);
        x-=lowbit(x);
    

void add2(int x,int k)
    while(x<=maxy)
        tr2[x]=max(tr2[x],k);
        x+=lowbit(x);
    

int ask1(int x)
    int ans=-0x3ffffff;
    while(x<=maxy)
        ans=max(tr1[x],ans);
        x+=lowbit(x);
    
    return ans;

int ask2(int x)
    int ans=-0x3fffffff;
    while(x)
        ans=max(tr2[x],ans);
        x-=lowbit(x);
    
    return ans;

void clear1(int x)
    while(x)
        tr1[x]=-0x3fffffff;
        x-=lowbit(x);
    

void clear2(int x)
    while(x<=maxy)
        tr2[x]=-0x3fffffff;
        x+=lowbit(x);
    

void CDQ(int l,int r)
    if(l==r) return;
    int mid=(l+r)/2,i=l,j=mid+1,p=l;
    CDQ(l,mid),CDQ(mid+1,r);
    while(i<=mid&&j<=r)
        if(q[i].x<=q[j].x)
            if(q[i].cnt==1)
                add1(q[i].y,q[i].x-q[i].y);//距离为 (x0-y0)-(x1-y1),找左上(x1-y1) 最大值
                add2(q[i].y,q[i].x+q[i].y);//距离为 (x0+y0)-(x1+y1),找左下(x1+y1) 最大值
            
            tmp[p++]=q[i++];
        
        else
            if(q[j].cnt==2)
                ans[q[j].id]=min(ans[q[j].id],(q[j].x-q[j].y)-ask1(q[j].y));//同上
                ans[q[j].id]=min(ans[q[j].id],(q[j].x+q[j].y)-ask2(q[j].y));//同上
            
            tmp[p++]=q[j++];
        
    
    while(i<=mid)//全跑光
        if(q[i].cnt==1)
             add1(q[i].y,q[i].x-q[i].y);
             add2(q[i].y,q[i].x+q[i].y);
        
        tmp[p++]=q[i++];
    
    while(j<=r)//同上
        if(q[j].cnt==2)
            ans[q[j].id]=min(ans[q[j].id],(q[j].x-q[j].y)-ask1(q[j].y));//同上
            ans[q[j].id]=min(ans[q[j].id],(q[j].x+q[j].y)-ask2(q[j].y));//同上
        
        tmp[p++]=q[j++];
    
    for(int k=l;k<=mid;k++)//消除影响
        if(q[k].cnt==1)
            clear1(q[k].y);
            clear2(q[k].y);
        
     
    //跑右边的   
    i=mid,j=r;
    while(i>=l&&j>=mid+1)
        if(q[i].x>=q[j].x)
            if(q[i].cnt==1)
                add1(q[i].y,-(q[i].x+q[i].y)); //距离为 -(x+y)-(-(x1+y1)) 维护右上 -(x1+y1) 最大值
                add2(q[i].y,q[i].y-q[i].x); //距离为 (y-x)-(y1-x1) 维护右下 (y1-x1) 最大值
            
            i--;
        
        else
            if(q[j].cnt==2)
                ans[q[j].id]=min(ans[q[j].id],-(q[j].x+q[j].y)-ask1(q[j].y));
                ans[q[j].id]=min(ans[q[j].id],(q[j].y-q[j].x)-ask2(q[j].y));
            
            j--;
        
    
    while(i>=l)//全跑光
        if(q[i].cnt==1)
            add1(q[i].y,-(q[i].x+q[i].y));
            add2(q[i].y,q[i].y-q[i].x); 
        
        i--;
    
    while(j>=mid+1)//同上
        if(q[j].cnt==2)
            ans[q[j].id]=min(ans[q[j].id],-(q[j].x+q[j].y)-ask1(q[j].y));
            ans[q[j].id]=min(ans[q[j].id],(q[j].y-q[j].x)-ask2(q[j].y));
        
        j--;
    
    for(int k=l;k<=mid;k++)//消除影响
        if(q[k].cnt==1)
            clear1(q[k].y);
            clear2(q[k].y);
        
    
    for(int k=l;k<=r;k++) q[k]=tmp[k];

int main()
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        ++num;
        scanf("%d%d",&q[num].x,&q[num].y);
        maxy=max(maxy,q[num].y);
        q[num].id=tot;
        q[num].cnt=1;
    
    for(int i=1;i<=m;i++)
        ++num;
        scanf("%d%d%d",&q[num].cnt,&q[num].x,&q[num].y);
        maxy=max(maxy,q[num].y);
        if(q[num].cnt==2)q[num].id=++tot;
    
    for(int i=0;i<=tot;i++) ans[i]=0x3ffffff;
    for(int i=1;i<=maxy;i++) tr1[i]=tr2[i]=-0x3fffffff;
    CDQ(1,num);
    for(int i=1;i<=tot;i++)
        printf("%d\\n",ans[i]);
    

 

以上是关于「天使玩偶」· 题解 (点分治)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 2716 [Violet 3]天使玩偶 CDQ分治

[Violet]天使玩偶/SJY摆棋子

BZOJ.2716.[Violet3]天使玩偶(CDQ分治 坐标变换)

luogu4169 [Violet]天使玩偶/SJY摆棋子

Luogu P4169 [Violet]天使玩偶/SJY摆棋子

[Violet]天使玩偶/SJY摆棋子