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

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

测试地址: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;
}
### 关于51Nod 3100 上台阶问题的C++解法 #### 题目解析 该题目通常涉及斐波那契数列的应用。假设每次可以走一步或者两步,那么到达第 \( n \) 层台阶的方法总数等于到达第 \( n-1 \) 层和第 \( n-2 \) 层方法数之和。 此逻辑可以通过动态规划来解决,并且为了防止数值过大,需要对结果取模操作(如 \( \% 100003 \)[^1])。以下是基于上述思路的一个高效实现: ```cpp #include <iostream> using namespace std; const int MOD = 100003; long long f[100010]; int main() { int n; cin >> n; // 初始化前两项 f[0] = 1; // 到达第0层有1种方式(不移动) f[1] = 1; // 到达第1层只有1种方式 // 动态规划计算f[i] for (int i = 2; i <= n; ++i) { f[i] = (f[i - 1] + f[i - 2]) % MOD; } cout << f[n] << endl; return 0; } ``` 以上代码通过数组 `f` 存储每层台阶的结果,利用循环逐步填充至目标层数 \( n \),并最终输出结果。 --- #### 时间复杂度分析 由于仅需一次线性遍历即可完成所有状态转移,时间复杂度为 \( O(n) \)。空间复杂度同样为 \( O(n) \),但如果优化存储,则可进一步降低到 \( O(1) \): ```cpp #include <iostream> using namespace std; const int MOD = 100003; int main() { int n; cin >> n; long long prev2 = 1, prev1 = 1, current; if (n == 0 || n == 1) { cout << 1 << endl; return 0; } for (int i = 2; i <= n; ++i) { current = (prev1 + prev2) % MOD; prev2 = prev1; prev1 = current; } cout << prev1 << endl; return 0; } ``` 在此版本中,只保留最近两个状态变量 (`prev1`, `prev2`) 来更新当前值,从而节省内存开销。 --- #### 输入输出说明 输入部分接受单个整数 \( n \),表示台阶数量;程序会返回从地面走到第 \( n \) 层的不同路径数目,结果经过指定模运算处理以适应大范围数据需求。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值