首页 > 其他分享 >LG3107 [USACO14OPEN] Odometer S 题解 (数位DP+容斥)

LG3107 [USACO14OPEN] Odometer S 题解 (数位DP+容斥)

时间:2024-07-25 16:22:32浏览次数:10  
标签:int 题解 容斥 tot Odometer k2 k1 DP 数位

题意

定义一个数是神奇的当且仅当这个数中有一个数位出现了一半及以上,比如 1122233。求 \([l,r]\) 中有多少个好的数字,\(100\le l,r\le 10^{18}\)。

题解

考虑数位 DP,先把答案转为 \(Ans(r)-Ans(l-1)\),我们钦定一个数 \(k\) 让他必须出现多于一半,然后我们想求 \([1,x]\) 中有多少个数满足 \(k\) 出现多于一半,所以我们想到记 \(f(x,k,t,top)\) 表示考虑我们填到了第 \(x\) 位,\(k\) 这个数出现了 \(t\) 次,当前是否达到最大值(详见数位 DP)。每次转移我们枚举第 \(x\) 位该填哪个数字,转移即可。

但是发现这样子会算重,也就是会出现 1122 这种数字,我们在 \(k=1,k=2\) 的时候都会把这个数算到里面。于是我们考虑再写一个数位 DP 处理这个问题。设 \(g(x,k1,k2,t1,t2,top)\) 表示考虑我们填到第 \(x\) 位,要求这个数里要么是 \(k1\) 要么是 \(k2\),其中 \(k1\) 出现 \(t1\) 次,\(k2\) 出现 \(t2\) 次,每次转移考虑第 \(x\) 位是填 \(k1\) 还是 \(k2\) 即可。

为避免前导零,我们可以考虑在 \(f,g\) 外边枚举第一个数。

int f[21][21][2], d[21], tot;

int dfs1(int x, int k, int t, bool top, int vd) {
	if (x > tot) return t >= (vd + 1) / 2;
	assert(x <= 20);
	if (~f[x][t][top]) return f[x][t][top];
	int ret = 0, mx = top ? d[x] : 9;
	for (int i = 0; i <= mx; ++i) {
		ret += dfs1(x + 1, k, (i == k) + t, top && i == mx, vd);
	}
	return f[x][t][top] = ret;
}

int g[21][21][21][2];

int dfs2(int x, int k1, int k2, int t1, int t2, bool top) {
	if (x > tot) return t1 == t2;
	assert(x <= 20);
	if (~g[x][t1][t2][top]) return g[x][t1][t2][top];
	int ans = 0;
	if (!top) {
		ans += dfs2(x + 1, k1, k2, t1 + 1, t2, false);
		ans += dfs2(x + 1, k1, k2, t1, t2 + 1, false);
		return g[x][t1][t2][top] = ans;
	} else {
		if (d[x] < k1) return g[x][t1][t2][top] = 0;
		if (k1 <= d[x] && d[x] < k2) {
			return g[x][t1][t2][top] = dfs2(x + 1, k1, k2, t1 + 1, t2, k1 == d[x]);
		}
		if (k2 <= d[x]) {
			ans += dfs2(x + 1, k1, k2, t1 + 1, t2, k1 == d[x]);
			ans += dfs2(x + 1, k1, k2, t1, t2 + 1, k2 == d[x]);
			return g[x][t1][t2][top] = ans;
		}
		assert(false); // wtf???
	}
}

int calc(int x) {
	if (x == 1e18) return calc(x - 1) + 1;
	int t = x;
	tot = 0;
	while (t) {
		d[++tot] = t % 10;
		t /= 10;
	}
	reverse(d + 1, d + 1 + tot);
	int ans = 0;
	for (int i = 1; i <= tot; ++i) {
		for (int k = 0; k <= 9; ++k) {
			for (int j = 1; j <= 9; ++j) {
				if (i == 1 && j > d[1]) break;
				memset(f, -1, sizeof f);
				int tmp = dfs1(i + 1, k, j == k, i == 1 && j == d[1], tot - i + 1);
				ans += tmp;
			}
		}
	}
	for (int i = 1; i <= tot; ++i) {
		if ((tot - i + 1) % 2) continue;
		for (int k1 = 0; k1 <= 9; ++k1) {
			for (int k2 = k1 + 1; k2 <= 9; ++k2) {
				if (i != 1) {
					memset(g, -1, sizeof g);
					if (k1) ans -= dfs2(i + 1, k1, k2, 1, 0, false);
					memset(g, -1, sizeof g);
					if (k2) ans -= dfs2(i + 1, k1, k2, 0, 1, false);
					continue;
				}
				if (d[1] < k1) break;
				if (k1 <= d[1] && d[1] < k2) {
					memset(g, -1, sizeof g);
					if (k1) ans -= dfs2(i + 1, k1, k2, 1, 0, d[1] == k1);
				}
				if (k2 <= d[1]) {
					memset(g, -1, sizeof g);
					if (k1) ans -= dfs2(i + 1, k1, k2, 1, 0, d[1] == k1);
					memset(g, -1, sizeof g);
					if (k2) ans -= dfs2(i + 1, k1, k2, 0, 1, d[1] == k2);
				}
			}
		}
	}
	return ans;
}

