【51Nod1688】LYKMUL-线段树+乘法原理

本文介绍了一道使用线段树与乘法原理解决的问题,通过对区间交并集的贡献进行计算,采用数据结构维护操作,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试地址:LYKMUL
做法:本题需要用到线段树+乘法原理。
这题是清北学堂zhw出的,当时讲的时候觉得非常妙,于是写在这里。
可以看出,一个集合的贡献等于这个集合中区间交和并的乘积,它也可以写成:
ab1 ∑ a ∈ 交 ∑ b ∈ 并 1
我们考虑每对这样的 (a,b) ( a , b ) 对答案的贡献,不难看出,贡献就是使得 a a 在交集,同时b在并集的集合数量。我们怎么计算这个数量呢?
考虑枚举 a a ,并提取出覆盖a的区间,不难看出满足条件的集合只能从这些区间中选,再考虑此时的一个 b b ,假设它被k个覆盖 a a 的区间覆盖,总的覆盖a的区间数量是 tot t o t ,我们知道,如果 b b 在一个集合的并集中,那么必须在k个覆盖它的区间选择至少一个,这样的方案数是 2k1 2 k − 1 ,而剩下的区间就可以任意选了,方案数是 2totk 2 t o t − k ,根据乘法原理, (a,b) ( a , b ) 的总贡献就是 2totk(2k1)=2tot(12k) 2 t o t − k ( 2 k − 1 ) = 2 t o t ( 1 − 2 − k ) 。那么在 a a 固定时,所有(a,b)的总贡献是 2tot2nb=1(12k(b)) 2 t o t ∑ b = 1 2 n ( 1 − 2 − k ( b ) ) ,也就是 2tot(2n2nb=12k(b)) 2 t o t ( 2 n − ∑ b = 1 2 n 2 − k ( b ) )
看到这个式子,很快想到用数据结构维护里面的和式,因为涉及的操作只有区间乘和区间求和,显然可以用线段树维护。那么我们只需要将区间的左右端点放在一起排个序,求出每个区间出现和消失的时间,在枚举 a a 的同时求出总贡献和就行了,总的时间复杂度为O(nlogn)
以下是本人代码:

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值