3.考古研究
(geologic.pas/c/cpp)
【问题描述】
很久很久以前,有一个叫NOIP的高级文明十分繁荣。但是由于火山喷发,这个高级文明最终还是毁灭了。NOIP文明沿着直线状的河流发展,当NOIP文明毁灭的时候,这块地表变成了平地。NOIP文明的遗迹可以看作坐标平面的x轴。y轴为高度。也就是说,在坐标平面上,直线y=0为地表,y>0的区域为地面上方,y<0的区域为地面下方。此外,从NOIP文明毁灭的时候算起,a年前(a>=0)的地层在直线y=-a的位置上。
NOIP文明毁灭后,NOIP文明的遗迹上发生了Q次地壳变动。第i次(1<=i<=Q)地壳变动用位置Xi,方向Di和变动量Li表示。Di为1或者2。第i次地壳变动以下文所述方式进行:
- 地层的移动方式如下:
○ Di=1时,沿着经过点(Xi, 0),斜率为1的直线形成断层,在这条直线上方的地层会沿着直线向上移动Li。也就是说,这条直线上方的点(x,y)会移动到(x+Li, y+Li)。
○ Di=2时,沿着经过点(Xi, 0),斜率为-1的直线形成断层,在这条直线上方的地层会沿着直线向上移动Li。也就是说,这条直线上方的点(x,y)会移动到(x-Li, y+Li)。
- 此后,y>0区域的地层会因风化作用而全部消失。
时光轮转,回到现代。考古学家LC博士开始发掘NOIP文明的遗迹。LC博士希望知道哪个位置的地表的地层是NOIP文明毁灭前多少年前的地层。目前已经知道发生过哪些地壳变动。你的工作是帮助LC博士计算,对于1<=i<=N的各个整数i,点(i-1, 0)和点(i, 0)之间的地表的地层是NOIP文明毁灭前多少年前的地层。
请写一个程序,给定NOIP文明的遗迹上发生的地壳变动的信息,对于所有整数i(1<=i<=N),输出点(i-1, 0)和点(i, 0)之间的地表底层是NOIP文明毁灭前多少年的地层。
【输入】
第一行是两个用空格隔开的整数N和Q。N是要计算的地点的个数,Q是地壳变动发生的回数。
接下来Q行中第i行(1<=i<=Q)包含3个用空格隔开的整数Xi, Di, Li。分别表示第i次地壳变动的位置,方向和变动量。
【输出】
输出一共N行。第i行(1<=i<=N)为一个整数,表示点(i-1, 0)和点(i, 0)之间的地表底层是NOIP文明毁灭前多少年的地层。
【数据范围】
30%的数据满足:N<=100,Q<=100,|Xi|<=100,Li=1;
70%的数据满足:N<=3 000,Q<=3 000;
100%的数据满足:1 ≦ N ≦ 200 000,1 ≦ Q ≦ 200 000, |Xi |≦ 1 000 000 000,
1 ≦ Di ≦ 2 ,1 ≦ Li ≦ 1 000 000 000 (1 ≦ i ≦ Q)。
题目特别神!!
真的特别神!!
30pts可以直接模拟。把地形拆成若干小块。
发现,风干的话,就把我们地表出保留的信息全部删除了。太浪费。
由于最后只要知道地表的信息。
我们时光倒流。
把上升改为沉降。
开始是一条长度为N的线段。
随着沉降会分成若干段。
每一个点的深度,就是最后地表这个位置的层号。
这个怎么维护呢?
四十五度实在不行。
考虑翻转坐标系。
其实 就是,原来的(x,y)->(x-y,x+y)=(x',y')
那么,现在,斜率为一的操作,就是竖劈一刀。
斜率为-1的操作,就是横劈一刀。
①操作1
新的纵坐标y'>xi的点都会往右走2*L
横坐标+2*L
纵坐标不变。
②操作2
新的横坐标x'<=xi的点都会往下走2*L
纵坐标-2*L
横坐标不变。
发现,无论怎么操作,由于是一个后缀前缀的移动,每个点的横坐标、纵坐标的相对大小都不变。
所以像是一个区间!!
可以用两棵线段树。一棵维护横坐标,一棵维护纵坐标。
后缀前缀,就要找到第一个>xi的位置,第一个<=xi的位置。
直接二分即可。
最后,两棵线段树dfs一遍。
要还原真实的横坐标。y=(y'-x')/2
然后,y=-y才是真正的层数(越往下号越大)
代码:
#include<bits/stdc++.h> #define mid ((l+r)>>1) using namespace std; typedef long long ll; const int N=200000+5; const int inf=0x3f3f3f3f; int n,m; struct node{ int xi,di,L; }q[N]; struct tree{ ll mx,ad; #define tr t[c] }t[3][4*N]; void pushup(int c,int x){ tr[x].mx=max(tr[x<<1].mx,tr[x<<1|1].mx); } ll p[3][N]; void build(int c,int x,int l,int r){ if(l==r){ tr[x].mx=l; tr[x].ad=0; return; } build(c,x<<1,l,mid); build(c,x<<1|1,mid+1,r); pushup(c,x); } void pushdown(int c,int x){ if(!tr[x].ad) return; tr[x<<1].ad+=tr[x].ad; tr[x<<1].mx+=tr[x].ad; tr[x<<1|1].ad+=tr[x].ad; tr[x<<1|1].mx+=tr[x].ad; tr[x].ad=0; } int pos; void find1(int x,int l,int r,int k){//t[1] if(l==r){ if(t[1][x].mx<=k) pos=max(pos,l); return; } pushdown(1,x); if(t[1][x<<1].mx<=k) { pos=max(pos,mid);find1(x<<1|1,mid+1,r,k); } else { find1(x<<1,l,mid,k); } } void find2(int x,int l,int r,int k){//t[2] if(l==r){ if(t[2][x].mx>k) pos=min(pos,l); return; } pushdown(2,x); if(t[2][x<<1].mx<=k){ find2(x<<1|1,mid+1,r,k); } else { find2(x<<1,l,mid,k); } } void add(int c,int x,int l,int r,int L,int R,int val){ if(L<=l&&r<=R){ tr[x].mx+=val; tr[x].ad+=val; return; } pushdown(c,x); if(L<=mid) add(c,x<<1,l,mid,L,R,val); if(mid<R) add(c,x<<1|1,mid+1,r,L,R,val); pushup(c,x); } void dfs(int c,int x,int l,int r){ if(l==r){ p[c][l]=tr[x].mx; return; } pushdown(c,x); dfs(c,x<<1,l,mid); dfs(c,x<<1|1,mid+1,r); } int main(){ scanf("%d%d",&n,&m); //int pos,dir,len; for(int i=1;i<=m;i++){ scanf("%d%d%d",&q[i].xi,&q[i].di,&q[i].L); } build(1,1,1,n); build(2,1,1,n); pos=0;//warning!! ! ! ! ! ! ! ! for(int i=m;i>=1;i--){ if(q[i].di==1){ pos=0; find1(1,1,n,q[i].xi); if(pos)add(2,1,1,n,1,pos,-2*q[i].L); } else{ pos=inf; find2(1,1,n,q[i].xi); if(pos!=inf)add(1,1,1,n,pos,n,2*q[i].L); } } dfs(1,1,1,n); dfs(2,1,1,n); for(int i=1;i<=n;i++){ ll ans=-(p[2][i]-p[1][i])/2; printf("%lld\n",ans); } return 0; }
总结:
这个题真的是神题。
放进省选组也可以。
本身题目很抽象,倾斜直线很难处理。所以倾斜坐标系,变成横平竖直的直线。
本质上是(x,y)->(x-y,x+y)最后还原真实坐标即可。
这个技巧要注意!!
然后,由于向上升很麻烦,而且会损失很多存储的信息。我们就让它下沉。
而最后的位置恰好就是层数。
时光倒流。
线段树还是比较自然的。