【无码专区4】幸运数字4(折半搜索+计数+结论)

本文介绍了一种解决计数问题的方法,通过折半搜索和双关键字排序,计算给定数列中子集和表示为444的十进制数位数。作者详细解释了如何处理进位影响,利用优化的基排算法降低时间复杂度,最终给出了C++代码实现。
摘要由CSDN通过智能技术生成

因为只有std,没有自我实现,所以是无码专区

主要是为了训练思维能力

solution才是dls正解,但是因为只有潦草几句,所以大部分会有我自己基于正解上面的算法实现过程,可能选择的算法跟std中dls的实现不太一样。

std可能也会带有博主自己的注释。

problem

给定 n n n 个数,选择一个子集然后加起来,统计十进制结果数位表示中有多少个 4 4 4

对所有的 2 n 2^n 2n 种方案,都统计一边,计算总和。

n ≤ 40 , a i ≤ 44444444 n\le 40,a_i\le 44444444 n40,ai44444444

时限: 1 s   256 M S 1s\ 256MS 1s 256MS


我的想法

这种计数类题目,一般是两条线。

  • 差分然后数位 d p dp dp
  • 每一位单独求解计算贡献,即每一位的贡献乘以这一位所在的合法情况数。

本题的 n n n 非常小,而这种 n n n 一般都是折半搜索的标志。

但是我不知道,怎么处理低位进位导致高位数字改变,这就导致了无法抽离子集本身进行求解,必须放在具体的两个子集内才能计数。


solution

折半搜索,每一位分开统计答案。

假设计算第 k k k 位为 4 4 4 的贡献。

结论:考虑两边各取一个数,分别为 s , t s,t s,t,求和为 w w w,如果 w w w 1 0 k + 1 10^{k+1} 10k+1 取模后,最高位是 4 4 4,那么一定满足 s , t s,t s,t 分别对 1 0 k + 1 10^{k+1} 10k+1 取模后再加起来的结果属于 [ 4 × 1 0 k , 5 × 1 0 k ) [4\times 10^k,5\times 10^k) [4×10k,5×10k) [ 14 × 1 0 k , 15 × 1 0 k ) [14\times 10^k,15\times 10^k) [14×10k,15×10k)

将左右两边按 1 0 k + 1 10^{k+1} 10k+1 取模后升序排序,指针线性扫描即可。

直接排序,时间复杂度 O ( 2 n 2 n log ⁡ m ) O(2^{\frac{n}{2}}n\log m) O(22nnlogm),比较悬。

考虑优化,将 k k k 从小到大进行计算。

每次排序相当于是在取模 1 0 k 10^k 10k 的基础上,加上了最高位。

用类似于双关键字的基排做法,在 O ( 2 n 2 ) O(2^{\frac{n}{2}}) O(22n) 时间内解决。

总时间复杂度 O ( 2 n 2 log ⁡ m ) O(2^\frac{n}{2}\log m) O(22nlogm)

参考代码

#include <bits/stdc++.h>

using namespace std;
const int maxn = 2e6 + 10;
struct node {
	long long l, r;
};
vector<node> a, b, c[10], d[10];
int n, val[maxn];

void solve(int *a, int k, vector<node> &b) {
	for (int i = 0; i < (1 << k); i++) {
		long long s = 0;
		for (int j = 0; j < k; j++)
			if (i & (1 << j))
				s += a[j];
		b.push_back(node{s, 0});
	}
}

long long get_jin0(vector<node> &a, vector<node> &b, long long base) { //处理没有进位的答案
	int point = b.size() - 1;
	long long ans = 0;
	for (int i = 0; i < a.size(); i++) {
		while (point >= 0 && a[i].r + b[point].r >= base)
			point--;
		ans += (point + 1);
	}
	return ans;
}

long long get_jin1(vector<node> &a, vector<node> &b, long long base) { //处理进位后的使当前位为4的答案
	int point = 0;
	long long ans = 0;
	int siz = b.size();
	for (int i = a.size() - 1; i >= 0; i--) {
		while (point <= siz - 1 && a[i].r + b[point].r < base)
			point++;
		ans += (b.size() - point);
	}
	return ans;
}


int main() {
	freopen("four.in", "r", stdin);
	freopen("four.out", "w", stdout);
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &val[i]);
	solve(val + 1, n / 2, a);
	solve(val + 1 + n / 2, n - (n / 2), b);
	long long base = 1, ans = 0;
	for (int w = 0; w <= 8; w++) {
		for (int i = 0; i < 10; i++)
			c[i].clear(), d[i].clear();
		for (int i = 0; i < a.size(); i++)
			c[a[i].l % 10].push_back(node{a[i].l / 10, a[i].r});
		for (int i = 0; i < b.size(); i++)
			d[b[i].l % 10].push_back(node{b[i].l / 10, b[i].r});
		for (int i = 0; i < 10; i++) {       //枚举左半边的和的当前位的数字
			int k1 = (14 - i) % 10, k2 = (13 - i) %
			                             10; //求右半边对应的和的当前位的数,k1表示在不进位的情况下的值,k2表示在后面有进位的情况下的需要的
			ans += get_jin0(c[i], d[k1], base) + get_jin1(c[i], d[k2], base);
		}
		int now = 0;
		for (int i = 0; i < 10; i++)
			for (int j = 0; j < c[i].size(); j++)
				a[now++] = node{c[i][j].l, c[i][j].r + i * base};
		now = 0;
		for (int i = 0; i < 10; i++)
			for (int j = 0; j < d[i].size(); j++)
				b[now++] = node{d[i][j].l, d[i][j].r + i * base};
		base *= 10;
	}
	printf("%lld\n", ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值