ABC462D 题解
题目描述
给定 n n n 个区间 l , r l,r l,r,求有多少个 s , i , j s,i,j s,i,j 满足 s , s + d s,s+d s,s+d 被 l i , r i ∩ l j , r j l_i,r_i\cap l_j,r_j li,ri∩lj,rj 包含,其中 1 ≤ i < j ≤ n 1\le i<j\le n 1≤i<j≤n。
数据范围请自己看题目。
思路
首先对所有区间进行排序是显然的。以 l l l 为第一关键字,以 r r r 为第二关键字从小到大排。
然后我们发现枚举两个区间的时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的,直接爆炸。
观察一下发现所有区间左右端点的取值范围才 10 6 10^6 106,所以考虑枚举开始作案的时间 s s s。
所以我们可以找到最后一个 满足 l k < s l_k<s lk<s 的区间 k k k,那么包含 s , s + d s,s+d s,s+d 的区间一定是从 1 ∼ k 1\sim k 1∼k 里面找。
给张图理解下。

由于我们的区间是按照 l l l 从小到大排序了,所以在 k k k 之前的区间的 l l l 一定也小于 s s s,也就是一定合法。
现在看怎么找 k k k。
然后注意到我们已经对所有区间排完序了,包含区间 s , s + d s,s+d s,s+d 的区间们在编号上一定也是个区间,且是整体的前缀。
而我们也是从小到大找 s s s 的,所以我们可以不断判断下一个区间的 l l l 是否小于 s s s,时间复杂度均摊 O ( 1 ) O(1) O(1),不懂看代码。
接下来在 1 ∼ k 1\sim k 1∼k 中的区间的左端点一定是合法的,所以只要看多少个右端点大等于 s + d s+d s+d 即可。这个可以用树状数组 O ( log n ) O(\log n) O(logn) 求。
假设有 S S S 个区间的右端点大等于 s + d s+d s+d,那么对答案的贡献就是 ( S 2 ) = S ( S − 1 ) 2 \binom{S}{2}=\frac{S(S-1)}{2} (2S)=2S(S−1)。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=2e5+5,M=1e6+5;
int n,cur;
ljl ans,mxr,trc[M],d;
int lowbit(int x){return x&(-x);}
ljl query(int x){
ljl ans=0;
for(;x>0;x-=lowbit(x))ans+=trc[x];
return ans;
}
ljl querylr(ljl l,ljl r){return query(r)-query(l-1);}
void add(int x,ljl val)
{
for(;x<=mxr;x+=lowbit(x))trc[x]+=val;
return;
}
struct NODE{
ljl l,r;
bool operator < (const NODE a)const{
if(l!=a.l)return l<a.l;
return r<a.r;
}
}node[N];
int main(){
ios::sync_with_stdio(0);
cin>>n>>d;
for(int i=1;i<=n;++i)
{
cin>>node[i].l>>node[i].r;
mxr=max(mxr,node[i].r);
}
sort(node+1,node+n+1);
for(ljl st=1;st+d<=mxr;++st)
{
while(cur<n&&node[cur+1].l<=st)//这里的cur最多只会增加n次,所以复杂度均摊O(1)
{
++cur;
add(node[cur].r,1ll);
// cout<<"------\n";
}
ljl sum=querylr(st+d,mxr);
// cout<<st<<": "<<sum<<'\n';
ans=ans+(sum*(sum-1)/2);
}
cout<<ans<<'\n';
return 0;
}