codeforces 1097H. Mateusz and an Infinite Sequence 数位Dp + bitset优化Dp

codeforces 1097H. Mateusz and an Infinite Sequence

题目链接

分析

题目大意:
给你一个长度为 d d d的数组 g e n gen gen和模数 m m m
定义序列 M k M_k Mk如下:
M 0 = 0 M_0={0} M0=0
M k ( k > = 1 ) M_k(k>=1) Mk(k>=1)是将 M k − 1 M_{k-1} Mk1复制 d d d份,且复制的第i份的所有元素都要加上 g e n i gen_i geni
保证 g e n 1 = 0 gen_1=0 gen1=0,给你一个序列 B B B和两个数 l , r l,r l,r,询问 M ∞ M_{\infty} M [ l , r ] [l,r] [l,r]区间中有多少个长度为 ∣ B ∣ |B| B的子串 A A A满足 A ≤ B A \le B AB
间共出现了多少次。
2 &lt; = d &lt; = 20 , 2 &lt; = m &lt; = 60 , ∣ B ∣ &lt; = 30000 , 1 &lt; = l &lt; = r &lt; = 1 0 18 2&lt;=d&lt;=20,2&lt;=m&lt;=60,|B|&lt;= 30000,1&lt;=l&lt;=r&lt;=10^{18} 2<=d<=20,2<=m<=60,B<=30000,1<=l<=r<=1018

首先由于 g e n 1 = 0 gen_1=0 gen1=0,所以原来的那个串是一定会保留的。
我们考虑 ∣ B ∣ = 1 |B|=1 B=1的情况,这个时候只需要记录每个数的出现次数, g e n i gen_i geni相当于是把当前的答案数组循环位移一段加回去,考虑 f [ i ] [ v ] f[i][v] f[i][v]表示 M 0 = v M_0=v M0=v时复读了 i i i轮的答案, f [ x ] [ i ] = ∑ j = 0 m − 1 f [ x − 1 ] [ k + g e n [ j ] m o d &ThinSpace;&ThinSpace; m ] f[x][i]=\sum_{j=0}^{m-1} f[x-1][k+gen[j] \mod m] f[x][i]=j=0m1f[x1][k+gen[j]modm],最后用类似数位Dp的方法可以求出答案。
这个时候考虑 ∣ B ∣ ≠ 1 |B|\neq 1 B̸=1的情况。我们发现,数位Dp那部分的方法其实可以沿用,仍然记录 f [ i ] [ v ] f[i][v] f[i][v]表示 M 0 = v M_0=v M0=v时复读了 i i i轮的信息。注意到我们并不能单纯地记录答案。因为 f [ x ] [ i ] f[x][i] f[x][i] 实际上是要合并所有 j j j f [ x − 1 ] [ k + g e n [ j ] m o d &ThinSpace;&ThinSpace; m ] f[x-1][k+gen[j] \mod m] f[x1][k+gen[j]modm]的信息。事实上每个 f [ x − 1 ] [ k + g e n [ j ] m o d &ThinSpace;&ThinSpace; m ] f[x-1][k+gen[j] \mod m] f[x1][k+gen[j]modm]都代表了一个长度为 d x − 1 d^{x-1} dx1的区间的信息。我们现在需要考虑的就转化成了两个区间的答案怎么合并。
首先肯定拿 B B B去各自匹配两个子区间,然后一种显然的思路是暴力记录一下两个子区间的前缀和后缀匹配 B B B的情况,合并的时候暴力枚举。
这样的话复杂度是 O ( m n d l o g d r ) O(mndlog_dr) O(mndlogdr),算一下发现超了一点点,需要优化。
考虑一下具体的做法是:
我们设 s u f i suf_i sufi表示当前子区间的 N − i N-i Ni后缀可以和 B B B N − i N-i Ni前缀匹配, p r e i pre_i prei表示当前子区间的 i i i前缀可以和 B B B i i i后缀匹配。那么我们发现答案就是 ∑ [ s u f i &amp; p r e i ] \sum [suf_i \&amp; pre_i] [sufi&prei]。所以可以用 b i t s e t bitset bitset优化之。复杂度 O ( m n w d l o g d r ) O(m\frac{n}{w}dlog_dr) O(mwndlogdr)
注意细节。

代码

#include<bits/stdc++.h>
const int N = 3e4 + 10;
typedef std::bitset<N> BI; 
long long ri() {
	char c = getchar(); long long x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
long long len[66];
int n, d, m, tot, gen[25], b[N]; 
BI all;
struct info {
	long long ans, len;
	BI pre, suf;
	info operator + (const info &b) { //Merge this and b
		info c;
		c.ans = ans + b.ans;
		c.len = len + b.len;
		c.pre = pre; c.suf = b.suf;
		if(len < n - 1) c.pre &= (b.pre >> len) | (all << n - 1 - len);
		if(b.len < n - 1) c.suf &= (suf << b.len) | (all >> n - 1 - b.len);
		if(len + b.len >= n) {
			BI res = suf & b.pre;
			if(len < n - 1) res &= (all >> n - 1 - len);
			if(b.len < n - 1) res &= (all << n - 1 - b.len);
			c.ans += res.count();
		}
		return c;
	}
	void operator += (const info &b) {*this = !len ? b : *this + b;}
}f[66][66];
long long Query(long long x) {
	info res; res.ans = res.len = 0;
	int add = 0;
	for(int i = tot; ~i; --i)
		for(int j = 1;j <= d; ++j) {
			if(x >= len[i]) {
				x -= len[i];
				res += f[i][(add + gen[j]) % m];
			}
			else {
				(add += gen[j]) %= m;
				break;
			}
		} 
	return res.ans;
}
int main() {
	d = ri(); m = ri();
	for(int i = 1;i <= d; ++i)
		gen[i] = ri();
	n = ri();
	for(int i = 1;i <= n; ++i)
		b[i] = ri();
	for(int i = 1;i < n; ++i)
		all.set(i);
	len[tot = 0] = 1;
	for(int i = 0;i < m; ++i) { //init
		f[0][i].len = 1;
		if(n == 1) f[0][i].ans = i <= b[1];
		else {
			for(int j = 1;j <= n - 1; ++j) {
				f[0][i].pre[j] = i <= b[j + 1];
				f[0][i].suf[j] = i <= b[j];
			}
		}
	}
	long long L = ri(), R = ri();
	for(;len[tot] <= R / d;) { //transformation
		++tot; len[tot] = len[tot - 1] * d;
		for(int i = 0;i < m; ++i)
			for(int j = 1;j <= d; ++j)
				f[tot][i] += f[tot - 1][(i + gen[j]) % m];
	}
	printf("%lld\n", Query(R) - Query(L + n - 2));
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值