網上很多人說這是一道線段樹水題,但是我卻錯了一晚上,下面我來分析一下這道題做的時候會遇到的困惑
如果看到了這篇題解,我相信你已經看到其他題解中離散化的正確方法,這也是本題中最難的一個地方
但是我發現網上的題解並沒有詳細講述為什么這樣就能防止錯誤情況,對於初學者來說或許難以理解原理
1.首先為什么只有在兩個相鄰大於1的數之間插入一個值就能防止因為離散放縮后導致的區間覆蓋問題?
我們拿1 10 1 4 6 10這個常例來說
因為我們在離散化的時候只關心相對大小,所以這個6就是4之后的一個數,但是我們顯然知道這樣覆蓋了5這個區間
我們在相鄰大於1的數之間插入一個數,不管這個數是多大,只要他比前面的數大比后面的數小。我們都能夠在離散化后的區間中模擬出來真實區間的間隔,並且我們不關心這個間隙有多大
只需要存在間隙,我們就能模擬成真實的情況,因為離散后的區間一定會被間隔開,所以這個數可以是任何數,例如兩個數的平均值,或者是大數-1
而相鄰差1的數,他在真實的區間中本來也就是相鄰的,所以無需這種操作
2.為什么我們要對每個滿足情況的數都進行這樣的操作?就是為什么從頭遍歷?
其實我們只需要在真實的詢問之中插入數即可 比如在1 4 6 10之間插數,但是我們已經離散化了,並不知道哪個區間是我們真的要操作的詢問,並且我們發現,在不是題目中詢問的插數並不影響真實的答案
為了方便,我們都插一插,這樣肯定覆蓋了真實要插的區間。
3.為什么我總是runtime error?一個是很有可能你在詢問中判定左右節點相等的時候退出,一個是線段樹開小了,一個是你寫了一個int函數,但是在該返回值的時候忘記return了
如果上面三個都不是,那就是你程序寫的有問題
4.網上大部分都是數組來存,我習慣用結構體存儲咋辦?
那太好了,我這篇題解就是用了結構體存儲,下面我們邊看代碼邊理解其中的精妙之處

#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<vector> using namespace std; const int N=1e5+10; int ans[N]; int li[N],ri[N]; //存儲詢問 vector<int> num; struct node{ int l,r; int lazy; }tr[N<<2]; int n; void build(int u,int l,int r){ if(l==r){ tr[u]={l,r,0}; return ; } else{ tr[u]={l,r,0}; int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); } } void pushdown(int u){ //當我們的懶標記不是0,說明該段染色,所以往下傳遞 if(tr[u].lazy!=0){ tr[u<<1].lazy=tr[u<<1|1].lazy=tr[u].lazy; tr[u].lazy=0; } } void modify(int u,int l,int r,int x){ if(tr[u].l>=l&&tr[u].r<=r){ tr[u].lazy=x; return ; } pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid) modify(u<<1,l,r,x); if(r>mid) modify(u<<1|1,l,r,x); //按道理我們這有個pushup操作,但是本題並不需要根據兒子節點來更新父親節點也能做 } void query(int u,int l,int r){ //這個詢問和普通線段樹的詢問略有不同 if(tr[u].lazy){ // 如果懶標記存在,說明這個區間是單色的 ans[tr[u].lazy]=1; //將有被染色的顏色記錄下來 return ; } if(tr[u].l==tr[u].r) //一定要這個判斷條件,否則你沒有遞歸終止條件,就rt了 return ; //這與普通線段樹的格式有區別,但是原理相同 int mid=tr[u].l+tr[u].r>>1; pushdown(u); query(u<<1,l,r);//如果是0,有兩種情況,一個是沒有染色,一個是有多種顏色 query(u<<1|1,l,r); } void Solve(){ //離散化操作和插點操作 int i,x,y; sort(num.begin(),num.end()); num.erase(unique(num.begin(),num.end()),num.end()); int m=num.size(); for(i=0;i<m-1;++i) if(num[i+1]-num[i]>1) num.push_back((num[i+1]-1)); sort(num.begin(),num.end()); build(1,0,num.size()-1); for(i=1;i<=n;++i) { x=lower_bound(num.begin(),num.end(),li[i])-num.begin(); y=lower_bound(num.begin(),num.end(),ri[i])-num.begin(); modify(1,x,y,i); } int res=0; memset(ans,0,sizeof(ans)); query(1,0,num.size()-1); for(i=1;i<=n;++i) if(ans[i]) res++; printf("%d\n",res); } int main(){ int t,i; scanf("%d",&t); while(t--){ scanf("%d",&n); num.clear(); for(i=1;i<=n;++i){ scanf("%d %d",&li[i],&ri[i]); num.push_back(li[i]); num.push_back(ri[i]); } Solve(); } return 0; }

#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<vector> using namespace std; const int N=1e5+10; int ans[N]; int li[N],ri[N]; //存儲詢問 vector<int> num; struct node{ int l,r; int lazy; }tr[N<<2]; int n; void build(int u,int l,int r){ if(l==r){ tr[u]={l,r,0}; return ; } else{ tr[u]={l,r,0}; int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); } } void pushdown(int u){ //當我們的懶標記不是0,說明該段染色,所以往下傳遞 if(tr[u].lazy!=0){ tr[u<<1].lazy=tr[u<<1|1].lazy=tr[u].lazy; tr[u].lazy=0; } } void modify(int u,int l,int r,int x){ if(tr[u].l>=l&&tr[u].r<=r){ tr[u].lazy=x; return ; } pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid) modify(u<<1,l,r,x); if(r>mid) modify(u<<1|1,l,r,x); //按道理我們這有個pushup操作,但是本題並不需要根據兒子節點來更新父親節點也能做 } void query(int u,int l,int r){ //這個詢問和普通線段樹的詢問略有不同 if(tr[u].lazy){ // 如果懶標記存在,說明這個區間是單色的 ans[tr[u].lazy]=1; //將有被染色的顏色記錄下來 return ; } if(tr[u].l==tr[u].r) //一定要這個判斷條件,否則你沒有遞歸終止條件,就rt了 return ; //這與普通線段樹的格式有區別,但是原理相同 int mid=tr[u].l+tr[u].r>>1; pushdown(u); query(u<<1,l,r);//如果是0,有兩種情況,一個是沒有染色,一個是有多種顏色 query(u<<1|1,l,r); } void Solve(){ //離散化操作和插點操作 int i,x,y; sort(num.begin(),num.end()); num.erase(unique(num.begin(),num.end()),num.end()); int m=num.size(); for(i=0;i<m-1;++i) if(num[i+1]-num[i]>1) num.push_back((num[i+1]-1)); sort(num.begin(),num.end()); build(1,1,num.size()); for(i=1;i<=n;++i) { x=lower_bound(num.begin(),num.end(),li[i])-num.begin()+1; y=lower_bound(num.begin(),num.end(),ri[i])-num.begin()+1; modify(1,x,y,i); } int res=0; memset(ans,0,sizeof(ans)); query(1,1,num.size()); for(i=1;i<=n;++i) if(ans[i]) res++; printf("%d\n",res); } int main(){ int t,i; scanf("%d",&t); while(t--){ scanf("%d",&n); num.clear(); for(i=1;i<=n;++i){ scanf("%d %d",&li[i],&ri[i]); num.push_back(li[i]); num.push_back(ri[i]); } Solve(); } return 0; }