【无码专区2】序列划分(数学)

有std,但是没有自我实现,所以是无码专区

description

完全由数字组成的字符串 s s s,划分成若干段,每一段看成一个十进制的数(允许前导零)求有多少种划分方法使得相邻两个数至少一个是 D D D 的倍数。对 1 0 9 + 7 10^9+7 109+7 取模。

数据: 5 × 1 0 5 , 1 0 6 5\times 10^5,10^6 5×105,106 级别。

我的想法

针对 30 % 30\% 30% 的数据: n ≤ 1000 n\le 1000 n1000 n = ∣ s ∣ n=|s| n=s】。

d p i , 0 / 1 : dp_{i,0/1}: dpi,0/1: 划分在 i i i 处,枚举上一个划分点 j j j,判断 ( j , i ] (j,i] (j,i] 是否是 D D D 的倍数 的方案数。

然后直接转移,就是 O ( n 2 ) O(n^2) O(n2) 的。

solution

对于另 20 % : ( D , 10 ) = 1 20\%:(D,10)=1 20%:(D,10)=1 就是提示正解的部分分。

S i = ( ∑ s i ⋅ 1 0 n − i ) % D S_i=(\sum_{s_i}·10^{n-i})\%D Si=(si10ni)%D,即以 i i i 开始的后缀对应的十进制数(在取模 D D D 的意义下)

判定条件: s [ i , j ] s[i,j] s[i,j] 组成的数能被 D D D 整除,当且仅当 S i = S j + 1 S_{i}=S_{j+1} Si=Sj+1

d p i , 0 / 1 : dp_{i,0/1}: dpi,0/1: 从前往后划分到 i i i ,当前段能否被 D D D 整除的方案数。

如果能被 D D D 整除,那么必须从 S j = S i + 1 , 1 ≤ j < i S_j=S_{i+1},1\le j<i Sj=Si+1,1j<i 转移过来,否则从不被整除的 S j S_j Sj 转移过来。

用一个桶数组 c n t i : S j = i cnt_i:S_j=i cnti:Sj=i d p j dp_j dpj 的和。

i.e. i i i 转移被整除的信息的时候,相当于去找 c n t S i cnt_{S_i} cntSi 这个桶, d p i → c n t S i dp_i\rightarrow cnt_{S_i} dpicntSi

以上是 ( D , 10 ) = 1 (D,10)=1 (D,10)=1,当 ( D , 10 ) ≠ 1 (D,10)\neq 1 (D,10)=1 时,上述的判定条件就不完全对了。

改写 D = D 1 ⋅ D 2 D=D_1·D_2 D=D1D2,其中 ( D 1 , 10 ) = 1 , D 2 = 2 a 5 b (D_1,10)=1,D_2=2^a5^b (D1,10)=1,D2=2a5b

改写 S i : S_i: Si: i i i 开始到结尾形成的十进制数对 D 1 D_1 D1 取模的结果。

改写判定条件: s [ i , j ] s[i,j] s[i,j] 组成的数能被 D D D 整除,当且仅当 S i = S j + 1 ∧ D 2 ∣ s [ i , j ] S_i=S_{j+1}\wedge D_2\Big |s[i,j] Si=Sj+1D2s[i,j]

在十进制下 s [ i , j ] s[i,j] s[i,j] 要能被 2 , 5 2,5 2,5 的幂次整除,只需要看后面的若干位即可。

这里 D ≤ 1 0 6 ≈ 2 20 ≈ 5 9 D\le 10^6\approx 2^{20}\approx 5^9 D10622059,所以最多看后面的 20 20 20 位。

具体而言

  • 首先判断 s [ i − 19 , i ] s[i-19,i] s[i19,i] 组成的数是否 D D D 的倍数。
    • 是,再从桶里面找 c n t S i + 1 → d p i cnt_{S_{i+1}}\rightarrow dp_i cntSi+1dpi
  • 暴力处理 s [ j , i ] , i − 19 ≤ j ≤ i − 1 s[j,i],i-19\le j\le i-1 s[j,i],i19ji1 判断是否是 D D D 的倍数,再 → d p i \rightarrow dp_i dpi
  • 对于 j = i − 19 j=i-19 j=i19 i i i 中间已经包含有 20 20 20 位了【 i − ( i − 19 ) + 1 = 20 i-(i-19)+1=20 i(i19)+1=20】,所以 S j → c n t S_j\rightarrow cnt Sjcnt,可以入桶了。

参考code

#include <bits/stdc++.h>

using namespace std;

template <typename T>
T power(T a, long long b) {
	T r = 1;
	while (b) {
		if (b & 1) {
			r *= a;
		}
		a *= a;
		b >>= 1;
	}
	return r;
}

