1. 程式人生 > 其它 >Codeforces1034D Intervals of Intervals

Codeforces1034D Intervals of Intervals

Description

\(n\le 3\times 10^5\) 個區間 \([a_i,b_i]\)

定義區間的區間 \([l,r]\) 的價值是第 \(l\) 個區間到第 \(r\) 個區間的並的長度,找出 \(k\) 個不同的區間的區間,使得總價值最大。

Solution

二分第 \(K\) 大並區間的並的長度,由於 並集大小 在區間個數增加的同時是不降的,可以雙指標求出來每個右端點的最遠左端點

此時需要快速求解一組區間的並的長度,發現上面過程和掃描線是吻合的,那麼使用資料結構維護每個元素最後一次出現的位置,如果求 \([l,r]\) 的區間並集大小就是在資料結構上求有幾個元素的最後一次出現 \(\ge l\)

乍一看甚至需要樹套樹了,但是轉變成維護每個左端點的答案 \(ans_l\),配合 std::set 維護所有區間的最後一次出現位置 \(lst_i\),此時每次修改一段的 \(lst_i\) 就是對 \(ans\) 進行區間加

那麼挪動指標是單點查詢,這是平凡的

複雜度現在是 \(\Theta(n\log V\log^2n)\) 的,需要找到冗餘減少計算量

首先區間加法的形式在每次二分中是一樣的,可以提前處理;其次區間加法可以差分,如果當前指標比差分開始的位置大就在指標的位置加權值而在 \(i+1\) 處將增量撤銷即可

最後求答案需要解決形如 “權值 \(\ge x\) 的區間的權值之和” 這樣一個問題,仍然套用求個數的過程,維護當前所有合法左端點的區間並之和

挪動指標時新增差分陣列上的值,而在出現指標大於區間加的起始點的情況需要補加左端點個數個增量

總複雜度 \(\Theta(n\log V)\)

Code

const int N=3e5+10;
int n,K,l[N],r[N],val[N];
vector<pair<int,int> > incr[N];
set<tuple<int,int,int> > inter;
signed main(){
	n=read(); K=read();
	rep(i,1,n){
		int l=read(),r=read()-1;
		auto split=[&](const int x){
			auto iter=inter.lower_bound({x,0,0});
			if(iter!=inter.begin()){
				--iter;
				int l,r,id; 
				tie(l,r,id)=*iter;
				if(r>=x){
					inter.erase(iter);
					inter.insert({l,x-1,id});
					inter.insert({x,r,id});
				}
			}	
			return ;
		};
		split(l); split(r+1);
		auto iter=inter.lower_bound({l,0,0});
		int sumlen=0;
		while(1){
			if(iter==inter.end()) break;
			int L,R,id; tie(L,R,id)=*iter;
			if(L>r) break;
			incr[i].emplace_back(id+1,R-L+1);
			++iter;
			sumlen+=R-L+1;
			inter.erase(prev(iter));
		}
		if(sumlen<r-l+1){
			incr[i].emplace_back(1,r-l+1-sumlen);
		}
		inter.insert({l,r,i});
	}
	auto Num=[&](const int mid){
		int cnt=0,indic=0;
		rep(i,1,n) val[i]=0;
		for(int i=1;i<=n;++i){
			for(auto e:incr[i]){
				val[max(e.fir,indic)]+=e.sec;
				val[i+1]-=e.sec;
			}
			while(indic+1<=i&&val[indic+1]+val[indic]>=mid){
				++indic;
				val[indic]+=val[indic-1];
			}
			cnt+=indic;
		}
		return cnt;
	};
	auto Sum=[&](const int mid){
		int sum=0,cur=0,indic=0;
		rep(i,1,n) val[i]=0;
		for(int i=1;i<=n;++i){
			for(auto e:incr[i]){
				if(e.fir<=indic) cur+=e.sec*(indic-e.fir+1);
				val[max(e.fir,indic)]+=e.sec;
				val[i+1]-=e.sec;
			}
			while(indic+1<=i&&val[indic+1]+val[indic]>=mid){
				++indic;
				val[indic]+=val[indic-1];
				cur+=val[indic];
			}
			sum+=cur;
		}
		return sum;
	};
	int ans=0,l=0,r=1e9;
	while(l<=r){
		int mid=(l+r)>>1;
		if(Num(mid)>=K) ans=mid,l=mid+1;
		else r=mid-1;
	}
	print(Sum(ans+1)+(K-Num(ans+1))*ans);
	return 0;
}

題外話:這題注意到差分的左端點可以隨便調花了非常非常久的時間