K - Transformation (区间标记思想)

Yuanfang is puzzled with the question below:
There are n integers, a 1, a 2, …, a n. The initial values of them are 0. There are four kinds of operations.
Operation 1: Add c to each number between a x and a y inclusive. In other words, do transformation a k<---a k+c, k = x,x+1,…,y.
Operation 2: Multiply c to each number between a x and a y inclusive. In other words, do transformation a k<---a k×c, k = x,x+1,…,y.
Operation 3: Change the numbers between a x and a y to c, inclusive. In other words, do transformation a k<---c, k = x,x+1,…,y.
Operation 4: Get the sum of p power among the numbers between a x and a y inclusive. In other words, get the result of a x p+a x+1 p+…+a y p.
Yuanfang has no idea of how to do it. So he wants to ask you to help him.

Input

There are no more than 10 test cases.
For each case, the first line contains two numbers n and m, meaning that there are n integers and m operations. 1 <= n, m <= 100,000.
Each the following m lines contains an operation. Operation 1 to 3 is in this format: "1 x y c" or "2 x y c" or "3 x y c". Operation 4 is in this format: "4 x y p". (1 <= x <= y <= n, 1 <= c <= 10,000, 1 <= p <= 3)
The input ends with 0 0.

Output

For each operation 4, output a single integer in one line representing the result. The answer may be quite large. You just need to calculate the remainder of the answer when divided by 10007.

Sample Input

5 5
3 3 5 7
1 2 4 4
4 1 5 2
2 2 5 8
4 3 5 3
0 0

Sample Output

307
7489

题解:题意很好懂吧,就不赘述了;这个题主要用到了标记思想,否则会很麻烦的,而且还不一定能做出来~~

第一次做这样的题是死活也想不出来这样的思想。看到大神的博客后恍然大悟:

https://blog.csdn.net/qq_36386435/article/details/83280634

我们建立线段树的同时开个vis标记数组,如果该节点为1说明其所有子节点的值全部相同,否则不同。那么到查询的时候我们对于节点标记为1的并且在查询区间内的则可大大简化,由于该节点的所有子节点的值全部相同,则只需算出来一个的p次方,再乘以区间长度即可~~~其他的看代码的注释吧~~

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=100010;
const int mod=10007;
int n,m,x,y,c,q;
int tree[maxn<<2],vis[maxn<<2];//vis为标记数组,如果为1,说明此节点的所有子节点的值全部一样,否则不一样
void pushdown(int rt)//自上往下更新
{
    if(vis[rt])
    {
        vis[rt*2]=vis[rt*2+1]=1;//如果根节点标记为1,那么子节点一定也是标记为1
        vis[rt]=0;//这时取消对根节点的标记即可
        tree[rt*2]=tree[rt*2+1]=tree[rt];//根节点标记为1说明子节点的值全部一样,为左右孩子赋值
    }
}
void update(int q,int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&R>=r&&vis[rt])//如果该ĵ区间在查询区间范围内并且该根节点标记为1说明它以下的所有子节点值全部一样,对此根节点进行操作,之后pushdown即可
    {
        if(q==1)           //主函数里没有分类,这里分1到3类
            tree[rt]=(tree[rt]+c)%mod;
        else if(q==2)
            tree[rt]=(tree[rt]*c)%mod;
        else if(q==3)
            tree[rt]=c;
        return ;
    }
    pushdown(rt);   //下放
    int mid=(l+r)/2;
    if(L<=mid)  //左右区间就不多说了
        update(q,L,R,c,l,mid,rt*2);
    if(R>=mid+1)
        update(q,L,R,c,mid+1,r,rt*2+1);
    //进行一波更新以后,其对应的值已经发生改变,所以每次都得更新vis数组,而pushdown只是简简单单的下放
    if(!vis[rt*2]||!vis[rt*2+1])//如果子节点有假,那么该根节点必为假
        vis[rt]=0;
    else     //子节点全部标记为真
    {
        if(tree[rt*2]!=tree[rt*2+1])//如果左右孩子的值不一样,那么这个标记不能做到该根节点上去
            vis[rt]=0;
        else//如果能做到,就将该根节点标记为1,同时为根节点赋值
        {
            vis[rt]=1;
            tree[rt]=tree[rt*2];
        }
    }
}
int querysum(int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&R>=r&&vis[rt])//如果该区间在查找区间范围内,并且该根节点标记为1说明他下面的子节点全部值都一样,可简化如下
    {
        int ans=1;
        for(int i=1;i<=c;i++)
        {
            ans=ans*tree[rt]%mod;//求一个数的p次方
        }
        return ans*(r-l+1)%mod;//区间里有r-l+1个一样的值,所以只要求出来1个再乘以区间长度即可
    }
    pushdown(rt);
    int ans=0;
    int mid=(l+r)/2;
    if(L<=mid)          //左右递归查询就不多说了
        ans+=querysum(L,R,c,l,mid,rt*2);
    if(R>=mid+1)
        ans+=querysum(L,R,c,mid+1,r,rt*2+1);
    return ans%mod;
}
int main()
{
    while(cin>>n>>m)
    {
        if(n+m==0)
            break;
        memset(tree,0,sizeof(tree));//由于初始值全为0,那么直接清零即可,不用写建树函数了
        memset(vis,1,sizeof(vis));//初始化为1,说明所有节点的所有子节点全部相同(即全为0)
        for(int i=1;i<=m;i++)
        {
            cin>>q>>x>>y>>c;
            if(q<=3)
                update(q,x,y,c,1,n,1);//1大类更新,根据自己的喜好来选定参数值的表达方式即可
            else
            {
                int ans=querysum(x,y,c,1,n,1);
                cout<<ans<<endl;
            }
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值