template <typename T>
T inverse(T a, T m) {
	a %= m;
	if (a < 0) {
		a += m;
	}
	T b = m, u = 0, v = 1;
	while (a) {
		T t = b / a;
		b -= a * t;
		swap(a, b);
		u -= v * t;
		swap(u, v);
	}
	if (u < 0) {
		u += m;
	}
	return u;
}

template <int _P>
struct modnum {
		static constexpr int P = _P;

	private:
		int v;

	public:
		modnum() : v(0) {
		}

		modnum(long long _v) {
			v = _v % P;
			if (v < 0) {
				v += P;
			}
		}

		explicit operator int() const {
			return v;
		}

		bool operator==(const modnum &o) const {
			return v == o.v;
		}

		bool operator!=(const modnum &o) const {
			return v != o.v;
		}

		modnum inverse() const {
			return modnum(::inverse(v, P));
		}

		modnum operator-() const {
			return modnum(v ? P - v : 0);
		}

		modnum operator+() const {
			return *this;
		}

		modnum &operator++() {
			v++;
			if (v == P) {
				v = 0;
			}
			return *this;
		}

		modnum &operator--() {
			if (v == 0) {
				v = P;
			}
			v--;
			return *this;
		}

		modnum operator++(int) {
			modnum r = *this;
			++*this;
			return r;
		}

		modnum operator--(int) {
			modnum r = *this;
			--*this;
			return r;
		}

		modnum &operator+=(const modnum &o) {
			v += o.v;
			if (v >= P) {
				v -= P;
			}
			return *this;
		}

		modnum operator+(const modnum &o) const {
			return modnum(*this) += o;
		}

		modnum &operator-=(const modnum &o) {
			v -= o.v;
			if (v < 0) {
				v += P;
			}
			return *this;
		}

		modnum operator-(const modnum &o) const {
			return modnum(*this) -= o;
		}

		modnum &operator*=(const modnum &o) {
			v = (int) ((long long) v * o.v % P);
			return *this;
		}

		modnum operator*(const modnum &o) const {
			return modnum(*this) *= o;
		}

		modnum &operator/=(const modnum &o) {
			return *this *= o.inverse();
		}

		modnum operator/(const modnum &o) const {
			return modnum(*this) /= o;
		}
};

template <int _P>
ostream &operator<<(ostream &out, const modnum<_P> &n) {
	return out << int(n);
}

template <int _P>
istream &operator>>(istream &in, modnum<_P> &n) {
	long long _v;
	in >> _v;
	n = modnum<_P>(_v);
	return in;
}

using num = modnum<1000000007>;

int main() {
	freopen("division.in", "r", stdin);
	freopen("division.out", "w", stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	string s;
	int d;
	cin >> s >> d;
	int e = 1;
	while (d % 2 == 0) {
		d /= 2;
		e *= 2;
	}
	while (d % 5 == 0) {
		d /= 5;
		e *= 5;
	}
	int n = s.size();
	vector<int> a(n + 1);
	for (int i = 0; i < n; ++i) {
		a[i + 1] = (a[i] * 10 + (s[i] - '0')) % (d * e);
	}
	int inv10 = d == 1 ? 0 : inverse(10, d);
	vector<int> pw(n + 1);
	pw[0] = 1;
	for (int i = 0; i < n; ++i) {
		pw[i + 1] = (long long) pw[i] * inv10 % d;
	}
	vector<int> base(20);
	base[0] = 1;
	for (int i = 0; i + 1 < 20; ++i) {
		base[i + 1] = base[i] * 10 % (d * e);
	}
	vector<num> foo(n + 1), bar(n + 1);
	vector<num> offset_foo(d), offset_bar(d);
	bar[0] = 1;
	num pref = 0;
	for (int i = 0; i <= n; ++i) {
		foo[i] += pref;
		if (a[i] % e == 0) {
			foo[i] += offset_foo[(long long) a[i] * pw[i] % d];
			bar[i] += offset_bar[(long long) a[i] * pw[i] % d];
		}
		pref += bar[i];
		offset_foo[(long long) a[i] * pw[i] % d] -= bar[i];
		offset_bar[(long long) a[i] * pw[i] % d] += foo[i] + bar[i];
		for (int j = i + 1; j <= n && j < i + 20; ++j) {
			if ((a[j] - (long long) a[i] * base[j - i]) % (d * e) == 0) {
				foo[j] -= bar[i];
				bar[j] += foo[i] + bar[i];
			}
			if (a[j] % e == 0 && (long long) a[i] * pw[i] % d == (long long) a[j] * pw[j] % d) {
				foo[j] += bar[i];
				bar[j] -= foo[i] + bar[i];
			}
		}
	}
	cout << foo[n] + bar[n] << "\n";
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值