线段树+优化(hdu 5828 hdu 4027)

    xiaoxiao2024-12-28  14

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5828

    题目意思:对于一组数据,只能进行三种操作:1 对区间[l,r]中的每个数据进行加value操作,2 对区间[l,r]中的每个数据进行开方向下取整操作 3 对于区间[l,r]中的数据进行求和操作

    题目思路:开根号操作很快,对于2^64 最多8次会变成1,所以会存在很大一些数据相等的情况,对于加法操作,只需要多用一个lazy进行维护,对于没有用到的底层,只要把加法保留到上一层即可,等到用到了在进行向下更新,没有用到一直累加在上一层,但注意每一次进行add插入之时要进行更新,免得出矛盾,对于sqrt见下注释

    代码

    #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cmath> #define N 100050 using namespace std; long long sum[N*4],minn[N*4],maxx[N*4],lazy[N*4];//lazy用于记录没有下放的add数据总和,只有需要使用时才下放 void push_up(int root)//每次下层数据更新后,更新当前层数据 { sum[root]=sum[root<<1]+sum[root<<1|1]; minn[root]=min(minn[root<<1],minn[root<<1|1]); maxx[root]=max(maxx[root<<1],maxx[root<<1|1]); } void push_down(int root,int length) //每次对之前没有继续下放的数据进行更新 { if(lazy[root])//用于更新add没有下放的数据 { lazy[root<<1]+=lazy[root]; lazy[root<<1|1]+=lazy[root]; sum[root<<1]+=lazy[root]*(length-length/2); sum[root<<1|1]+=lazy[root]*(length/2); maxx[root<<1]+=lazy[root]; maxx[root<<1|1]+=lazy[root]; minn[root<<1]+=lazy[root]; minn[root<<1|1]+=lazy[root]; lazy[root]=0; } if(minn[root]==maxx[root])//用于更新sqrt没有下放的数据 { minn[root<<1]=minn[root<<1|1]=maxx[root<<1]=maxx[root<<1|1]=minn[root]; sum[root<<1]=minn[root]*(length-length/2);sum[root<<1|1]=minn[root]*(length/2); } } void build(int root,int l,int r) //建树 { lazy[root]=0; if(l==r) { scanf("%lld",&sum[root]); minn[root]=sum[root]; maxx[root]=sum[root]; return; } int mid=(l+r)>>1; build(root<<1,l,mid); build(root<<1|1,mid+1,r); push_up(root); } void updateadd(int vl,int vr,int v,int root,int l,int r)//add操作 { if(vl<=l && r<=vr) { sum[root]+=(long long)v*(r-l+1); maxx[root]+=v; minn[root]+=v; lazy[root]+=v; return; } push_down(root,r-l+1); int mid=(l+r)>>1; if(vr>mid) { updateadd(vl,vr,v,root<<1|1,mid+1,r); } if(vl<=mid) { updateadd(vl,vr,v,root<<1,l,mid); } push_up(root); } void updatesqrt(int vl,int vr,int root,int l,int r)//sqrt操作 { if(vl<=l && r<=vr) { if((int)(sqrt(minn[root]))==(int)(sqrt(maxx[root]))) { minn[root]=maxx[root]=(int)sqrt(minn[root]); sum[root]=minn[root]*(r-l+1); } //只有极差为1的时候才会出现加一个值再开方会出现原序列 /* 比如2 3 2 3 2 3然后整体加6然后开根号,又会变回2 3 2 3 2 3相当于不会出现区间数字一样的情况了, 这样就相当于没剪枝,变成了n^2...后来发现只有极差为1的时候才会出现这种情况,极差>1的话是不可 能维持原来的序列的,那么就可以维护一个最大值和最小值,如果极差为1的话那么区间开根号其实相当 于区间减去了x-sqrt(x) */ else if(minn[root]+1==maxx[root]) { int k = maxx[root]-(int)sqrt(maxx[root]); updateadd(vl,vr,-k,root,l,r); } else { push_down(root,r-l+1); int mid = (l+r)>>1; if(vl<=mid)updatesqrt(vl,vr,root<<1,l,mid); if(vr>mid)updatesqrt(vl,vr,root<<1|1,mid+1,r); push_up(root); } return; } push_down(root,r-l+1); int mid=(l+r)>>1; if(vr>mid) { updatesqrt(vl,vr,root<<1|1,mid+1,r); } if(vl<=mid) { updatesqrt(vl,vr,root<<1,l,mid); } push_up(root); } long long query(int vl,int vr,int root,int l,int r) { if(vl<=l && r<=vr) { return sum[root]; } push_down(root,r-l+1); int mid=(l+r)>>1; long long ans=0; if(vl<=mid) { ans+=query(vl,vr,root<<1,l,mid); } if(vr>mid) { ans+=query(vl,vr,root<<1|1,mid+1,r); } return ans; } int main() { int t; int n,m; scanf("%d",&t); while(t--) { scanf("%d %d",&n,&m); build(1,1,n); for(int i=1;i<=m;i++) { int l,r,v,op; scanf("%d",&op); if(op==1) { scanf("%d %d %d",&l,&r,&v); updateadd(l,r,v,1,1,n); } if(op==2) { scanf("%d %d",&l,&r); updatesqrt(l,r,1,1,n); } if(op==3) { scanf("%d %d",&l,&r); printf("%lld\n",query(l,r,1,1,n)); } } } return 0; } 类似题目: http://acm.hdu.edu.cn/showproblem.php?pid=4027

    题目意思:就是上面的1,2操作,去掉了加法

    思路:就是上面的思路,代码也是直接上面改的,但要主要每次case结束要空一行,并且l r 不一定满足l<r

    代码:

    #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cmath> #define N 100050 using namespace std; long long sum[N*4],minn[N*4],maxx[N*4],lazy[N*4]; void push_up(int root) { sum[root]=sum[root<<1]+sum[root<<1|1]; minn[root]=min(minn[root<<1],minn[root<<1|1]); maxx[root]=max(maxx[root<<1],maxx[root<<1|1]); } void push_down(int root,int length) { if(lazy[root]) { lazy[root<<1]+=lazy[root]; lazy[root<<1|1]+=lazy[root]; sum[root<<1]+=lazy[root]*(length-length/2); sum[root<<1|1]+=lazy[root]*(length/2); maxx[root<<1]+=lazy[root]; maxx[root<<1|1]+=lazy[root]; minn[root<<1]+=lazy[root]; minn[root<<1|1]+=lazy[root]; lazy[root]=0; } if(minn[root]==maxx[root]) { minn[root<<1]=minn[root<<1|1]=maxx[root<<1]=maxx[root<<1|1]=minn[root]; sum[root<<1]=minn[root]*(length-length/2);sum[root<<1|1]=minn[root]*(length/2); } } void build(int root,int l,int r) { lazy[root]=0; if(l==r) { scanf("%lld",&sum[root]); minn[root]=sum[root]; maxx[root]=sum[root]; return; } int mid=(l+r)>>1; build(root<<1,l,mid); build(root<<1|1,mid+1,r); push_up(root); } void updateadd(int vl,int vr,int v,int root,int l,int r) { if(vl<=l && r<=vr) { sum[root]+=(long long)v*(r-l+1); maxx[root]+=v; minn[root]+=v; lazy[root]+=v; return; } push_down(root,r-l+1); int mid=(l+r)>>1; if(vr>mid) { updateadd(vl,vr,v,root<<1|1,mid+1,r); } if(vl<=mid) { updateadd(vl,vr,v,root<<1,l,mid); } push_up(root); } void updatesqrt(int vl,int vr,int root,int l,int r) { if(vl<=l && r<=vr) { if((int)(sqrt(minn[root]))==(int)(sqrt(maxx[root]))) { minn[root]=maxx[root]=(int)sqrt(minn[root]); sum[root]=minn[root]*(r-l+1); } //只有极差为1的时候才会出现加一个值再开方会出现原序列 /* 比如2 3 2 3 2 3然后整体加6然后开根号,又会变回2 3 2 3 2 3相当于不会出现区间数字一样的情况了, 这样就相当于没剪枝,变成了n^2...后来发现只有极差为1的时候才会出现这种情况,极差>1的话是不可 能维持原来的序列的,那么就可以维护一个最大值和最小值,如果极差为1的话那么区间开根号其实相当 于区间减去了x-sqrt(x) */ else if(minn[root]+1==maxx[root]) { int k = maxx[root]-(int)sqrt(maxx[root]); updateadd(vl,vr,-k,root,l,r); } else { push_down(root,r-l+1); int mid = (l+r)>>1; if(vl<=mid)updatesqrt(vl,vr,root<<1,l,mid); if(vr>mid)updatesqrt(vl,vr,root<<1|1,mid+1,r); push_up(root); } return; } push_down(root,r-l+1); int mid=(l+r)>>1; if(vr>mid) { updatesqrt(vl,vr,root<<1|1,mid+1,r); } if(vl<=mid) { updatesqrt(vl,vr,root<<1,l,mid); } push_up(root); } long long query(int vl,int vr,int root,int l,int r) { if(vl<=l && r<=vr) { return sum[root]; } push_down(root,r-l+1); int mid=(l+r)>>1; long long ans=0; if(vl<=mid) { ans+=query(vl,vr,root<<1,l,mid); } if(vr>mid) { ans+=query(vl,vr,root<<1|1,mid+1,r); } return ans; } int main() { int t=1; int n,m; while(scanf("%d",&n)!=EOF) { printf("Case #%d:\n",t++); build(1,1,n); scanf("%d",&m); for(int i=1;i<=m;i++) { int l,r,v,op; scanf("%d",&op); /*if(op==3) { scanf("%d %d %d",&l,&r,&v); updateadd(l,r,v,1,1,n); }*/ scanf("%d %d",&l,&r); if(l>r){swap(l,r);} if(op==0) { updatesqrt(l,r,1,1,n); } if(op==1) { printf("%lld\n",query(l,r,1,1,n)); } } printf("\n"); } return 0; }

    转载请注明原文地址: https://ju.6miu.com/read-1295066.html
    最新回复(0)