[NOIP模拟赛]统计

【题目描述】
定义一个整数集合S,S中的元素满足在十进制表示下只有5和7,例如5、7、57、75。
对于一个有n个元素的数列{a}进行以下两种操作:
操作一:add l r v,表示对于数列中加上v。

操作二:query l r,表示询问数列中有多少个满足。


【输入格式】
第一行输入两个整数n、m,分别表示数列长度和操作次数。
第二行输入n个整数,表示这个数列。

接下来的m行,每一行输入一个操作,操作格式如上。


【输出格式】

对于每一个询问操作输出一行一个整数。


【样例输入】
10 5
1 5 7 2 5 75 8 7 9 57
query 2 7
add 1 5 2
query 1 5
add 3 8 -1

query 1 10


【样例输出】
4
2

3


【数据范围】
n≤10^5,m≤10^5

对于所有数据保证任意时刻的ai∈[1,10000]


【提示】

注意常数因子对效率的影响。


【来源】

By tsy



题解:分块
令K表示在10000以内S集合的元素个数,经手数K=30。
把序列划分成长度为B的若干块。对于一个块,用cnt[i]表示i在每块中的个数,同时维护一个加法标记。
接下来考虑如何处理这两种操作:
Add l r:对于块内的元素,暴力修改,时间复杂度O(B)。对于连续的块,修改标记即可,时间复杂度O(n/B)。
Query l r v:对于块外的元素,标记下传暴力查询,时间复杂度O(B)。对于连续的块,假设已经打上了Δx的标记,就对于每一个L∈S,统计块中有多少个L-Δx,时间复杂度O(n*K/B)。

总时间复杂度:O(m*(B+n*K/B))。当B=sqrt(n*K)时,时间复杂度最小。


关于分块,这里写得很详细:http://blog.youkuaiyun.com/XianHaoMing/article/details/52201698


#include<iostream>
#include<cstdio>
#include<cmath>
const int T=1750;//T=sqrt( n*k )
const int N=1e5+10;
int n, m;
void Getin( int &num ){
	char c; int f=1; num=0;
	for( c=getchar(); c<'0' || c>'9'; c=getchar() ) if( c=='-' ) f=-1;
	for( ; c>='0'&&c<='9'; c=getchar() ) num=num*10+c-'0';
	num*=f;
}

bool flg[10005];
int sp[35]={ 0, 5, 7, 55, 57, 75, 77, 555, 557, 575, 577, 755, 757, 775, 777, 5555, 5557, 
5575, 5577, 5755, 5757, 5775, 5777, 7555, 7557, 7575, 7577, 7755, 7757, 7775, 7777 };
void Pre() { for( int i=1; i<=30; i++ ) flg[ sp[i] ]=1; }

int num[N];
struct node{ int area, p; }pos[N];
struct block{ int len, add, num[T], cnt[10005]; }B[T];

void Build() {
	int l=(int)sqrt( 30*n+0.5 ), cnt=1;
	for( int i=1; i<=n; i++ ) {
		if( B[cnt].len==l ) cnt++;
		B[cnt].num[ ++B[cnt].len ]=num[i];
		B[cnt].cnt[ num[i] ]++;
		pos[i].area=cnt;//记录数字在哪一块和在那一块的位置
		pos[i].p=B[cnt].len;
	}
}

void Change( int P ) {
	if( !B[P].add ) return;
	for( int i=1; i<=B[P].len; i++ ) {
		B[P].cnt[ B[P].num[i] ]--;
		B[P].num[i]+=B[P].add;
		B[P].cnt[ B[P].num[i] ]++;
	}
	B[P].add=0;
}

void Change( int l, int r, int P, int add ) {
	if( l==1 && r==B[P].len ) { B[P].add+=add; return; }
	Change( P );
	for( ; l<=r; l++ ) {
		B[P].cnt[ B[P].num[l] ]--;
		B[P].num[l]+=add;
		B[P].cnt[ B[P].num[l] ]++;
	}
}

void Add( int l, int r, int add ) {
	int L=pos[l].area, R=pos[r].area;
	if( L==R ) { Change( pos[l].p, pos[r].p, L, add ); return; }//同一块
	Change( pos[l].p, B[L].len , L, add ); Change( 1, pos[r].p, R, add );//不同块, 暴力处理最左右
	for( L++; L<R; L++ ) B[L].add+=add;//加上中间块
}

int Sum( int P ) {
	int ans=0;
	for( int i=1; i<=30; i++ )
		if( sp[i]-B[P].add>=0 && sp[i]-B[P].add<=10000 )
			ans+=B[P].cnt[ sp[i]-B[P].add ];
	return ans;
}

