题意:
lucky数只由4和7组成
给n个数,m个操作,操作有两种
1.区间内每个数加上d
2.询问区间lucky数有多少个
数据保证在执行完所有区间加操作后,每个数不会超过1e4!
思路1 线段树:直接暴力的话会T
看上去是区间修改,其实lucky数的修改只能落到单点上,所以值的修改和计数的修改,都是单点修改
①如果每修改一次都判断一下是不是lucky数,会T
②如果不管是不是lucky数(可能不用修改cnt),都logn地跑到叶节点,会T
优化这两个地方 3000ms/4000ms可过
具体来说,写出1e4内lucky数的表,每次O(1)地判断是不是lucky数,
线段树只维护计数cnt,每次修改直接在原数组上修改,当计数需要改变,再用线段树的单点修改。
#include<bits/stdc++.h>
using namespace std;
#define ls (p<<1)
#define rs (ls|1)
#define mid (t[p].l+t[p].r>>1)
const int N=1e5+5;
bool vis[N];
struct node{
int l,r,cnt;
}t[N<<2];//4倍空间了!
int a[N];
int lucky[]={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};
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r){
if(vis[a[l]]) t[p].cnt=1;
return;//return!
}
build(ls,l,mid);
build(rs,mid+1,r);
t[p].cnt=t[ls].cnt+t[rs].cnt;//pushup(p);
}
void update(int p,int x,int d){
if(t[p].l==t[p].r){
t[p].cnt+=d;
return;
}
if(x<=mid) update(ls,x,d);// l<=mid
else update(rs,x,d); //区间没有else 单点修改是else
t[p].cnt=t[ls].cnt+t[rs].cnt;
}
int query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].cnt;
int res=0;
if(l<=mid) res+=query(ls,l,r);//天呐这里打错了 mid!
if(r>mid) res+=query(rs,l,r);
return res;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<30;++i) vis[lucky[i]]=true;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
build(1,1,n);
char op[10];
while(m--){
scanf("%s",&op);
int l,r;
if(op[0]=='a'){
int d;
scanf("%d%d%d",&l,&r,&d);
for(int i=l;i<=r;++i){
if(!vis[a[i]]&&vis[a[i]+d]) update(1,i,1);
else if(vis[a[i]]&&!vis[a[i]+d]) update(1,i,-1);
a[i]+=d;
}
}else{
scanf("%d%d",&l,&r);
cout<<query(1,l,r)<<"\n";
}
}
}
这就是我线段树的板子了!!!
法二 树状数组:树状数组不加上面的第二个优化也能过 树状数组比线段树常数更小 2000ms内可过
为什么我没想到这题能用树状数组呢?
树状数组是统计前缀和
这里的cnt就是可以被统计的前缀和
凡是用线段树维护的sum
都可用树状数组维护
是这样?
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x)&(-x))
const int N=1e5+5;
bool vis[N];
int a[N],c[N];
int lucky[]={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};
//返回前x个整数之和
int getsum(int x){
int sum=0;
for(int i=x;i>0;i-=lowbit(i)){//x>0
sum+=c[i];
}
return sum;
}
//第x个整数加上v
void update(int x,int v){
for(int i=x;i<=N;i+=lowbit(i)){
c[i]+=v;
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<30;++i) vis[lucky[i]]=true;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
if(vis[a[i]]) update(i,1);
}
char op[10];
while(m--){
scanf("%s",&op);
int l,r;
if(op[0]=='a'){
int d;
scanf("%d%d%d",&l,&r,&d);
for(int i=l;i<=r;++i){
if(!vis[a[i]]&&vis[a[i]+d]) update(i,1);
else if(vis[a[i]]&&!vis[a[i]+d]) update(i,-1);
a[i]+=d;
}
}else{
scanf("%d%d",&l,&r);
cout<<getsum(r)-getsum(l-1)<<"\n";
}
}
}