CF121E 题解
题目链接。
大体思路:
首先看到这题,很明显是一道数据结构,再一看,很明显的线段树,维护两个操作,区间查询,区间修改,可以维护两个值,一个是加上一个数后的本身的值,一个是是否满足是幸运数字,怎么判断幸运数字呢,当然可以打表了,暴力打表之后欣喜的发现只有 30 个幸运数,具体也很好实现。
但是,有没有更简单的做法呢,当然是树状数组啦。
但是要区间加怎么办,好办,暴力枚举要加的区间就好了。
Q:
为什么这样的复杂度是正确的?
A:
观察题面,不难得到一个性质。“保证所有数操作前后都不超过
这就意味着一个很重要的性质,每个数最多就只会被加
(注意,我这里把加小区间的时间忽略了,并且,add 操作可能会进行更多次,但是这也就意味着枚举的区间变小了,整体复杂度均摊下来,最坏
因此,这里用树状数组是可以很完美的冲过去的,并不存在什么数据水。
代码实现:
自认为马蜂刻毒。
#include <bits/stdc++.h>
using namespace std;
const int N=101010;
int n,m;
int check[100]={0,4,7,44,47,74,77,444,447,474,477,744,747,774,777,4444,4447,4474,4477,4744,4747,4774,4777,7444,7447,7474,7477,7744,7747,7774,7777};
int check2[N];
int tr[N];
int sum[N];
int a[N];
//常规
int lobit(int x){
return x&-x;
}
int query(int x){
int res=0;
while(x){
res+=tr[x];
x-=lobit(x);
}
return res;
}
void update(int x,int c){
while(x<=n){
tr[x]+=c;
x+=lobit(x);
}
}
//常规
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=30;i++){
check2[check[i]]=true;
}//预处理打表数组
for(int i=1;i<=n;i++){
cin>>a[i];
if(check2[a[i]]){
update(i,1);
}
}//如果是幸运数,就加上。
for(int i=1;i<=m;i++){
string op;
cin>>op;
if(op=="count"){
int l,r;
cin>>l>>r;
cout<<query(r)-query(l-1)<<endl;//直接求区间的前缀和之差就好了
}
else{
int l,r,d;
cin>>l>>r>>d;
for(int i=l;i<=r;i++){
int x=a[i]+d;
if(check2[a[i]]){//之前有的消去,加上一个数后原数不一定是幸运数
update(i,-1);
}
a[i]+=d;
if(check2[x]){//是就加上
update(i,1);
}
}
}
}
return 0;
}