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;
}

浙公网安备 33010602011771号