HDU 4578 Transformation(线段树+做了4天的神题+详解)

来源:互联网 发布:微博域名修改 编辑:程序博客网 时间:2024/05/24 06:03

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 53 3 5 71 2 4 44 1 5 22 2 5 84 3 5 30 0
Sample Output
3077489

题解:

我只能说这题很迷,迷之WA,这题我陆陆续续做了4天才AC,其中WA了3页,共计40多次吧,重写代码3次,用各种不同方法写。。最后无奈看着别人的博客照着一个大佬的写就AC了。。吐血,一开始我打算用3个v,3个tag来写,3个v表示区间的一二三次方值,tag分别表示加,乘等于的标记,先处理等,如果有等号标记,区间更新,清除加号和乘号标记,在处理乘和加之前看子区间有没有等号标记,有就先处理子区间的等号标记,然后处理乘,如果同时有加号标记把加的值乘上一个要乘的数,最后处理乘号标记,至于一次方二次方三次方公式是搞数学的老刘推的。。结果就是我样例过了,自己测的几组也过了还是WA,各种迷,怎么修改都是WA,后来看到一个大佬的没有推公式也没有储存三次方。。方法很神奇,而且速度比一般用储存3个值的那种快得多。。

说一下思路:

同样tag1表示加号标记,tag2表示乘,tag3表示等,无疑在向下更新的时候等号的优先级应该最高,先处理等号标记,并把加号和乘号标记置为0和1(初始值),然后重点来了,在处理乘号标记的时候,如果发现子区间有等号标记,直接修改等号标记,否则先将子区间pushdwon一下清空标记,再进行该子区间标记,加号同理,如果子区间有等号标记,就修改等号标记,否则将子区间pushdown清空标记,再将该子区间标记,所有操作完都要清空当前区间标记,然后修改的时候也差不多,先检查该区间有没有等号标记,有的话直接修改等号标记,否则pushdown清空该区间标记,再进行各种标记,查询的时候是整个算法的精髓,他是查询到要查询区间的子区间的等号标记不为初始标记,即为一段相同的数字的时候停止,然后返回该区间的值,进行各个子区间累加,得出答案。。。。一开始我还以为会超时,结果居然比一般的算法快得多,膜拜大佬

大佬博客:http://blog.csdn.net/cq_pf/article/details/47054667

代码:

#include<algorithm>#include<iostream>#include<cstring>#include<stdio.h>#include<math.h>#include<string>#include<stdio.h>#include<queue>#include<stack>#include<map>#include<deque>using namespace std;#define left k*2#define right k*2+1const int N=10007;struct node{    int l,r;    int tag1,tag2,tag3;//分别表示加号,乘号,等号标记}t[100005*4];int n;void Build(int l,int r,int k){    t[k].l=l;    t[k].r=r;    t[k].tag1=0;    t[k].tag2=1;    t[k].tag3=-1;    if(l==r)    {        t[k].tag3=0;//最底层要赋值为0        return;    };    int mid=(l+r)/2;    Build(l,mid,left);    Build(mid+1,r,right);}void pushdown(int k){    if(t[k].l==t[k].r)//没有子区间了不用退了        return;    if(t[k].tag3!=-1)//处理等号    {        t[left].tag3=t[right].tag3=t[k].tag3;//更新子区间等号标记        t[left].tag2=t[right].tag2=1;        t[left].tag1=t[right].tag1=0;//清空子区间加乘标记        t[k].tag3=-1;        return;    }    if(t[k].tag2!=1)//处理乘号    {        if(t[left].tag3!=-1)//如果子区间有等号标记,直接修改等号标记            t[left].tag3=(t[left].tag3*t[k].tag2)%N;        else//否则清空该子区间标记,进行子区间标记        {            pushdown(left);            t[left].tag2=(t[left].tag2*t[k].tag2)%N;        }        if(t[right].tag3!=-1)//同理处理右区间            t[right].tag3=(t[right].tag3*t[k].tag2)%N;        else        {            pushdown(right);            t[right].tag2=(t[right].tag2*t[k].tag2)%N;        }        t[k].tag2=1;    }    if(t[k].tag1!=0)//处理加号标记,和上面同理处理    {        if(t[left].tag3!=-1)            t[left].tag3=(t[left].tag3+t[k].tag1)%N;        else        {            pushdown(left);            t[left].tag1=(t[left].tag1+t[k].tag1)%N;        }        if(t[right].tag3!=-1)            t[right].tag3=(t[right].tag3+t[k].tag1)%N;        else        {            pushdown(right);            t[right].tag1=(t[right].tag1+t[k].tag1)%N;        }        t[k].tag1=0;//记得还原    }}void update(int l,int r,int v,int d,int k){    if(t[k].l==l&&t[k].r==r)    {        if(d==1)        {            if(t[k].tag3!=-1)//如果有等号标记,就直接修改等号标记            {                t[k].tag3=(t[k].tag3+v%N)%N;            }            else            {                pushdown(k);//否则清空该区间,进行标记                t[k].tag1=(t[k].tag1+v%N)%N;            }        }        else if(d==2)//同理        {            if(t[k].tag3!=-1)            {                t[k].tag3=(t[k].tag3*v%N)%N;            }            else            {                pushdown(k);                t[k].tag2=(t[k].tag2*v%N)%N;            }        }        else        {            t[k].tag3=v%N;            t[k].tag1=0;            t[k].tag2=1;        }        return;    }    pushdown(k);//向下更新    int mid=(t[k].l+t[k].r)/2;    if(r<=mid)        update(l,r,v,d,left);    else if(l>mid)        update(l,r,v,d,right);    else    {        update(l,mid,v,d,left);        update(mid+1,r,v,d,right);    }}int query(int l,int r,int p,int k)//查询{    if(t[k].l>=l&&t[k].r<=r&&t[k].tag3!=-1)//查到是查询区间的子区间且一段全为相同的数    {        int temp=1;        for(int i=1;i<=p;i++)            temp=(temp*t[k].tag3)%N;        return ((t[k].r-t[k].l+1)%N*temp)%N;//注意要乘上长度    }    pushdown(k);    int mid=(t[k].l+t[k].r)/2;    if(r<=mid)        return query(l,r,p,left)%N;    else if(l>mid)        return query(l,r,p,right)%N;    else    {        return (query(l,mid,p,left)+query(mid+1,r,p,right))%N;    }}int main(){    int i,j,k,m,d,x,y,c;    while(scanf("%d%d",&n,&m)!=EOF)    {        if(n==0&&m==0)            break;        Build(1,n,1);        for(i=0;i<m;i++)        {            scanf("%d%d%d%d",&d,&x,&y,&c);            if(d>=1&&d<=3)            {                update(x,y,c,d,1);            }            else            {                printf("%d\n",query(x,y,c,1)%N);            }        }    }    return 0;}