2023牛客暑期多校训练营9 -I Non-Puzzle: Segment Pair
题目描述
( 1 ≤ n ≤ 5 ⋅ 1 0 5 ) , ( 1 ≤ l i ≤ r i ≤ 5 ⋅ 1 0 5 , 1 ≤ l i ′ ≤ r i ′ ≤ 5 ⋅ 1 0 5 ) (1\le n\le 5·10^5),(1\le l_i \le r_i \le5·10^5,1\le l_i^{'} \le r_i^{'} \le5·10^5) (1≤n≤5⋅105),(1≤li≤ri≤5⋅105,1≤li′≤ri′≤5⋅105)
解题思路
本题中有区间的计算同时n的大小也只有5000,所以我们可以想到差分。
一开始,会想到枚举每一对线段在继续查询,但显然这个复杂度不对,是
2
n
2^n
2n 所以我们另辟蹊径。
因为一对线段重叠,则统计的方案数
∗
2
*2
∗2所以可以先用差分标记读进来的两条线段,
c
[
l
]
+
+
,
c
[
r
+
1
]
−
−
c[l]++,c[r+1]--
c[l]++,c[r+1]−−如果右区间的左端点 在左区间的右端点 右侧则中间的这一段区间 它的贡献值为
∗
2
*2
∗2。同时有贡献的区域就是
[
+
1
]
个数
−
[
+
2
]
为
n
[+1]个数-[+2]为n
[+1]个数−[+2]为n的区间,加上
2
[
+
2
]
的点个数
2^{[+2]的点个数}
2[+2]的点个数。由于我们差分标记的是点而不是区间,所以一段区间内我们重复算了许多次,需要最后删除。
代码
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=5e5+4;
#define ll long long
ll q(int x,int r) 快速幂-其实也可以直接预处理
{
int res=1;
while(r)
{
if(r&1) res=1ll*res*x%mod;
x=1ll*x*x%mod;
r>>=1;
}
return res;
}
vector<pair<int,int>> ca[N],rep[N]; ca是点的差分,rep是需要删除的重复计算的差分(就是点的右端点左移
int n;
int main()
{
scanf("%d",&n);
int l1,r1,l2,r2;
int bg=N,la=0; 找到点大小的左右值域
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
bg=min(bg,min(l1,l2));
la=max(la,max(r1,r2));
ca[l1].push_back({1,0}); 差分
ca[l2].push_back({1,0});
ca[r1+1].push_back({-1,0});
ca[r2+1].push_back({-1,0});
int lf=max(l1,l2),rg=min(r1,r2); 如果有重叠部分
if(lf<=rg)
{
ca[lf].push_back({0,1}); 将 second 赋值
ca[rg+1].push_back({0,-1});
}
r1--,r2--; 右端点左移 ,只留下一个点
rep[l1].push_back({1,0});
rep[l2].push_back({1,0});
rep[r1+1].push_back({-1,0});
rep[r2+1].push_back({-1,0});
rg--;
if(lf<=rg)
{
rep[lf].push_back({0,1});
rep[rg+1].push_back({0,-1});
}
}
int cnt1=0,cnt2=0,cnt3=0,cnt4=0;
ll ans1=0,ans2=0;
for(int i=bg;i<=la;i++)
{
for(auto j:ca[i])
{
cnt1+=j.first; 有值的
cnt2+=j.second; 重叠部分
}
if(cnt1-cnt2==n) 若n对线段包含的
ans1=(ans1+q(2,cnt2))%mod;
for(auto j:rep[i])
{
cnt3+=j.first;
cnt4+=j.second;
}
if(cnt3-cnt4==n)
ans2=(ans2+q(2,cnt4))%mod;
}
printf("%lld",((ans1-ans2)%mod+mod)%mod);
return 0;
}