[HNOI2016] 大数(莫队)

problem

luogu-P3245

solution

将这个 n n n 位数从右往左的记录取模 p p p 的结果,即 f ( i ) = ( f ( i − 1 ) ∗ 10 + s i ) % p f(i)=(f(i-1)*10+s_i)\% p f(i)=(f(i1)10+si)%p

显然一个子串是 p p p 的倍数必然满足: f ( r ) − f ( l − 1 ) 1 0 r − l + 1 ≡ 0 ( m o d p ) \frac{f(r)-f(l-1)}{10^{r-l+1}}\equiv 0\pmod p 10rl+1f(r)f(l1)0(modp)

但这并不严谨,虽然好像 10 10 10 和质数是互质的,但是当 p = 2 / 5 p=2/5 p=2/5 的时候上述式子便不成立了。

但是 2 , 5 2,5 2,5 有着非常特殊的性质,即可以通过数的最后一位判断是否该数是否是其倍数。

所以当 p = 2 , p = 5 p=2,p=5 p=2,p=5 的时候我们可以直接特殊的在线处理。

否则就可以按照上面这样离线下来。因为存在互质,则等价于 f ( r ) ≡ f ( l − 1 ) ( m o d p ) f(r)\equiv f(l-1)\pmod p f(r)f(l1)(modp)

这就相当于是 [ l , r ] [l,r] [l,r] 区间内数相同点对的个数,莫队经典题目。

注意 p p p 过大,所以还要再来个离散化。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
struct node { int l, r, id; }q[maxn];
int n, p, m, B, ans;
char s[maxn];
int f[maxn], g[maxn], h[maxn], block[maxn], a[maxn], b[maxn], cnt[maxn], ret[maxn];

void add( int x ) {
	ans -= cnt[a[x]] * ( cnt[a[x]] - 1 ) / 2;
	cnt[a[x]] ++;
	ans += cnt[a[x]] * ( cnt[a[x]] - 1 ) / 2;
}

void sub( int x ) {
	ans -= cnt[a[x]] * ( cnt[a[x]] - 1 ) / 2;
	cnt[a[x]] --;
	ans += cnt[a[x]] * ( cnt[a[x]] - 1 ) / 2;
}

signed main() {
	scanf( "%lld %s %lld", &p, s + 1, &m );
	int n = strlen( s + 1 );
	if( p == 2 or p == 5 ) {
		if( p == 2 ) h[0] = h[2] = h[4] = h[6] = h[8] = 1;
		else h[0] = h[5] = 1;
		for( int i = 1;i <= n;i ++ ) {
			f[i] = f[i - 1] + h[s[i] ^ 48];
			g[i] = g[i - 1] + h[s[i] ^ 48 ] * i;
		}
		for( int i = 1, l, r;i <= m;i ++ ) {
			scanf( "%lld %lld", &l, &r );
			printf( "%lld\n", g[r] - g[l - 1] - ( f[r] - f[l - 1] ) * ( l - 1 ) );
		}
		return 0;
	}
	B = sqrt( n );
	for( int i = 1;i <= n + 1;i ++ ) block[i] = ( i - 1 ) / B + 1;
	for( int i = 1;i <= m;i ++ ) scanf( "%lld %lld", &q[i].l, &q[i].r ), q[i].r ++, q[i].id = i;
	sort( q + 1, q + m + 1, []( node x, node y ) { return block[x.l] == block[y.l] ? x.r < y.r : x.l < y.l; } );
	for( int i = n, x = 1;i;i --, x = x * 10 % p ) {
		a[i] = ( ( s[i] ^ 48 ) * x + a[i + 1] ) % p;
		b[i] = a[i];
	}
	sort( b + 1, b + n + 2 );
	int t = unique( b + 1, b + n + 2 ) - b - 1;
	for( int i = 1;i <= n + 1;i ++ ) a[i] = lower_bound( b + 1, b + t + 1, a[i] ) - b;
	int curl = 1, curr = 0;
	for( int i = 1;i <= m;i ++ ) {
		int l = q[i].l, r = q[i].r;
		while( curl < l ) sub( curl ++ );
		while( curl > l ) add( -- curl );
		while( curr < r ) add( ++ curr );
		while( curr > r ) sub( curr -- );
		ret[q[i].id] = ans;
	}
	for( int i = 1;i <= m;i ++ ) printf( "%lld\n", ret[i] );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值