CF679E 题解

题意简述

我们称 42正整数次幂为“坏数”(正整数次幂,不包含 1),而其他的正整数都是“好数”。

给出长度为 n(1n105) 的序列 ai(1ai109),并进行 q(1q105) 次操作。操作共三种:

  1. 给定 x,求 ax 的值。

  2. 给定 l,r,将区间 al,al+1,,ar1,ar 全赋值为给定的 x(1x109)

  3. 给定 l,rx(1x109)。先将 al,al+1,,ar1,ar 全部加上 x,然后不断如此操作下去一直到 al,al+1,,ar1,ar 都是“好数”为止。

题目分析

显然操作三并不能不停操作下去(操作有限次之后一定能找到两个相邻的 42 的次幂使得 alar 都夹在其之间,证明此处不多赘述),这就表明可能涉及到的 42 的次幂也是有限的。具体来说,最大的 42 的次幂在 O(qx) 级别,也就是 1014 左右,不会超过 4294×1014。可以直接把 421,422,,429 即所谓“坏数”预处理一下。

考虑使用线段树维护 alar 之间每个 ai 到下一个“坏数”距离的最小值。操作二的区间赋值很简单,打懒标记简单处理,更新最小值直接 lower_bound 查找新的“坏数”计算就行(直接枚举也行);操作三则先实现区间加,然后不断区间加直到 a1an 距“坏数”距离最小值不为 0 即可。而单点查询,单凭一个数距“坏数”的距离不能确定其原始值,但是区间赋值标记可以解决这一问题。我们直接让 l=r 的结点的区间赋值标记记录原始值即可。

至于时间复杂度,根据刚刚的分析,每个位置只会被修改 O(log42qx) 次,所以总时间复杂度为 O((n+q)log2nlog42qx)

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,q,op,l,r;
long long pow42[12],a[100010],x;
struct node
{
int l,r;//左右端点。
long long mn,tag1,tag2;//mn:离下一个“坏数”距离的最小值;tag1 是区间加标记,tag2 是区间赋值标记。
}tr[400010];
template<typename T>void rd(T &x)
{
x=0;
char c=getchar();
for(;c>'9'||c<'0';c=getchar());
for(;c<='9'&&c>='0';c=getchar())
x=(x<<3)+(x<<1)+c-'0';
}//快读
template<typename T>void wt(T x)
{
if(x>=10)
wt(x/10);
putchar(x%10+'0');
}//快写
long long calc(long long x)
{
return (*lower_bound(pow42+1,pow42+11,x))-x;//距下一个“坏数”的距离
}
void pushup(int p)
{
tr[p].mn=min(tr[p<<1].mn,tr[p<<1|1].mn);
}//子结点更新父结点
void addtag1(int p,long long tag)
{
if(tr[p].tag2)//区间赋值过就加到标记上
{
tr[p].tag2+=tag;
tr[p].mn=calc(tr[p].tag2);
}
else//没区间赋值过就直接打标记
{
tr[p].mn-=tag;
tr[p].tag1+=tag;
}
}//区间加打标记
void addtag2(int p,long long tag)
{
tr[p].tag2=tag;
tr[p].mn=calc(tag);
tr[p].tag1=0;//区间赋值之后,之前的区间加都失效
}//区间赋值打标记
void pushdown(int p)
{
if(tr[p].tag1)
{
addtag1(p<<1,tr[p].tag1);
addtag1(p<<1|1,tr[p].tag1);//向左右子结点下传标记
tr[p].tag1=0;//清空标记
}
if(tr[p].tag2)
{
addtag2(p<<1,tr[p].tag2);
addtag2(p<<1|1,tr[p].tag2);//向左右子结点下传标记
tr[p].tag2=0;//清空标记
}
}
void build(int p,int l,int r)//建树
{
tr[p].l=l,tr[p].r=r;
if(l==r)
{
addtag2(p,a[l]);//为了方便询问,初始化就添上一个赋值标记
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void change1(int p,int l,int r,int x)//区间加
{
if(tr[p].l>=l&&tr[p].r<=r/*区间全包含*/&&(tr[p].tag2||tr[p].mn>=x)/*若被区间赋值过或者区间加操作完后,没有比原来的“坏数”大,那么就可以直接打标记*/)
{
addtag1(p,x);
return;
}
//否则就递归下去算
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l)
change1(p<<1,l,r,x);
if(mid<r)
change1(p<<1|1,l,r,x);
pushup(p);
}
void change2(int p,int l,int r,int x)//区间赋值
{
if(tr[p].l>=l&&tr[p].r<=r)
{
addtag2(p,x);
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l)
change2(p<<1,l,r,x);
if(mid<r)
change2(p<<1|1,l,r,x);
pushup(p);
}
long long query(int p,int x)//单点查询,也可以直接二分实现
{
if(tr[p].l==tr[p].r)
return tr[p].tag2;
int mid=tr[p].l+tr[p].r>>1;
pushdown(p);
if(mid>=x)
return query(p<<1,x);
else
return query(p<<1|1,x);
}
int main()
{
pow42[0]=1ll;
for(int i=1;i<=10;i++)
pow42[i]=42ll*pow42[i-1];//预处理“坏数”
rd(n),rd(q);
for(int i=1;i<=n;i++)
rd(a[i]);
build(1,1,n);
while(q--)
{
rd(op),rd(l);
switch(op)
{
case 1:
wt(query(1,l));
putchar('\n');
break;
case 2:
rd(r),rd(x);
change2(1,l,r,x);
break;
case 3:
rd(r),rd(x);
do
{
change1(1,l,r,x);
}while(tr[1].mn==0);//由于至少要操作一次,所以用 do-while。
}
}
return 0;
}
posted @   Hadtsti  阅读(20)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示