【51Nod1688】LYKMUL-线段树+乘法原理
测试地址:LYKMUL
做法:本题需要用到线段树+乘法原理。
这题是清北学堂zhw出的,当时讲的时候觉得非常妙,于是写在这里。
可以看出,一个集合的贡献等于这个集合中区间交和并的乘积,它也可以写成:
我们考虑每对这样的对答案的贡献,不难看出,贡献就是使得在交集,同时在并集的集合数量。我们怎么计算这个数量呢?
考虑枚举,并提取出覆盖的区间,不难看出满足条件的集合只能从这些区间中选,再考虑此时的一个,假设它被个覆盖的区间覆盖,总的覆盖的区间数量是,我们知道,如果在一个集合的并集中,那么必须在个覆盖它的区间选择至少一个,这样的方案数是,而剩下的区间就可以任意选了,方案数是,根据乘法原理,的总贡献就是。那么在固定时,所有的总贡献是,也就是。
看到这个式子,很快想到用数据结构维护里面的和式,因为涉及的操作只有区间乘和区间求和,显然可以用线段树维护。那么我们只需要将区间的左右端点放在一起排个序,求出每个区间出现和消失的时间,在枚举的同时求出总贡献和就行了,总的时间复杂度为。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
const ll inv=500000004;
int n,l[100010],r[100010],tot=0;
ll seg[800010],p[800010],ans;
struct oper
{
int id,type,pos;
}q[200010];
bool cmp(oper a,oper b)
{
return a.pos<b.pos;
}
void pushup(int no)
{
seg[no]=(seg[no<<1]+seg[no<<1|1])%mod;
}
void buildtree(int no,int l,int r)
{
p[no]=1;
if (l==r) {seg[no]=1;return;}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
void pushdown(int no)
{
if (p[no]!=1)
{
p[no<<1]=(p[no<<1]*p[no])%mod;
p[no<<1|1]=(p[no<<1|1]*p[no])%mod;
seg[no<<1]=(seg[no<<1]*p[no])%mod;
seg[no<<1|1]=(seg[no<<1|1]*p[no])%mod;
p[no]=1;
}
}
void modify(int no,int l,int r,int s,int t,ll x)
{
if (l>=s&&r<=t)
{
p[no]=(p[no]*x)%mod;
seg[no]=(seg[no]*x)%mod;
return;
}
int mid=(l+r)>>1;
pushdown(no);
if (s<=mid) modify(no<<1,l,mid,s,t,x);
if (t>mid) modify(no<<1|1,mid+1,r,s,t,x);
pushup(no);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&l[i],&r[i]);
q[++tot].id=i,q[tot].type=0,q[tot].pos=l[i];
q[++tot].id=i,q[tot].type=1,q[tot].pos=r[i]+1;
}
n<<=1;
buildtree(1,1,n);
sort(q+1,q+n+1,cmp);
int last=1;
ans=0;
ll now=1;
for(int i=1;i<=n;i++)
{
while(last<=n&&q[last].pos<=i)
{
int p=q[last].id;
if (!q[last].type) now=(now<<1)%mod,modify(1,1,n,l[p],r[p],inv);
else now=(now*inv)%mod,modify(1,1,n,l[p],r[p],2);
last++;
}
ans=((ans+now*(n-seg[1]))%mod+mod)%mod;
}
printf("%lld",ans);
return 0;
}