洛谷P1712 [NOI2016]區間 尺取法+線段樹+離散化

keep/發表於2020-09-09

洛谷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;
}

完結撒花


相關文章