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

题目背景

XLk觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。

题目描述

"第一分钟,X说,要有数列,于是便给定了一个正整数数列。

第二分钟,L说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。

第三分钟,k说,要能查询,于是便有了求一段数的和的操作。

第四分钟,彩虹喵说,要是noip难度,于是便有了数据范围。

第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。

第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过64位有符号整数类型的表示范围的限制。

第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"

——《上帝造题的七分钟·第二部》

所以这个神圣的任务就交给你了。

输入输出格式

输入格式:

第一行一个整数 nn ,代表数列中数的个数。

第二行 nn 个正整数,表示初始状态下数列中的数。

第三行一个整数 mm ,表示有 mm 次操作。

接下来 mm 行每行三个整数k,l,r

  • k=0表示给 [l,r][l,r] 中的每个数开平方(下取整)
  • k=1表示询问 [l,r][l,r] 中各个数的和。

数据中有可能 l>rl>r ,所以遇到这种情况请交换l和r

输出格式:

对于询问操作,每行输出一个回答。

输入输出样例

输入样例#1:  复制
10
1 2 3 4 5 6 7 8 9 10
5
0 1 10
1 1 10
1 1 5
0 5 8
1 4 8
输出样例#1:  复制
19
7
6









说明

对于30%的数据, 1\le n,m\le 10001n,m1000 ,数列中的数不超过 3276732767 。

对于100%的数据, 1 \le n,m \le 1000001n,m100000 , 1 \le l,r \le n1l,rn ,数列中的数大于 00 ,且不超过 10^{12}1012 。

注意l有可能大于r,遇到这种情况请交换l,r。




Solution:

    一看到题目中的区间修改和区间查询就想到了线段树,再看修改:区间里的每个数都开方下取整和区间和有什么关系吗?然后就在那死推公式,然而并没有推出什么来。后来发现这句话:数列中的数大于 00 ,且不超过 10^{12}1012 。每一个数都不超过 10^{12}1012

 ,就是说每一个数在开过至多6次方之后必定会变为1!!

    那也就是说只要对不全为1的区间暴力开方,问题便迎刃而解了。时间复杂度O(n logn),只是常熟略大而已。


上代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int size=100010;

struct SegmentTree
{
    ll l,r,sum;
    bool one;
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define sum(x) tree[x].sum
    #define one(x) tree[x].one
} tree[size*4];
ll a[size];

void build(int p,int l,int r)
{
//	printf("%d %d %d\n",p,l,r);
    l(p)=l,r(p)=r;
    if(l==r)
    {
        sum(p)=a[l];
        if(sum(p)==1) one(p)=true;
        else one(p)=false;
        return ;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    sum(p)=sum(p<<1)+sum(p<<1|1);
    one(p)=one(p<<1)&&one(p<<1|1);
    return ;
}

void do_sqrt(int p,int l,int r)
{
//	printf("%d %d %d\n",p,l(p),r(p));
    if(one(p)) return ;
    if(l(p)==r(p))
    {
        sum(p)=(ll)sqrt(sum(p));
        if(sum(p)==1) one(p)=true;
        return ;
    }
    int mid=(l(p)+r(p))>>1;
    if(l<=mid) do_sqrt(p<<1,l,r);
    if(r>mid) do_sqrt(p<<1|1,l,r);
    sum(p)=sum(p<<1)+sum(p<<1|1);
    one(p)=one(p<<1)&&one(p<<1|1);
    return ;
}

ll ask(int p,int l,int r)
{
    if(l<=l(p)&&r(p)<=r) return sum(p);
    int mid=(l(p)+r(p))>>1;
    ll value=0;
    if(l<=mid) value+=ask(p<<1,l,r);
    if(r>mid) value+=ask(p<<1|1,l,r);
    return value;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    build(1,1,n);
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(b>c)
        {
            int t=b;
            b=c,c=t;
        }
        if(a==0) do_sqrt(1,b,c);
//		printf("%d %d %d\n",a,b,c);
        if(a==1) printf("%lld\n",ask(1,b,c));
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值