CF1034D Intervals of Intervals
应该是第一道 *3500
将端点化为线段,以便于处理
先考虑 \([l,r]\) 的区间并怎么快速求出。
如果固定 \(r\),考虑维护数轴上每个单位线段最后一次被哪个区间覆盖,记为 \(lst\),则一个询问 \([l,r]\) 的贡献是所有 \(lst\) 不小于 \(l\) 的单位线段长度之和。用 set 维护覆盖情况,每次的更新情况是区间加(即在区间 \((lst,r]\) 加上大小为长度的贡献)。复杂度 \(\mathcal O(n\log n)\)
二分第 \(k\) 个区间的价值,考虑如何求出价值 \(\geq mid\) 的区间个数。
考虑到 \(\forall l'\leq l\leq r\leq r'\),\(v(l',r')\geq v(l,r)\),设 \(f(i)\) 表示以 \(i\) 为右端点时第一个价值 \(< mid\) 的位置,则 \(f(i)\) 单调不降。因此用双指针维护答案,用线段树算贡献,复杂度为 \(\mathcal O(n\log n\log k)\),不能通过。
考虑在二分前预处理出贡献。贡献形式为 \((l,r,v)\) 表示当询问区间 \([L,R]\) 满足 \(l \leq L\leq r,R\geq r\) 时,会对答案产生 \(v\) 的贡献。此时左端点限制仍然难以处理。
进一步差分为 \((l,r,v)\) 和 \((r+1,r,-v)\),此时重新定义贡献为当 \(L\geq l,R\geq r\) 时,询问加上 \(v\)。
由于双指针的性质 ,\(L,R\) 都单调不降,因此贡献一旦加入就不会被撤销。
若将将 \([L,R]\) 看作一个平面点 \((L,R)\),则其贡献为左下角点的权值和。
因此,可以按如下方法维护贡献:当 \(L\) 更新时,加入以新的 \(L\) 为横坐标,纵坐标小于等于 \(R\) 的贡献,对于 \(R\) 同理。
对于区间价值和,可以在 \(R\) 移动时加上修改的贡献,在 \(L\) 移动时直接将该区间的贡献加入即可。
这样,复杂度降为 \(\mathcal O(n\log k)\),可以通过。
一些细节:贡献等于 \(mid\) 的点可能有很多,不能直接将 \(\geq mid\) 的区间全部求和。
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <vector>
#include <set>
using namespace std;
char buf[1<<14],*p1=buf,*p2=buf;
#define GetC() ((p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<14,stdin),p1==p2)?EOF:*p1++)
struct Ios{}io;
template <typename _tp>
Ios &operator >>(Ios &in,_tp &x){
x=0;int w=0;char c=GetC();
for(;!isdigit(c);w|=c=='-',c=GetC());
for(;isdigit(c);x=x*10+(c^'0'),c=GetC());
if(w) x=-x;
return in;
}
const int N=3e5+5;
struct Interval{int l,r,lst;};
bool operator <(Interval a,Interval b){
if(a.l!=b.l) return a.l<b.l;
return a.r<b.r;
}
set<Interval> s;
using pii=pair<int,int>;
using ll=long long;
#define pb push_back
void split(int x){
if(x==0) return ;
auto pos=--s.upper_bound({x,(int)2e9,0});
if(pos->r==x) return ;
s.insert({pos->l,x,pos->lst});
s.insert({x+1,pos->r,pos->lst});
s.erase(pos);
}
vector<pii> tagL[N],tagR[N];
ll sum=0,cnt=0;
int n,k;
bool check(int mid){
sum=cnt=0;
ll cur=0,cur_sum=0;
for(int l=1,r=1;r<=n;++r){
for(auto tmp:tagR[r]){
if(tmp.first>l) break;
cur+=tmp.second;
cur_sum+=(ll)tmp.second*(l-tmp.first);
}
while(cur>=mid){
++l;cur_sum+=cur;
for(auto tmp:tagL[l]){
if(tmp.first>r) break;
cur+=tmp.second;
}
}
sum+=cur_sum;
cnt+=l-1;
}
return cnt>=k;
}
int main(){
io>>n>>k;
s.insert({1,(int)1e9,0});
for(int i=1;i<=n;++i){
int l,r;io>>l>>r;--r;
split(l-1),split(r);
while(1){
auto pos=s.lower_bound({l,0,0});
if(pos==s.end()||pos->l>r) break;
int lst=pos->lst,len=pos->r-pos->l+1;
tagL[lst+1].pb({i,len});
tagL[i+1].pb({i,-len});
tagR[i].pb({lst+1,len});
tagR[i].pb({i+1,-len});
s.erase(pos);
}
s.insert({l,r,i});
sort(tagR[i].begin(),tagR[i].end());
}
for(int i=1;i<=n;++i) sort(tagL[i].begin(),tagL[i].end());
int l=1,r=1e9;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) l=mid+1;
else r=mid-1;
}
check(r);
printf("%lld\n",sum+(k-cnt)*r);
return 0;
}