BZOJ3211 花神游历各国 区间开根号
花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线型的,也就是说,所有游历国家呈一条线的形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。
每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度Q变为sqrt(Q)(可能是花神虐爆了那些国家的OI,从而感到乏味)。
现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。
输入格式
第一行是一个整数N,表示有个国家;
第二行有N个空格隔开的整数,表示每个国家的初始喜欢度Qi;
第三行是一个整数M,表示有条信息要处理;
第四行到最后,每行三个整数x,l,r,当x=1时询问游历国家到的开心值总和,也就是x=2,当时国家到中每个国家的喜欢度q变为sqrt(q)。
输出格式
每次时,每行一个整数。表示这次旅行的开心度
input
4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4
output
101
11
11
对于全部数据
1<=N<=100000
1<=M<=2*100000
1<=l<=r<=n
0<=Qi<=1e9
注:建议使用 sqrt 函数,且向下取整.
Sol1:
看来对点修改,想到了树状数组。
然后这种成段修改,用Bit操作还是有点烦的,但是因为发现一个正整数
其实开不了几次根号,就会变得<=1,于是后面就不要再开根号了。
所以还是一个个去修改吧,但可以加的优化是:
我们可以利用并查集来快速找到某个数字(含它自身在内)右边第一个还可以
进行开根号操作的数字。
#include <cstring> #include <algorithm> #include <cstdio> #include <cmath> #include <cstdlib> using namespace std; typedef long long LL; const int N=100005; int n,m,fa[N],v[N],j; LL tree[N]; int lowbit(int x){ return x&-x; } void add(int x,int d){ for (;x<=n;x+=lowbit(x)) tree[x]+=d; } LL sum(int x){ LL ans=0; for (;x>0;x-=lowbit(x)) ans+=tree[x]; return ans; } int getf(int k){ return fa[k]==k?k:fa[k]=getf(fa[k]); } int main() { scanf("%d",&n); memset(tree,0,sizeof tree); for (int i=1;i<=n;i++) { scanf("%d",&v[i]); add(i,v[i]); fa[i]=i; } fa[n+1]=n+1; scanf("%d",&m); while (m--){ int op,a,b; scanf("%d%d%d",&op,&a,&b); if (op==1) printf("%lld\n",sum(b)-sum(a-1)); else { j=getf(a); while(j<=b) { int v_=int(sqrt(v[j])); add(j,v_-v[j]); v[j]=v_; if (v[j]<=1) { fa[j]=j+1; j=getf(j); } else j++; } } } return 0; }
Sol2:思路同上,利用线段树来进行单点修改。
#include <cstdio> #include <iostream> #include <cmath> #include <stack> #include <algorithm> #include <cstring> #include <climits> #define MAXN 2000000+10 #define LL long long using namespace std; LL n,m,a[MAXN],num[MAXN<<2],sit[MAXN]; int fa[MAXN]; int j; int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } void build(int rt,int l,int r) { if(l==r) { num[rt]=a[l]; sit[l]=rt; return ; } int m=(l+r)>>1; build(rt<<1,l,m); build(rt<<1|1,m+1,r); num[rt]=num[rt<<1]+num[rt<<1|1]; } void update(int x,int l,int r,int rt) { if(l==r&&l==x) { num[rt]=sqrt(num[rt]); return; } int m=(l+r)>>1; if(x<=m) update(x,l,m,rt<<1); else if(x>m) update(x,m+1,r,rt<<1|1); num[rt]=num[rt<<1]+num[rt<<1|1]; } LL query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) return num[rt]; int m=(l+r)>>1;LL ans=0; if(L<=m) ans+=query(L,R,l,m,rt<<1); if(R>m) ans+=query(L,R,m+1,r,rt<<1|1); return ans; } int main() { scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); fa[i]=i; } fa[n+1]=n+1; build(1,1,n); scanf("%lld",&m); for(int i=1;i<=m;i++) { int o,a,b; scanf("%d%d%d",&o,&a,&b); if(a>b) swap(a,b); if(o==2) { j=find(a); while(j<=b) { update(j,1,n,1); if(num[sit[j]]==1||num[sit[j]]==0) //如果已变成1或0了,则这个数字不要再进行开根号运算了 { fa[j]=j+1; j=find(j); } else //否则j就只能移动它后面的一个数字上 j++; } /* for(int j=find(a-1)+1;j<=find(b);) { update(j,1,n,1); if(num[sit[j]]==1||num[sit[j]]==0) fa[j-1]=j; j=find(j)+1; } */ }else printf("%lld\n",query(a,b,1,n,1)); } return 0; }
Sol3:使用线段树来进行成段更新
#include<stdio.h> #include<iostream> #include<algorithm> #include<math.h> using namespace std; #define p1 (p<<1) #define p2 (p<<1|1) const int N=100005; int n,m,i,p,x,y,a[N],add[N<<2]; long long ans,t[N<<2]; void build(int l,int r,int p) { if(l==r) { t[p]=a[l]; if(a[l]<=1) add[p]=1; else add[p]=0; return; } int mid=(l+r)>>1; build(l,mid,p1); build(mid+1,r,p2); t[p]=t[p1]+t[p2]; add[p]=add[p1]&add[p2]; } void update(int l,int r,int x,int y,int p) { if(add[p]) return; if(l==r) { t[p]=(int)(sqrt(t[p])); if(t[p]<=1) add[p]=1; return; } int mid=(l+r)>>1; if(x<=mid) update(l,mid,x,y,p1); if(y>mid) update(mid+1,r,x,y,p2); add[p]=add[p1]&add[p2]; t[p]=t[p1]+t[p2]; } void solve(int l,int r,int x,int y,int p) { if(x<=l&&r<=y) { ans+=t[p]; return; } int mid=(l+r)>>1; if(x<=mid) solve(l,mid,x,y,p1); if(y>mid) solve(mid+1,r,x,y,p2); } int main() { scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d%d",&p,&x,&y); if(p==1) { ans=0; solve(1,n,x,y,1); printf("%lld\n",ans); } else update(1,n,x,y,1); } return 0; }
Sol4:分块算法
#include<cmath> #include<cstdio> #include<algorithm> using namespace std; #define ll long long int n,k,opt,l,r,c,p[50010]; ll t[50010],w[50010];bool f[50010]; void Work(int now) { w[p[now]]-=t[now];//从总和中减去当前位置上的值 t[now]=sqrt(t[now]);//当前位置上的值开根号 w[p[now]]+=t[now];//再加回去 } void Calc(int now) { if(f[now])//如果当前位置已全为0了 return; f[now]=true;//假设这一块全为0了,这一句非常重要,因为如果不全为0,后面会改过去的 w[now]=0; for(int i=(now-1)*k+1;i<=now*k;i++) { t[i]=sqrt(t[i]); w[now]+=t[i]; if(t[i]>1) f[now]=false; } } void Change(int l,int r,int c) { if(p[l]==p[r]) for(int i=l;i<=r;i++) Work(i); else{ for(int i=l;i<=p[l]*k;i++) Work(i); for(int i=(p[r]-1)*k+1;i<=r;i++) Work(i); for(int i=p[l]+1;i<=p[r]-1;i++) Calc(i); } } ll Query(int l,int r) { ll ans=0; if(p[l]==p[r]) for(int i=l;i<=r;i++) //暴力统计 ans+=t[i]; else{ for(int i=l;i<=p[l]*k;i++) ans+=t[i]; for(int i=(p[r]-1)*k+1;i<=r;i++) ans+=t[i]; for(int i=p[l]+1;i<=p[r]-1;i++) ans+=w[i]; } return ans; } int main(){ scanf("%d",&n);k=sqrt(n); for(int i=1;i<=n;i++)p[i]=(i-1)/k+1; for(int i=1;i<=n;i++) scanf("%d",&t[i]),w[p[i]]+=t[i]; for(int i=1;i<=n;i++){ scanf("%d%d%d%d",&opt,&l,&r,&c); if(opt) printf("%lld\n",Query(l,r)); else //表示将位于[l,r]的之间的数字都开方 Change(l,r,c); } return 0; }