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;
}
posted @ 2020-12-24 14:16  duyiblue  阅读(327)  评论(1编辑  收藏  举报