CF1464C Poman Numbers
CF1464C Poman Numbers
Codeforces, Codeforces Round #692 (Div. 1, based on Technocup 2021 Elimination Round 3), CF#692, CF1464C Poman Numbers
题目大意
对一个由小写字母组成的非空字符串 \(S\),定义它可以对应的数值 \(f(S)\):
- 如果 \(|S| > 1\),则可以任选一个 \(1\leq m < |S|\),并令 \(f(S) = -f(S[1,m]) + f(S[m + 1, |S|])\)。
- 否则设 \(S\) 里唯一的字符为 \(c\),则 \(f(S) = 2^{\text{pos}(c)}\)。其中 \(\text{pos}(c)\) 表示字符 \(c\) 在字母表中的位置,\(\text{pos}(\texttt{a}) = 0, \text{pos}(\texttt{z}) = 25\)。
每一步里的 \(m\) 可以独立选择。
给你一个长度为 \(n\) 的字符串 \(S\),和一个整数 \(T\)。请你判断 \(f(S)\) 是否可能等于 \(T\)。
数据范围:\(1\leq n\leq 10^5\),\(-10^{15}\leq T\leq 10^{15}\)。
本题题解
考虑 \(f(S[1,n])\) 递归的过程,相当于建出一棵二叉树,满足这棵二叉树恰有 \(n\) 个叶子,且每个非叶子节点恰有两个儿子。从左到右,每个叶子依次对应了 \(S[1,n]\) 里的每个字符。
那么我们可以将 \(f(S[1,n])\) 的值拆成 \(n\) 个叶子的贡献。对每个叶子,考虑从它到根的路径,设其中有 \(\text{leftStep}\) 步是向左走的,那么它最终的贡献系数就是 \((-1)^{\text{leftStep}}\)。
引理一:
\(S_n\) 的贡献系数必须为 \(1\),\(S_{n - 1}\) 的贡献系数必须为 \(-1\)。除 \(S_n\) 和 \(S_{n - 1}\) 外,其他每个叶子的贡献系数无论怎么选择,都能构造出对应的二叉树。
证明:
设每个节点的贡献系数分别为:\(c_1,c_2,\dots,c_n\)。(\(\forall i: c_i\in\{-1,1\}\))。
\(S_n\) 在二叉树上只能是一直向右(因为每个非叶子节点必须有两个儿子),所以贡献系数 \(c_n\) 一定是 \(1\)。
又因为 \(S_{n - 1}\) 是 \(S_n\) 的兄弟,所以它们只有最下面一步不同(\(n-1\) 向左,\(n\) 向右),因此 \(c_{n - 1} = -1\)。
对于其他节点,考虑递归地解决。初始时 \(C = \{c_1,c_2,\dots,c_n\}\) (\(n\geq 2\))。
- 若当前 \(c_1 = -1\),则递归 \(C_L = \{c_1\}, C_R = \{c_2, c_3, \dots, c_{|C|}\}\)。
- 若当前 \(c_1 = 1\),则找到它后面第一个 \(c_p = -1\) 的位置 \(p\)(显然 \(p < |C|\))。递归 \(C_L = \{-c_1,-c_2,\dots , -c_p\}, C_R = \{c_{p + 1}, \dots ,c_{|c|}\}\)。发现两个集合都仍然满足 \(c_{|C|} = 1, c_{|C| - 1} = -1\)。
边界是 \(|C| = 1\) 时,就已经自动完成了构造。
于是问题转化为,要确定一个 \(c\) 序列,满足 \(\forall i: c_i \in\{-1,1\}\),且 \(c_n = 1, c_{n - 1} = -1\),使得 \(\sum_{i = 1}^{n}c_i 2^{\text{pos}(S_i)} = T\)。
不妨先假设 \(\forall 1\leq i\leq n - 2\),\(c_i = -1\)。求出此时的和 \(\text{sum} = -\sum_{i = 1}^{n-1}2^{\text{pos}(S_i)} + 2^{\text{pos}(S_n)}\)。这是能得到的最小值。若 \(T < \text{sum}\) 则一定无解。否则令 \(T'= T - \text{sum}\)。
然后问题进一步转化为:给定 \(n - 2\) 个形如 \(2^k\) 的数 \(a_1,\dots,a_{n - 2}\),问是否能从中选出一个子集,使得和为 \(T'\)。其中 \(1\leq k\leq 26\),注意不是 \(0\) 到 \(25\),因为从 \(-1\) 变成 \(1\) 相当于增大两倍。
引理二:
对于任意整数 \(K\geq 1\),如果一个可重集 \(A = \{a_1,a_2,\dots,a_m\}\) 满足所有 \(a_i\) 形如 \(2^k\) (\(0\leq k < K\),\(k\) 是整数),且所有数之和大于 \(2^K\)。那么一定存在一个子集 \(B\subset A\),满足 \(B\) 里所有数之和等于 \(2^K\)。
证明:
当 \(K = 1\) 时显然正确。
当 \(K > 1\) 时,用归纳法。假设已经对 \(K - 1\) 成立。考虑 \(K\),先把 \(A\) 里所有 \(2^{K - 1}\) 挑出来,如果有至少 \(2\) 个 \(2^{K - 1}\),那么已经达到要求。否则用剩下的数去拼 \(2^{K - 1}\),根据归纳假设,一定能恰好拼出所需的数量。
回到本题,我们从大到小考虑 \(a_1,\dots, a_{n - 2}\) 里所有数。如果当前数 \(a_i \leq T'\),就选择 \(a_i\),并令 \(T'\) 减去 \(a_i\)。最终如果 \(T' = 0\),答案就是 \(\texttt{Yes}\),否则是 \(\texttt{No}\)。根据引理二可以说明这个贪心的正确性。
时间复杂度 \(\mathcal{O}(n + \Sigma)\),\(\Sigma = 26\)。
参考代码
// problem: CF1464C
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 1e5;
int n;
char s[MAXN + 5];
ll goal;
int cnt[26];
int main() {
cin >> n >> goal;
cin >> (s + 1);
goal -= (1LL << (s[n] - 'a')); // s[n] 一定是正号
goal += (1LL << (s[n - 1] - 'a')); // s[n - 1] 一定是负号
ll low = 0;
for (int i = 1; i <= n - 2; ++i) {
int c = (s[i] - 'a');
cnt[c]++;
low -= (1LL << c);
}
if (goal < low) {
cout << "No" << endl;
return 0;
}
goal = goal - low;
for (int i = 25; i >= 0; --i) {
ll v = (1LL << (i + 1));
ll x = min(goal / v, (ll)cnt[i]);
goal -= x * v;
}
if (goal) {
cout << "No" << endl;
} else {
cout << "Yes" << endl;
}
return 0;
}