[P4145 上帝造题的七分钟 2 / 花神游历各国]题解

发布时间 2023-04-29 16:36:34作者: hewanying

P4145 上帝造题的七分钟 2 / 花神游历各国

题目描述

分析

一开始在思考有没有一个数学公式来处理每一个开方的操作
但发现数据的\(\le 10^{12}\)
那么最多开六次就变成1了(突破口)
这样每一个数的有用操作只有6次
其他就全部是1

很显然,我们可以去记录每一段是否全为1
再用线段树、分块或树状数组解决了

由于我才学了分块,所以就用分块吧

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=1e5+10;
int block,n,m,op,l,r,id[maxn];
ll a[maxn],tag[1005],sum[1005];

void ssqrt(int x){
  ll res=0;
  int L=(x-1)*block+1,R=x*block;
  bool flag=true;
  for(int i=L;i<=R;i++){
  	if(a[i]==1){res++;continue;}
  	a[i]=(ll)floor(sqrt(a[i]));
  	if(a[i]!=1) flag=false;
  	res+=a[i];
  }
  if(flag) tag[x]=1;
  sum[x]=res;
}

void change(int l,int r){
  //1.左散块
  for(int i=l;i<=min(r,id[l]*block);i++)
    sum[id[i]]-=a[i],a[i]=(ll)floor(sqrt(a[i])),sum[id[i]]+=a[i];
  if(id[l]==id[r]) return ; 
  //2.中间的整块
  for(int i=id[l]+1;i<=id[r]-1;i++)
    if(!tag[i]) ssqrt(i);
  //3.右散块 
  for(int i=(id[r]-1)*block+1;i<=r;i++)
    sum[id[i]]-=a[i],a[i]=(ll)floor(sqrt(a[i])),sum[id[i]]+=a[i];
}

ll query(int l,int r){
  ll res=0;
  //1.左散块
  for(int i=l;i<=min(r,id[l]*block);i++)
    res+=a[i];
  if(id[l]==id[r]) return res;
  //2.中间的整块
  for(int i=id[l]+1;i<=id[r]-1;i++) res+=sum[i];
  //3.右散块 
  for(int i=(id[r]-1)*block+1;i<=r;i++) res+=a[i];
  return res;
}

int main(){
  /*2023.4.29 hewanying P4145 上帝造题的七分钟 2 / 花神游历各国 分块*/ 
  scanf("%d",&n);
  block=(int)floor(sqrt(n));
  for(int i=1;i<=n;i++){
  	id[i]=(i-1)/block+1;
  	scanf("%lld",&a[i]);
  	sum[id[i]]+=a[i];
  }
  scanf("%d",&m);
  for(int i=1;i<=m;i++){
  	scanf("%d%d%d",&op,&l,&r);
  	if(l>r) swap(l,r);
  	if(op==0) change(l,r);
  	else printf("%lld\n",query(l,r));
  }
  return 0;
}

总结

1.对于那些看似没有公式或方法进行区间处理的,我们可以考虑计算每个值被有效操作的次数,一般情况这样的次数会很小,从而直接可以暴力枚举
2.区间数据结构对于每一个整块的操作一定是\(O(1)\)的,可以从这一点下手