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

本文介绍了一种使用KD树解决最近邻问题的方法,并针对数据不平衡问题引入了替罪羊树思想进行优化。文章详细阐述了KD树的构建、查询过程以及如何通过重构保持树的平衡,提高查询效率。

题目链接

题目描述

Ayu 在七年前曾经收到过一个天使玩偶,当时她把它当作时间囊埋在了地下。而七年后 的今天,Ayu 却忘了她把天使玩偶埋在了哪里,所以她决定仅凭一点模糊的记忆来寻找它。

我们把 Ayu 生活的小镇看作一个二维平面坐标系,而 Ayu 会不定时地记起可能在某个点 (xmy) 埋下了天使玩偶;或者 Ayu 会询问你,假如她在 (x,y) ,那么她离近的天使玩偶可能埋下的地方有多远。

因为 Ayu 只会沿着平行坐标轴的方向来行动,所以在这个问题里我们定义两个点之间的距离为dist(A,B)=|Ax-Bx|+|Ay-By|。其中 Ax 表示点 A的横坐标,其余类似。

Sol

CDQ 分治太难写了 , 蒟蒻不会 , 只能打个 KD_Tree 骗分…

如果写 KD_Tree 的话 , 就直接是求解最近邻问题了 , 和原来的平面最近点对一样 , 只是把欧式距离改为了曼哈顿距离

简单的思路:
建出 KD_Tree 后 , 对于每一组询问直接在 KD_Tree 上不停地向左右子树递归寻找最优解,
利用 KD_Tree 记录的当前节点统辖的点的上下边界来算出可能的最优答案并进行剪枝 , 因为每次询问只有一个点 , 我们 KD_Tree 良好的划分方式使得剪枝效率还是比较高的
插入操作就直接插进去就行了

如果是 bzoj ,这样写就过了 , 但是在洛谷上是过不了的 , 因为直接插入会使 KD_Tree 很不平衡
所以要用到重构的思想来保证复杂度

一种方法是每插入个 5000~10000 次就直接把整棵树重构一次,这种方法比较省力
但是还是可能被卡 , 有趣的是如果在 bzoj 上这样写重构也会 TLE , 但是不重构就过了…

而洛谷上的数据比较强 , 这种重构方法貌似过不了 , 这时要用到替罪羊树的思想
如果某一棵子树的左右儿子相差过大 , 那么就把这棵子树重构, 这样重构的好处在于有目的性 , 重构的地方精准 , 能很大程度上提升效率

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<algorithm>
#include<set>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
namespace IO{
    const int maxn(1<<21|1);
    char ibuf[maxn],*iS,*iT,obuf[maxn],*oS=obuf,*oT=obuf+maxn-1,c,st[55];
    int f,tp;
    char Getc(){
        return (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,maxn,stdin),(iS==iT?EOF:*iS++)):*iS++);
    }
    void Flush(){
        fwrite(obuf,1,oS-obuf,stdout);
        oS=obuf;
    }
    void Putc(char x){
        *oS++=x;
        if(oS==oT)Flush();
    }
    template<class Int>void Input(Int &x){
        for(f=1,c=Getc();c<'0'||c>'9';c=Getc())f=c=='-'?-1:1;
        for(x=0;c<='9'&&c>='0';c=Getc())x=(x<<3)+(x<<1)+(c^48);
        x*=f;
    }
    template<class Int>void Print(Int x){
        if(!x)Putc('0');
        if(x<0)Putc('-'),x=-x;
        while(x)st[++tp]=x%10+'0',x/=10;
        while(tp)Putc(st[tp--]);
    }
}
using IO::Input;
using IO::Print;
using IO::Putc;
#define LS T[u].ls
#define RS T[u].rs
namespace KD_tree{
	typedef long long ll;
	typedef double db;
    const int N=6e5+10;int ti=0;
    const int INF=1e9;
	const db alpha=0.75;
    int rt,cnt,now;int ans;
    int n,m;
    struct point {
        int data[2];
        inline bool operator <(point b)const{
            return data[now]<b.data[now];
        }
    }a[N];
	int st[N],top=0;
    struct node{
        int ls,rs,data[2];int Min[2],Max[2];int size;
        node(){ls=rs=data[1]=data[0]=Min[0]=Min[1]=Max[0]=Max[1]=size=0;}
    }T[N];
    inline void Fill(node &A,point B){
        A=node();A.size=1;
        A.data[0]=A.Min[0]=A.Max[0]=B.data[0];
        A.data[1]=A.Min[1]=A.Max[1]=B.data[1];
        return;
    }
    inline void update(int u){
        if(LS){
            T[u].Min[0]=min(T[u].Min[0],T[LS].Min[0]);
            T[u].Min[1]=min(T[u].Min[1],T[LS].Min[1]);
            T[u].Max[0]=max(T[u].Max[0],T[LS].Max[0]);
            T[u].Max[1]=max(T[u].Max[1],T[LS].Max[1]);
        }
        if(RS){
            T[u].Min[0]=min(T[u].Min[0],T[RS].Min[0]);
            T[u].Min[1]=min(T[u].Min[1],T[RS].Min[1]);
            T[u].Max[0]=max(T[u].Max[0],T[RS].Max[0]);
            T[u].Max[1]=max(T[u].Max[1],T[RS].Max[1]);
        }
		T[u].size=T[LS].size+T[RS].size+1;
        return;
    }
    void Rebuild(int &u,int l,int r,int type)
    {
        if(l>r) return;int mid=l+r>>1;now=type;nth_element(a+l,a+mid,a+r+1);
		u=st[top--];Fill(T[u],a[mid]);
        Rebuild(LS,l,mid-1,type^1);Rebuild(RS,mid+1,r,type^1);
        update(u);
        return;
    }
    inline void init()
    {
        Input(n);Input(m);
        for(int i=1;i<=n;++i){Input(a[i].data[0]);Input(a[i].data[1]);st[i]=n-i+1;} top=n;
        Rebuild(rt,1,n,0);
    }
	void Push(int u){
		if(!u) return;
		Push(LS);st[++top]=u,a[top]=(point){T[u].data[0],T[u].data[1]};Push(RS);
		return;
	}
	inline void check(int &u,int type){
		top=0;
		if(T[u].size*alpha<T[RS].size||T[u].size*alpha<T[LS].size)
			Push(u),u=0,Rebuild(u,1,top,type);
		return;
	}
    void insert(int &u,int type,point A) {
        if(!u) {u=++n;Fill(T[u],A);return;}
        if(A.data[type]<T[u].data[type]) insert(LS,type^1,A);
        else insert(RS,type^1,A);
        return update(u),check(u,type);
    }
    inline int Dis(int x1,int y1,int x2,int y2){
        if(x1>x2) swap(x1,x2);if(y1>y2) swap(y1,y2);
        return x2+y2-x1-y1;
    }
    inline int get_maxdis(int u,int x,int y){
        register int ret=0;
        if(x>T[u].Max[0]) ret+=x-T[u].Max[0];
        if(x<T[u].Min[0]) ret+=T[u].Min[0]-x;
        if(y>T[u].Max[1]) ret+=y-T[u].Max[1];
        if(y<T[u].Min[1]) ret+=T[u].Min[1]-y;
        return ret;
    }
    void Query(int u,int x,int y){
        if(!u) return;
        int dl=INF,dr=INF,d;
        d=Dis(T[u].data[0],T[u].data[1],x,y);ans=min(ans,d);
        if(LS) dl=get_maxdis(LS,x,y);
        if(RS) dr=get_maxdis(RS,x,y);
        if(dl<dr) {
            if(dl<ans) Query(LS,x,y);
            if(dr<ans) Query(RS,x,y);
        }
        else{
            if(dr<ans) Query(RS,x,y);
            if(dl<ans) Query(LS,x,y);
        }
        return;
    }
    inline void work()
    {
        int op,x,y;
        for(int i=1;i<=m;++i) {
            Input(op);Input(x);Input(y);
            if(op==1){point A;A.data[0]=x;A.data[1]=y;insert(rt,0,A);}
            else{ans=INF;Query(rt,x,y);Print(ans);Putc('\n');}
        }
    }
}
int main()
{
    KD_tree::init();
    KD_tree::work();
    IO::Flush();
}

