P6146 [USACO20FEB]Help Yourself G(计数问题贡献法)
题目描述
在一个数轴上有 \(N\) 条线段,第 \(i\) 条线段覆盖了从 \(l_i\)到 \(r_i\) 的所有实数(包含 \(l_i 和 r_i\))。
定义若干条线段的并为一个包含了所有被至少一个线段覆盖的点的集合。
定义若干条线段的复杂度为这些线段的并形成的连通块的数目。
现在 Bessie 想要求出给定 NN 条线段的所有子集(共有 \(2^N\) 个)的复杂度之和对 \(10^9+7\) 取模的结果。
你也许猜到了,你需要帮 Bessie 解决这个问题。但不幸的是,你猜错了!在这道题中你就是 Bessie,而且没有人来帮助你。一切就靠你自己了!
输入格式
第一行一个整数 \(N\)(\(1 \leq N \leq 10^5\))。
接下来 \(N\) 行,每行两个整数 \(l_i,r_i\),描述一条线段。保证 \(1 \leq l_i \lt r_i \leq 2N\),且任意两个端点都不在同一位置上。
输出格式
输出所求答案对 \(10^9+7\)取模的结果。
输入输出样例
输入 #1复制
3
1 6
2 3
4 5
输出 #1复制
8
题解
这种子集的什么问题,可以看一看加入一条线段后,和之前的所有线段构成的新集合的答案,这样就可以递推了。
先将所有线段按左端点升序排序。
设 \(f_i\)表示前 \(i\) 条线段的所有子集的复杂度之和。
如果我们新添加了一条线段,复杂度会怎样变化呢?
不选这条线段。
这种情况下,复杂度没有变化,不包含这条线段的子集的复杂度仍然为 \(f_i\)。
选这条线段。
复杂度分两部分:
原来的复杂度(这部分不会因为新选一条线段而减少,因为线段已经按左端点排好顺序了)和新增加的复杂度(这条线段可能不与已有线段形成连通块)。
-
原来的复杂度仍然为 \(f_i\),而选这条线段可能会让部分子集的复杂度 +1。
-
如果之前的线段中有 \(x\)条线段不与当前线段相交,则选这 \(x\) 条线段的一个子集加上当前线段可以让复杂度在原来子集的复杂度基础上 +1。根据集合的知识,新增加的复杂度就是 \(2^x\)。
从而得到递推式:\(f_i=f_{i-1}+(f_{i-1}+2^x)=2f_{i-1}+2^x\)。
现在的问题就是计算 \(x\)。
容易看出,设第 \(i\) 条线段的左端点为 \(l_i\),右端点为 \(r_i\),则 \(x\) 等于右端点小于 \(r_i\) 的线段数量。
我们可以利用前缀和技巧来预处理所有 \(x\) 的值。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=201000;
struct segment{
int l,r;
}a[N];
int s[N];
int n;
long long f[N];
const int mod=1e9+7;
bool cmp(segment a,segment b)
{
return a.l<b.l;
}
long long poow(long long a,long long b)
{
int ans=1;
while(b)
{
if(b&1) ans=(long long) ans*a%mod;
a=(long long)a*a%mod;
b>>=1;
}
return ans%mod;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].l>>a[i].r;
s[a[i].r]++;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=2*n;i++)
{
s[i]=s[i-1]+s[i];
}
for(int i=1;i<=n;i++)
{
f[i]=(2*f[i-1]+poow(2,s[a[i].l-1]))%mod;
}
f[n]%=mod;
printf("%lld",f[n]);
return 0;
}