int Sum( int l, int r, int P ) {
	if( l==1 && r==B[P].len ) return Sum( P );
	Change( P );
	int ans=0;
	for( ; l<=r; l++ ) if( flg[ B[P].num[l] ] ) ans++;
	return ans;
}

int Query( int l, int r ) {
	int L=pos[l].area, R=pos[r].area;
	if( L==R ) return Sum( pos[l].p, pos[r].p, L );//同一块
	int ans=Sum( pos[l].p, B[L].len, L )+Sum( 1, pos[r].p, R );//不同块, 暴力处理最左右
	for( L++; L<R; L++ ) ans+=Sum( L );//加上中间块
	return ans;
}

int l, r, v;
char ord[10];
int main() {
	Pre(); Getin( n ); Getin( m );
	for( int i=1; i<=n; i++ ) Getin( num[i] );
	Build();
	for( int i=1; i<=m; i++ ) {
		scanf( "%s", ord ); Getin( l ); Getin( r );
		if( ord[0]=='a' ) Getin( v ), Add( l, r, v );
		else printf( "%d\n", Query( l, r ) );
	}
	return 0;
}


另附标程,除了跑得特别快,其他的和我的代码没有区别 :)

#include <iostream>
#include <cstdio>
#include <cmath>
#define MAXN 100005
using namespace std;
char word, s[10];
bool ff;
void GET(int &t) {
	t=0, ff=0;
	do{word=getchar();if(word=='-')ff=1;}while(word<'0'||word>'9');
	do{t=t*10+word-'0';word=getchar();}while(word>='0'&&word<='9');
	if(ff)t=-t;
}

int n, m, cnt, num[MAXN], pos[MAXN], pos2[MAXN];
const int a[35]={0,5,7,55,57,75,77,555,557,575,577,755,757,775,777,5555,5557,
5575,5577,5755,5757,5775,5777,7555,7557,7575,7577,7755,7757,7775,7777};
bool F[10005];

struct block {
	int num[1750], len, flag;
	int cnt[10005];
	inline void add(int a) {++cnt[a], num[++len]=a;}
	inline void putdown() {
	    if(flag) {
	        for(int i=1;i<=len;++i) {
				--cnt[num[i]];
				num[i]+=flag;
				++cnt[num[i]];
			}
            flag=0;
	    }
	}
	inline void modify(int l,int r,int v) {
	    if(l==1&&r==len){flag+=v;return;}
	    putdown();
	    for(int i=l;i<=r;++i) {
			--cnt[num[i]];
			num[i]+=v;
			++cnt[num[i]];
		}
	}
	inline int query() {
	    int ans=0;
	    for(int i=1;i<=30;++i)
            if( a[i]-flag<=10000 && a[i]-flag>=0 ) ans+=cnt[a[i]-flag];
        return ans;
	}
	inline int query(int l,int r) {
	    if(l==1&&r==len) return query();
	    putdown();
	    int ans=0;
	    for(int i=l;i<=r;++i) if(F[num[i]])++ans;
        return ans;
	}
}b[1750];

inline void build() {
	int l=(int)sqrt(n*26+0.5);
	cnt=1;
	for(int i=1;i<=n;++i) {
        if(b[cnt].len==l)++cnt;
        b[cnt].add(num[i]);
		pos[i]=cnt, pos2[i]=b[cnt].len;
    }
}

inline void add(int l,int r,int v) {
	int L=pos[l], R=pos[r];
    if(L==R) {
        b[L].modify(pos2[l],pos2[r],v);
        return;
    }
    for(int i=L+1;i<R;++i)b[i].flag+=v;
    b[L].modify(pos2[l],b[L].len,v), b[R].modify(1,pos2[r],v);
}

inline int query(int l,int r) {
    int L=pos[l], R=pos[r];
    if(L==R)return b[L].query(pos2[l],pos2[r]);
    int ans=b[L].query(pos2[l],b[L].len)+b[R].query(1,pos2[r]);
    for(int i=L+1;i<R;++i)ans+=b[i].query();
    return ans;
}

int main() {
    for(int i=1;i<=30;++i) F[a[i]]=1;
	GET(n), GET(m);
	for(int i=1;i<=n;++i) GET(num[i]);
	build();
	int l, r, v;
	while(m--) {
		scanf("%s",s), GET(l), GET(r);
		if(s[0]=='a') GET(v), add(l,r,v);
		else printf("%d\n",query(l,r));
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值