天使玩偶
[Violet] 天使玩偶/SJY摆棋子
题目描述
Ayu 在七年前曾经收到过一个天使玩偶,当时她把它当作时间囊埋在了地下。而七年后 的今天,Ayu 却忘了她把天使玩偶埋在了哪里,所以她决定仅凭一点模糊的记忆来寻找它。
我们把 Ayu 生活的小镇看作一个二维平面坐标系,而 Ayu 会不定时地记起可能在某个点 \((x, y)\) 埋下了天使玩偶;或者 Ayu 会询问你,假如她在 \((x,y)\),那么她离近的天使玩偶可能埋下的地方有多远。
因为 Ayu 只会沿着平行坐标轴的方向来行动,所以在这个问题里我们定义两个点之间的距离为 \(\operatorname{dist}(A,B)=|A_x-B_x|+|A_y-B_y|\)。其中 \(A_x\) 表示点 \(A\) 的横坐标,其余类似。
输入格式
第一行包含两个整数 \(n\) 和 \(m\),在刚开始时,Ayu 已经知道有 \(n\) 个点可能埋着天使玩偶, 接下来 Ayu 要进行 \(m\) 次操作。
接下来 \(n\) 行,每行两个非负整数 \((x_i,y_i)\),表示初始 \(n\) 个点的坐标。
再接下来 \(m\) 行,每行三个非负整数 \(t,x_i,y_i\)。
- 如果 \(t=1\),则表示 Ayu 又回忆起了一个可能埋着玩偶的点 \((x_i,y_i)\)。
- 如果 \(t=2\),则表示 Ayu 询问如果她在点 \((x_i,y_i)\),那么在已经回忆出来的点里,离她近的那个点有多远
输出格式
对于每个 \(t=2\) 的询问,在单独的一行内输出该询问的结果。
样例 #1
样例输入 #1
2 3
1 1
2 3
2 1 2
1 3 3
2 4 2
样例输出 #1
1
2
提示
数据规模与约定
对于 \(100\%\) 的数据 保证 \(1 \leq n,m\leq 3 \times 10^5\),\(0 \leq x_i,y_i \leq 10^6\)。
这篇博客是按照蓝书上的思路来写的,我觉得蓝书上的思路非常好
首先,看到这个题目的时候,我是完全不会的,一点思路没有
别说\(O(n\log_2(n))\)了,我连\(O(nm)\)都写不出来
那就先考虑简化的问题
如果这题没有添加操作,而是先告诉你有多少个地方可能有,怎么做?
那就是对于一个位置,我们每次的操作就是找到四个方向上距离这个点曼哈顿距离最短的点
写为式子就是\(min(|a-x|+|b-y|)\) 绝对值不是一个好东西,有他在基本就是不能用数据结构维护
考虑分类讨论,去绝对值
这个绝对值很好去,其实就是对以这个节点作为坐标原点的4个象限上分别统计就好
那么,去完绝对值之后的式子就是\(min(a+b-(x+y))\),\(a+b\)为定值,显然只需要求\((x+y)\)的最小值即可
这个问题是很简单的,以数组的开头或者结尾作为起点的区间最小值维护,树状数组维护即可。
明显这是一个二位偏序,因为有两个方向的条件需要同时满足,所以需要先按照横坐标排序,再按照纵坐标查询
所以如果只是这样子的话,时间复杂度\(O((n+m)logn)\)
但是这题目有插入操作
现在我们再来看看怎么做。
增加了一个添加的操作,我们可以把这个理解为一个新的限制
题目也被转化为:对于一个询问\((x,y)\),找到时间戳在其之前,且\(x_i\leq x且y_i\leq y\)的最大的\(x_i+y_i\)
那就是三维偏序
唉唉
那么CDQ分治除了三位偏序之外,还能干什么呢?
这个应该就要算是其他理解了,我现在的理解还局限在三位偏序
代码还是有点麻烦的,毕竟有4个方向
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
char c=getchar();int a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
inline int lowbit(int x)
{
return x&(-x);
}
struct contruct
{
int x,y,type,id,ans;
}a[3000001],b[3000001],temp[3000001];
int tr[6000001],n,m,k,tot;
void add(int i,int x)
{
while(i<=k)
{
tr[i]=max(tr[i],x);
i+=lowbit(i);
}
}
int ask(int i)
{
int ans=0;
while(i>0)
{
ans=max(tr[i],ans);
i-=lowbit(i);
}
return ans?ans:-1147483647;
}
void mymemset(int i)//The same role with memset ,but faster
{
while(i<=k)
{
tr[i]=0;
i+=lowbit(i);
}
}
void CDQ(int l,int r)
{
if(l==r)return ;
int mid=l+r>>1;
CDQ(l,mid);CDQ(mid+1,r);
int i=l,j=mid+1,len=l;
while(j<=r)
{
while(i<=mid&&b[i].x<=b[j].x)
{
if(b[i].type==1)
{
add(b[i].y,b[i].x+b[i].y);
}
temp[len++]=b[i++];
}
if(b[j].type==2)
{
a[b[j].id].ans=min(a[b[j].id].ans,b[j].x+b[j].y-ask(b[j].y));//It is passible that the direction has no point
}
temp[len++]=b[j++];
}
for(int h=l;h<i;h++)
{
if(b[h].type==1)mymemset(b[h].y);
}
while(i<=mid)temp[len++]=b[i++];
for(int i=l;i<=r;i++)
b[i]=temp[i];
}
void solve(int nx,int ny)
{
for(int i=1;i<=n+m;i++)
{
b[i]=a[i];
if(nx)b[i].x=k-b[i].x;
if(ny)b[i].y=k-b[i].y;
}
CDQ(1,n+m);
}
int main()
{
// freopen("angle.in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();k=0;
for(int i=1;i<=n;i++)
{
a[i].type=1;
a[i].x=read()+1;
a[i].y=read()+1;
a[i].id=i;
k=max(k,max(a[i].x,a[i].y));
}
for(int i=1+n;i<=m+n;i++)
{
a[i].type=read();
a[i].id=i;
a[i].x=read()+1;
a[i].y=read()+1;
a[i].ans=2147483647;
k=max(k,max(a[i].x,a[i].y));
}
k++;
solve(1,0);
solve(0,0);
solve(0,1);
solve(1,1);
for(int i=1+n;i<=n+m;i++)
{
if(a[i].type==2)cout<<a[i].ans<<endl;
}
return 0;
}
总结一下CDQ分治的性质和适用区间吧
首先是怎么从三位偏序推广到四维偏序?
要解决的问题是,在判断完\(b[i]<=b[j]\)后,还需要再保证\(c[i]<=c[j]\),才能假如以\(d\)为下表的树状数组中
而在统计答案的时候,则是要让\(b[i]>b[j]和c[i]>c[j]\)同时成立才能在树状数组中找答案
这个肯定是不能直接加在三位偏序里的,应该是要嵌套的,否则时间复杂度不就没变吗...
好像知道了。就是在每一个cdq分治sort完了之后,再对这个小区间cdq一次,然后这个里面的cdq是用\(c[i]\)来排序的
那就好了
CDQ分治不会真的就只能做三位偏序吧?
三维偏序是满足了什么性质才能用CDQ分治做呢?
想到了上篇博客的那句话
用一个子问题来计算对另一个子问题的贡献
这个贡献在原本的情况下是不好统计的
但是对于满足了一定条件的区间是可以统计的
这个想法和倍增和类似,都是把一段区间用\(2^n\)的小区间拼起来
CDQ分治统计的消息的特点是:可以较快速的统计一段区间上的操作对一个确定的区间的所有影响
所有,所以很难满足,在三维偏序中,刚刚好就满足了,在以\(a\)排序后,就是能够保证统计完后,前面的小区间对后面的小区间产生的所有影响或者说是价值被全部记录了
但是这个性质的分析不是很简单,不然我也不会像这么久了。。
我第一眼看三维偏序我是看不出来这个性质的,当然也有一部分的原因是因为我没有这个意识
这个性质应该只是一部分,我感觉CDQ分治应该不止只能用来做三维偏序,不然局限性也太强了
看看其他题目吧