[bzoj4542][Hnoi2016]大数——同余+莫队

题目大意:

给定一个质数p和一串数字序列,每次询问一个区间[L,R]中有多少个子区间表示的数为p的倍数。

思路:

首先考虑如何判断一段数字是不是p的倍数,不难想到可以用模p意义下的值来判断,但是这样最多便有可能会有 n 2 n^2 n2个余数,每一次计算也需要区间长度的时间,不太方便。
考虑记录以每一个点为起点的后缀所表示的数字在模p下的结果 s u m i sum_i sumi,对于任意一段区间[L,R],不难发现 s u m l − s u m r + 1 sum_l-sum_{r+1} sumlsumr+1所表示的是[L,R]所表示的数 × 1 0 x \times 10^x ×10x,对于素数里面只有2,5是有可能整除后面的 1 0 x 10^x 10x,于是我们只需要对2,5特殊判断一下,其他的素数直接用 s u m l − s u m r + 1 sum_l-sum_{r+1} sumlsumr+1对于p取模的结果来判断就好了。
这样对于任意一个区间[L,R],我们只需要看 s u m l sum_l suml s u m r + 1 sum_{r+1} sumr+1是否相同,题目便转化为了数一个区间内的相同颜色的个数并求其贡献,这种模型直接用莫队维护即可。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define MREP(i,x) for(int i=beg[x],v;v=to[i],i;i=las[i])
#define debug(x) cout<<#x<<"="<<x<<endl
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj4542.in","r",stdin);
	freopen("bzoj4542.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e5+10;
int n,p,m,a[maxn];
char str[maxn];

namespace subtask1{
	ll s1[maxn],s2[maxn];
	void work(){
		REP(i,1,n){
			s1[i]+=s1[i-1];
			s2[i]+=s2[i-1];
			if(a[i]%p==0){
				++s1[i];
				s2[i]+=i;
			}
		}
		int l,r;
		REP(i,1,m){
			read(l),read(r);
			printf("%lld\n",s2[r]-s2[l-1]-(s1[r]-s1[l-1])*(l-1));
		}
	}
}

namespace subtask2{
	int tot,bel[maxn];
	ll sum[maxn],p10[maxn],b[maxn],ans[maxn],ton[maxn],now;
	struct Query{
		int l,r,id;
		bool operator < (const Query & tt) const {
			if(bel[l]==bel[tt.l])return r<tt.r;
			return bel[l]<bel[tt.l];
		}
	}qu[maxn];
	void calc(int pos,int ty){
		int w=sum[pos];
		now-=ton[w]*(ton[w]-1)/2;
		ton[w]+=ty;
		now+=ton[w]*(ton[w]-1)/2;
	}
	void work(){
		p10[0]=1;
		REP(i,1,n)p10[i]=p10[i-1]*10%p;
		ll ss=0;
		DREP(i,n,1){
			ss=(ss+a[i]*p10[n-i])%p;
			sum[i]=(ss+p)%p;
		}

		REP(i,1,n+1)b[++tot]=sum[i];
		sort(b+1,b+tot+1);
		tot=unique(b+1,b+tot+1)-b-1;
		REP(i,1,n+1)sum[i]=lower_bound(b+1,b+tot+1,sum[i])-b;

		REP(i,1,n)bel[i]=(i-1)/400+1;
		REP(i,1,m)read(qu[i].l),read(qu[i].r),qu[i].id=i;
		sort(qu+1,qu+m+1);

		int L=1,R=0;
		REP(i,1,m){
			int l=qu[i].l,r=qu[i].r;
			while(L>l)calc(L-1,1),--L;
			while(R<r+1)calc(R+1,1),++R;
			while(L<l)calc(L,-1),++L;
			while(R>r+1)calc(R,-1),--R;
			ans[qu[i].id]=now;
		}
		REP(i,1,m)printf("%lld\n",ans[i]);
	}
}

int main(){
	File();
	read(p);
	scanf("%s",str+1);
	n=strlen(str+1);
	REP(i,1,n)a[i]=str[i]^'0';
	read(m);
	if(p==2 || p==5)return subtask1::work(),0;
	else return subtask2::work(),0;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值