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

由于我们的区间是按照 \(l\) 从小到大排序了,所以在 \(k\) 之前的区间的 \(l\) 一定也小于 \(s\),也就是一定合法。
现在看怎么找 \(k\)。
然后注意到我们已经对所有区间排完序了,包含区间 \([s,s+d]\) 的区间们在编号上一定也是个区间,且是整体的前缀。
而我们也是从小到大找 \(s\) 的,所以我们可以不断判断下一个区间的 \(l\) 是否小于 \(s\),时间复杂度均摊 \(O(1)\),不懂看代码。
接下来在 \(1\sim k\) 中的区间的左端点一定是合法的,所以只要看多少个右端点大等于 \(s+d\) 即可。这个可以用树状数组 \(O(\log n)\) 求。
假设有 \(S\) 个区间的右端点大等于 \(s+d\),那么对答案的贡献就是 \(\binom{S}{2}=\frac{S(S-1)}{2}\)。
代码
#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;
}

浙公网安备 33010602011771号