由于给定引用中未涉及洛谷P1186题目的相关C++代码及具体信息,所以无法直接分析解决该题目的C++代码。不过可以从一般的解题思路来考虑分析此类问题。 ### 代码功能分析 通常对于洛谷上的题目代码,要分析其功能,需要从输入输出、数据结构使用、核心算法逻辑等方面入手。比如,代码从标准输入读取数据,可能使用数组、结构体等数据结构来存储数据,通过某种算法(如动态规划、贪心算法、搜索算法等)对数据进行处理,最后将结果输出到标准输出。 ### 优化建议 - **时间复杂度优化**:如果代码的时间复杂度较高,如$O(n^2)$甚至更高,可以考虑使用更高效的算法。例如,将暴力枚举算法优化为动态规划算法,或者使用数据结构(如线段树、树状数组等)来降低时间复杂度。 - **空间复杂度优化**:若代码使用了大量的内存,可以考虑使用滚动数组、压缩存储等方法来减少空间使用。 - **代码结构优化**:可以将代码拆分成多个函数,使代码结构更清晰,提高代码的可读性和可维护性。 ### 示例代码分析(以引用中的其他代码为例) 以下以引用[2]中的寻宝题代码为例进行分析: ```cpp #include<iostream> using namespace std; struct dl{ int a; bool b; }; struct dl mig[10005][1005]; int n,m,l,num=0; int main(){ cin>>n>>m; for(int i=0;i<n;i++)//输入每个房间的信息 for(int j=0;j<m;j++){ cin>>mig[i][j].b>>mig[i][j].a; } cin>>l; for(int i=0;i<n;i++){//在每一层里面找到进入下一层的房间 int t=0;//计数器 int j=l; num=(num+mig[i][l].a)%20123;//求和 while(1){ if(mig[i][j].b) t++; if(t==mig[i][l].a) break;//注意j在不断变化 j++; if(j==m) j=0; } l=j; } cout<<num; return 0; } ``` #### 代码功能 - 定义了一个结构体`dl`,用于存储每个房间的两个信息:`b`(布尔类型)和`a`(整数类型)。 - 从标准输入读取楼层数`n`、每层的房间数`m`以及初始进入的房间编号`l`。 - 读取每个房间的信息并存储在二维数组`mig`中。 - 遍历每一层,在每一层中根据规则找到进入下一层的房间,同时累加每个房间的`a`值,并对结果取模20123。 - 最后输出累加结果。 #### 优化建议 - **代码可读性**:可以将查找进入下一层房间的逻辑封装成一个函数,使`main`函数更简洁。 - **边界条件检查**:可以添加对输入数据的边界条件检查,增强代码的健壮性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值