【nowcoder 225876】与巨(数论)

与巨

题目链接:nowcoder 225876

题目大意

问你有多少个 1~n 的数满足它乘上一个 c 之后它原来不是前导 0 的位跟之前一样。

思路

首先它只要留下前面的一些位,那我们可以相当于它是一个取模操作。

(如果是 \(t\) 位,那就是 \(\bmod (2^{t+1})\)

然后我们进行一个推式子:
\(i*c\bmod(2^{t+1})=i\)
\(i*(c-1)=0\bmod(2^{t+1})\)

那我们可以找到最低位的 \(1\),表示成 \(2^px\)

然后 \(i\) 就一定要有 \(2^{t+1-p}\) 这个因子。

然后设 \(g=t+1-p\),然后我们考虑枚举 \(t\),进行一个类似数位 DP 的感觉。
如果 \(t<m-1\)\(n\) 最大 \(m\) 位),那你就是随便选的。
那每次会满足的就分别是 \(2^t,2^t+2^g,2^t+2*2^g,...,2^t+x2^g(2^t+(x+1)2^g=2^{t+1})\)
然后 \(x=2^{t+1}-2^g\),每次多了 \(2^g\),这个是一个等差序列,可以很快求和。
(然后别忘了每次会满足的会贡献 \(2^g\) 次,所以要乘上 \(2^g\)

然后考虑 \(t=m-1\),那我们考虑能选多少。
我们要满足:\(2^t+x2^g\leqslant n\)
然后通过移项等可以得到:\(x=\left\lfloor \dfrac{n}{2^g}\right\rfloor-2^{t-g}\)

然后也是一个等差序列计算,但是你会发现最后移项不一定是计算 \(2^g\) 次。

它的计数范围是 \([2^t+x2^g,2^t+(x+1)2^g-1]\),然后实际要的是 \(n\),多了 \(2^t+(x+1)2^g-1-n\) 次。
然后就减去这些的贡献,记得要乘上 \(2^t+x2^g\),就是 \((2^t+(x+1)2^g-1-n)(2^t+x2^g)\)

然后就好啦。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
#define mo 998244353

using namespace std;

int T, m, c, p;
char n[10000001];
ll ans, two[10000001], inv2;

ll ksm(ll x, ll y) {
	ll re = 1;
	while (y) {
		if (y & 1) re = re * x % mo;
		x = x * x % mo;
		y >>= 1;
	}
	return re;
}

ll inv(ll x) {
	return ksm(x, mo - 2);
}

ll clac(ll s, ll t, ll n) {//三个分别是首项,尾项,个数
	return (s + t) % mo * n % mo * inv2 % mo;
}

int main() {
	two[0] = 1;
	for (int i = 1; i <= 10000000; i++)
		two[i] = two[i - 1] * 2 % mo;
	inv2 = inv(2);//预处理出 2 的逆元,每次都要 log 会超时
	
	scanf("%d", &T);
	while (T--) {
		scanf("%s %d", n + 1, &c);
		m = strlen(n + 1);
		
		c--;
		if (!c) {
			ll re = 0;
			for (int i = 1; i <= m; i++) {
				re = re * 2 + n[i] - '0';
				re %= mo;
			}
			printf("%lld\n", (re + 1) * re % mo * inv2 % mo);
			continue;
		}
		if (c & 1) {
			printf("0\n");
			continue;
		}
		
		p = 0;
		while (!(c & 1)) {
			p++;
			c >>= 1;
		}
		
		ans = 0;
		for (int t = 0; t < m - 1; t++) {
			int g = max(0, t + 1 - p);
			ans = (ans + two[g] * clac(two[t], (two[t + 1] - two[g] + mo) % mo, two[t - g])) % mo;
			ans %= mo;
		}
		
		int t = m - 1;
		ll N = 0;
		int g = max(0, (m - 1 + 1) - p);
		for (int i = 1; i <= m - g; i++)
			N = ((N << 1) + n[i] - '0') % mo;
		N = (N - two[t - g] + 1 + mo) % mo;
		ans = ans + two[g] * clac(two[t], (two[t] + (N - 1 + mo) % mo * two[g] % mo) % mo, N) % mo;
		ans %= mo;
		ll lst = (two[t] + (N - 1 + mo) % mo * two[g] % mo) % mo;
		ll l = 0;
		for (int i = 1; i <= m; i++) l = ((l << 1) + n[i] - '0') % mo;
		ll r = (lst + two[g] - 1 + mo) % mo;
		ans -= (r - l + mo) % mo * lst % mo;
		ans = (ans % mo + mo) % mo;
		
		printf("%lld\n", ans);
	}
	
	return 0;
}
posted @ 2021-10-27 06:56  あおいSakura  阅读(52)  评论(0编辑  收藏  举报