void work() {
	int l, r; cin >> l >> r;
	cout << calc(r) - calc(l - 1) << endl;
}

标签:int,题解,容斥,tot,Odometer,k2,k1,DP,数位
From: https://www.cnblogs.com/XiaoQuQu/p/18323432

相关文章

  • CF1015F 题解
    题面考虑这样的匹配问题,可以想如何确定第一次匹配,这样可以不重不漏地计数。考虑dp的时候同时维护有几个括号没有匹配,匹配到\(s\)的第几位,所以令\(f(i,j,k)\)表示dp到(要计数的序列的)第\(i\)个字符,有\(j\)个左括号没有匹配,匹配到\(s\)的第\(k\)位。转移很容易,考......
  • 程序设计:C++入门教程(速成) + 15道经典例题(附带例题解析)
    本文章以实用为主,若实在是不明白文字所表达的内容,无脑复制代码,自己动手运行一下,实验一下即可理解文章内容,放心,代码是全的,全选复制粘贴即可不废话,直接开整数据类型常用数据类型int:整数类型,用于表示整数值。例如:1,2,-3,0等。float:单精度浮点数类型,用于表示带有小数点的数......
  • AT_arc116_e [ARC116E] Spread of Information 题解
    题目传送门前置知识二分答案|树形DP解法答案显然具有单调性,考虑二分答案。设当前二分出的答案为\(mid\),则等价于覆盖距离为\(mid\)的情况下进行选点。做法同luoguP3942将军令,考虑进行贪心,对于深度最深的叶节点将选择的点放在边界时,即取\(mid\)级祖先时,覆盖的范......
  • 题解—[ABC265E] Warp
    题面简单计数DP。由于数据范围很大,所以状态不能常规设置为\(dp_{i,j}\)。注意到\(n\)只有\(300\),考虑设\(dp_{i,j,k}\)表示三种行走方法分别使用\(i\),\(j\),\(k\)次的路径数量。状态转移很简单,先计算出来当前所在位置,如果是障碍就跳过,否则$dp_{i,j,k}=dp_{i-1,j,k}+dp......
  • P2671 [NOIP2015 普及组] 求和 题解
    题目:P2671NOIP2015普及组求和题意给定一个带有颜色和数字的序列,我们要寻找三元组\((x,y,z)\)满足以下条件:\(y\)为\(x\)和\(z\)的中点且都为整数。\(color[x]=color[z]\)。我们命这样一个三元组对答案的贡献为\((x+z)*(num[x]+num[z])\)。整个序列的总价值为每个......
  • Temperature 题解
    前言题目链接:洛谷;SPOJ;Hydro&bzoj。题意简述有一个长度为\(n\)的序列,每个位置值的范围为\([L_i,R_i]\)内,求原序列可能的最长不降子串长度。题目分析尝试找一些性质。发现,连续一段合法的区间,都能分成若干真正参与最长不降子串,以及紧跟着的若干包含\(L_i\)的位置。下图......
  • [ABC318E]Sandwiches 题解
    题意给定一个序列\(A\),要求找有多少个三元组\((i,j,k)\)满足以下条件:\(1\lei<j<k\leN\)\(A_i=A_k\)\(A_i\neA_j\)思路相当于是找每两个相同的元素中有多少个不同的数字。例如:12131答案显然是4,即是\((1,2,3)(1,2,5)(1,4,5)(3,4,5)\)。用\(q[A[i]]\)......
  • [题解]CF117C Cycle
    思路发现最简单的方法就是直接枚举三个点,但是复杂度\(\Theta(n^3)\)无法接受。考虑枚举一个点,并确定它的一条边,那么只需要再枚举一个点了。于是转化为了,对于每一个点找到其最好的出边。观察下图,\(a\toc\)的边是不必要的。因为,如果有一个三元环包含\(a\toc\),那么一定能......
  • P3294 [SCOI2016] 背单词 题解
    题意给你\(n\)个字符串,让你对其进行排列,使得按以下规则花费最少:设当前字符串为\(s\),\(x\)为\(s\)在答案排列中的位置。如果\(s\)存在后缀且\(s\)的后缀在\(s\)之后,花费加\(n^2\)。如果\(s\)不存在后缀则花费加\(x\)。设\(y\)为\(s\)之前离其最近的......
  • P10717 题解
    好神仙的题目。赛时胡了一个状态和转移都和官解不同的做法,得到了\(O(n10^m)\)的优秀复杂度。卡了一场常卡进了\(75\)分。这个做法和官解关系不大,并且很难进行最后的优化部分,所以在此不再赘述。首先考虑\(k=1\)的情况。考虑记录一些状态能够描述子树内的选择方案,\(0\)表示......