洛谷P1712 [NOI2016]區間 尺取法+線段樹+離散化
洛谷P1712 [NOI2016]區間
noi2016第一題(大概是簽到題吧,可我還是不會)
連結在這裡
題面可以看連結;
先看題意
這麼大的l,r,先來個離散化
很容易,我們可以想到一個結論
假設一個點被覆蓋次數大於m
我們將覆蓋這個點的區間升序排序;
則所選區間一定是排序後序列中的一個長度為m+1的連續子序列
證明很容易,取更遠的點會使最大值更大從而使差值最大
我們可以從這個結論出發,再觀察該題所求,符合尺取法的思路
我們考慮用尺取法求解
沒了解尺取法的讀者可以去自行了解一下
如何求解呢?
我們考慮將區間按權值大小升序排序
從小到大載入到數軸上,統計數軸上點被覆蓋的最大次數
當我們將一個區間載入後若被覆蓋的最大次數大於m則說明存在符合條件的點
我們區間最大上界已經確定,接著確定下界
將區間由加入順序向後刪除
當刪除一個區間後總體max的值要小於m
區間序列的下界便確定了,
目前便得到了有可能更新答案的區間序列的最大值和最小值
在此我對幾個點進行解釋
1 .首先我們為什麼要按權值排序,
原因便是我們一開始就證明過的性質
利用該性質我們可以得到可能更新答案的所有情況從而求解
2 .最大值與最小值之前的區間呢?不會影響答案嗎?
不會影響,我們關心的只是符合題意的區間最小值和最大值
只關注邊界,至於內部在所選序列中的區間具體是誰我們並不關心
3.我們在確定區間序列下界時將一些區間刪掉了
不會對結果有影響嗎?
事實上,我們刪掉的區間一定是對答案無貢獻的
證明很容易
我們刪除區間的大小一定小於目前正在尋找的下界
即使之後在加入某個大區間時這個區間產生了貢獻成為最小區間
但所加入的最大區間一定大於等於之前的上界
而該區間又小於之前的下界
所以差值一定大於先前的值,故不對最終答案貢獻,
有了這些思路後我們就可以做了
至於如何獲得當前數軸的最大覆蓋次數
和如何將區間加入數軸
我們維護一顆最大值的線段樹即可
注意因為我們採取了離散化
所以線段樹陣列的大小由4倍變為8倍
時間複雜度便是線段樹的時間複雜度了
顯然是可以過5e5資料的
#include<iostream> #include<cstring> #include<cstdio> #include<string> #include<algorithm> #define inf 0x3f3f3f3f using namespace std; const int maxn =5e5+1; int tree[maxn*8]; int add[maxn*8]; inline int read(){ int ret=0; int f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-'){ f=-f; } ch=getchar(); } while(ch>='0'&&ch<='9'){ ret=ret*10+(ch^'0'); ch=getchar(); } return f*ret; } int n,m; struct edge{ int val; int num; }e[maxn]; struct node{ int val; int num; }p[maxn*2]; int cnt; bool cmp(node x,node y){ return x.val<y.val; } bool cmp2(edge x,edge y){ return x.val<y.val; } int ln[maxn*2]; int rn[maxn*2]; void pushdown(int rt){ if(add[rt]){ int ls=rt*2; int rs=rt*2+1; tree[ls]+=add[rt]; tree[rs]+=add[rt]; add[ls]+=add[rt]; add[rs]+=add[rt]; add[rt]=0; } return ; } void update(int ro,int l,int r,int ls,int rs,int val){ if(rs<l||ls>r){ return ; } if(rs>=r&&ls<=l){ tree[ro]+=val; add[ro]+=val; return ; } int mid=(l+r)>>1; pushdown(ro); update(ro*2,l,mid,ls,rs,val); update(ro*2+1,mid+1,r,ls,rs,val); tree[ro]=max(tree[ro*2],tree[ro*2+1]);//維護區間最大點值 return ; } int main(){ // freopen("a.in","r",stdin); n=read(); m=read(); int l,r; for(int i=1;i<=n;i++){ l=read(); r=read(); // cout<<l<<" "<<r<<endl; e[i].num=i; e[i].val=r-l; cnt++; p[cnt].num=i; p[cnt].val=l; cnt++; p[cnt].num=i; p[cnt].val=r; } int num=0; sort(p+1,p+1+cnt,cmp); for(int i=1;i<=cnt;i++){ if(p[i].val!=p[i-1].val){ num++;//num為新建的權值,當相鄰值相等時,權值不變 } int u=p[i].num;//更新原本的L,與r; if(!ln[u]){ ln[u]=num; } else rn[u]=num; } sort(e+1,e+1+n,cmp2); int rig=num; int ri=0; int li=0; int ans=inf; /* 本題該部分採用了尺取法 當區間內沒有點被覆蓋次數大於m時,我們加入一個區間進去 如果加入此區間 //cout<<m; */ while(true){ while(tree[1]<m&&ri<n){//尺取法,當我們加入一個節點,如果此事有一點被覆蓋次數大於等於m,這個區間為合法最大值 ri++; int u=e[ri].num; int ls=ln[u]; int rs=rn[u]; update(1,1,rig,ls,rs,1); } if(tree[1]<m){ break;//我們即使將所有區間加入也無法大於m故放棄 } while(tree[1]>=m&&li<n){ li++; int u=e[li].num; int ls=ln[u]; int rs=rn[u]; update(1,1,rig,ls,rs,-1); }//尋找影響這個區間的最小值 ans=min(ans,e[ri].val-e[li].val);// 更新最小值 } if(ans==inf){ cout<<-1<<endl; return 0; } cout<<ans; return 0; }
完結撒花