考古研究——四十五度翻轉+時光倒流+線段樹二分
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=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)最後還原真實坐標即可。
這個技巧要註意!!
然後,由於向上升很麻煩,而且會損失很多存儲的信息。我們就讓它下沈。
而最後的位置恰好就是層數。
時光倒流。
線段樹還是比較自然的。
考古研究——四十五度翻轉+時光倒流+線段樹二分