【莫队】BZOJ4542 [Hnoi2016] 大数

【前言】
我的莫队又没排序

【题目】
BZOJ
给定一个长度为 n n n的字符串 S S S以及一个模数 P P P Q Q Q次询问 S S S的一个子串 S [ l … r ] S[l\dots r] S[lr]中有多少个子串表示的数字(去掉前导零后)是 P P P的倍数。
n , m ≤ 1 0 5 , P n,m\leq 10^5,P n,m105,P是一个素数。

【解题思路】
首先我们不妨求出每个后缀所代表的数字对 P P P取模的余数,设为 c i c_i ci,对于某一段区间 [ l , r ] [l,r] [l,r],若 c l = c r + 1 c_l=c_{r+1} cl=cr+1,则 S [ l … r ] S[l\dots r] S[lr]这个字符串就是 P P P的倍数。为什么?根据同余的那一套理论,我们设 S [ l … r ] S_[l\dots r] S[lr] A , S [ r + 1 … n ] A,S_[r+1\dots n] A,S[r+1n] B B B则有:
A × 1 0 n − r + 1 + B ≡ B  (mod P) A\times 10^{n-r+1}+B\equiv B \text{ (mod P)} A×10nr+1+BB (mod P)
由于 1 0 n − r + 1 ̸ ≡ B  (mod P) 10^{n-r+1}\not\equiv B\text{ (mod P)} 10nr+1̸B (mod P),则 A ≡ 0  (mod P) A\equiv 0\text{ (mod P)} A0 (mod P)

当然当模数为 2 , 5 2,5 2,5时不满足,此时用两个数组分别表示每个前缀中 2 2 2 5 5 5的倍数时有多少种情况及有多少个数末尾是 2 2 2 5 5 5的倍数,用前缀和维护。这部分比较简单。

当模数为其他的时候,就是区间中有多少对相同的数字,我们可以用莫队来做。
复杂度 O ( n n ) O(n\sqrt n) O(nn )

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+10,lim=332;
int n,Q,mod;
char s[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void write(ll x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(ll x){write(x);putchar('\n');}

namespace S1
{
	int a[N],b[N],cnt[N],bl[N];
	ll res,ans[N];

	struct Tquery
	{
		int l,r,id;
		bool operator <(const Tquery&rhs)const{return bl[l]==bl[rhs.l]?r<rhs.r:l<rhs.l;}
	}q[N];
	void solve()
	{
		for(int i=n,bas=10%mod;i;--i,bas=1ll*bas*10%mod)
			b[i]=a[i]=((ll)a[i+1]+1ll*(s[i]^48)*bas)%mod;
		b[n+1]=0;sort(b+1,b+n+2);int cb=unique(b+1,b+n+2)-b;
		for(int i=1;i<=n+1;++i) a[i]=lower_bound(b+1,b+cb+1,a[i])-b+1,bl[i]=(i-1)/lim+1;
		for(int i=1;i<=Q;++i) q[i].l=read(),q[i].r=read()+1,q[i].id=i;
		sort(q+1,q+Q+1);

		int l=1,r=0;
		for(int i=1;i<=Q;++i)
		{
			for(;r<q[i].r;) ++r,res+=cnt[a[r]],++cnt[a[r]];
			for(;l>q[i].l;) --l,res+=cnt[a[l]],++cnt[a[l]];
			for(;r>q[i].r;) --cnt[a[r]],res-=cnt[a[r]],--r;
			for(;l<q[i].l;) --cnt[a[l]],res-=cnt[a[l]],++l;
			ans[q[i].id]=res;
		}
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
}

namespace S2
{
	ll cnt[N],sum[N];
	void solve()
	{
		for(int i=1;i<=n;++i) 
			if(!((s[i]^48)%mod)) cnt[i]=cnt[i-1]+1,sum[i]=sum[i-1]+i;
			else cnt[i]=cnt[i-1],sum[i]=sum[i-1];
		for(int i=1;i<=Q;++i)
		{
			int l=read(),r=read();
			writeln(sum[r]-sum[l-1]-(cnt[r]-cnt[l-1])*(l-1));
		}
	}
}


int main()
{
#ifdef Durant_Lee
	freopen("BZOJ4542.in","r",stdin);
	freopen("BZOJ4542.out","w",stdout);
#endif
	mod=read();scanf("%s",s+1);Q=read();n=strlen(s+1);
	if(mod!=2 && mod!=5) S1::solve();
	else S2::solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值