高中OJ3494. 【NOIP2013模拟联考13】线段(segment)

题目描述

Description
数轴上有很多单位线段,一开始时所有单位线段的权值都是1。有两种操作,第一种操作将某一区间内的单位线段权值乘以w,第二种操作将某一区间内的单位线段权值取w次幂。并且你还需要回答一些询问,每个询问需要求出某一区间的单位线段权值之积。由于答案可能很大,你只需要求出答案 mod (10^9+7)的值。
说明:n个点只有n-1条线段。

Input
第一行一个整数n,表示操作数量。

接下来n行,每行第一个整数表示操作类型,0表示第一种操作,1表示第二种操作,2表示询问,如果第一个数是0或1,则接下来3个数,表示操作区间和w,否则接下来两个数,表示询问区间。
Output
对于每组询问,输出一行,表示所求答案。

Sample Input
7

0 0 2 3

1 1 3 2

2 1 3

0 0 3 2

1 1 3 2

2 1 3

2 0 3
Sample Output
9

1296

7776

分析

如果数据范围较小的话,可以直接用线段树搞。
维护每段区间幂的次数和乘的数,根据费马小定理,幂可以与 109+6 取模。

动态开点线段树

如果按照数据范围来开点,区间长度过大的情况下会炸。
但是询问的次数很少,所以其实很多节点都是无用的。
所以可以根据与操作有关的区间动态开点。

code

#include <iostream>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define mod 1000000007
#define pmod 1000000006
#define mx 2000000001
using namespace std;
int n,len,x,y,z,i,s;
long long tr[4000000][6];
long long ans;
//0leftson 1rightson 2data 3multiplicative 4power 5length

void newt(int t,int s)//动态开点
{
    len++;
    tr[t][s]=len;

    tr[len][2]=1;
    tr[len][3]=1;
    tr[len][4]=1;
    tr[len][5]=tr[t][5]>>1;

    if ((tr[t][5]&1) && (!s))
    tr[len][5]++;
}

long long qpower(long long a,int b)
{
    long long ans=1;

    a%=mod;
    while (b)
    {
        if (b&1) ans=ans*a%mod;
        b>>=1;
        a=a*a%mod;
    }

    return ans;
}

void down(int t)//下传标记
{
    if (!tr[t][0])
    newt(t,0);
    if (!tr[t][1])
    newt(t,1);

    if (tr[t][4]!=1)
    {
        tr[tr[t][0]][4]=(tr[tr[t][0]][4]*tr[t][4])%pmod;
        tr[tr[t][0]][3]=qpower(tr[tr[t][0]][3],tr[t][4]);
        tr[tr[t][1]][4]=(tr[tr[t][1]][4]*tr[t][4])%pmod;
        tr[tr[t][1]][3]=qpower(tr[tr[t][1]][3],tr[t][4]);

        tr[t][2]=qpower(tr[t][2],tr[t][4]);
        tr[t][4]=1;
    }

    if (tr[t][3]!=1)
    {
        tr[tr[t][0]][3]=(tr[tr[t][0]][3]*tr[t][3])%mod;
        tr[tr[t][1]][3]=(tr[tr[t][1]][3]*tr[t][3])%mod;

        tr[t][2]=(tr[t][2]*qpower(tr[t][3],tr[t][5]))%mod;
        tr[t][3]=1;
    }
}

void up(int t)
{
    tr[t][2]=(tr[tr[t][0]][2]*tr[tr[t][1]][2])%mod;
}

void change(int t,long long l,long long r,int x,int y,int s1,int s2)
{
    if (!tr[t][0])
    newt(t,0);
    if (!tr[t][1])
    newt(t,1);

    down(tr[t][0]);
    down(tr[t][1]);

    if ((x<=l) && (r<=y))
    {
        tr[t][3]=s1;
        tr[t][4]=s2;

        down(t);

        return;
    }

    long long mid=(l+r)/2;

    if (x<=mid)
    change(tr[t][0],l,mid,x,y,s1,s2);
    if (mid<y)
    change(tr[t][1],mid+1,r,x,y,s1,s2);

    up(t);
}

void find(int t,long long l,long long r,int x,int y)
{
    if (!tr[t][0])
    newt(t,0);
    if (!tr[t][1])
    newt(t,1);

    down(tr[t][0]);
    down(tr[t][1]);

    if ((x<=l) && (r<=y))
    {   
        ans=(ans*tr[t][2])%mod;

        return;
    }

    long long mid=(l+r)/2;

    if (x<=mid)
    find(tr[t][0],l,mid,x,y);
    if (mid<y)
    find(tr[t][1],mid+1,r,x,y);

    up(t);
}

int main()
{
    freopen("segment.in","r",stdin);
    freopen("segment.out","w",stdout);

    scanf("%d",&n);

    len=1;
    tr[1][2]=1;
    tr[1][3]=1;
    tr[1][4]=1;
    tr[1][5]=mx;

    fo(i,1,n)
    {
        scanf("%d%d%d",&s,&x,&y);

        if (s<2)
        scanf("%d",&z);

        x+=1000000001;//避免区间范围为负
        y+=1000000000;

        if (s==0)
        change(1,1,mx,x,y,z,1);
        else
        if (s==1)
        change(1,1,mx,x,y,1,z);
        else
        {
            ans=1;
            find(1,1,mx,x,y);

            printf("%d\n",ans);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值