1 条题解
-
0
根据观察得到的一些规律(?)
引理一:设组成答案的两个数在二进制下分别是 ,那么 一定由且仅由一段前缀所有的 组成, 是一坨前导 和剩下部分组成。
比如以下样例:
10110100那么 。
定义分界点 表示 这段前缀里所有的 都在 里,而 里面所有的 以及 这段后缀都在 。
设 表示去除前导 后, 的长度。
引理二:考虑从前往后枚举分界点,枚举到第一个 的分界点,此时的 即为答案。
让我想想怎么证明 2333.
::::info[来自deepseek]
一、最优解的结构
引理1:存在一个最优划分,使得其中一个数(记为 )恰好由原串某个前缀 中的所有
1按顺序组成,而另一个数(记为 )由该前缀中的所有0以及后缀 的全部字符按顺序组成。简要证明:考虑任意最优划分,得到两个数 和 (不妨设 )。设 为 的最高有效位在原串中的位置。则所有在 之前的
1都必须分配给 (否则,若某个 且 分配给了 ,则 会成为 的最高有效位,与 的定义矛盾)。进一步,可以通过交换调整使得 仅包含这些1,且不增加和。详细论证可通过反证法或交换差值的分析完成。
二、确定最优分界点
对于形如上引理的划分,设 为前缀 中
1的个数(即 的有效长度), 为 去除前导零后的有效长度。令 ,其中 , 为后缀 的二进制值。当 增加时(仅考虑 的情形,因为 时 不变),计算 可得:
其中 (因为此时 ,故 的有效长度即为后缀长度)。因此:
- 若 ,则 ,和随 增加而减小;
- 若 ,则 ,和不变;
- 若 ,则 ,和随 增加而增大。
故 在 时开始单调不减,最小值出现在第一个满足 的 处(或与之等价的 的最后一个 ,但两者对应的和相同)。
三、算法实现
根据以上分析,算法步骤如下:
- 预处理数组 ,表示从位置 开始的后缀去除前导零后的有效长度。
- 初始化 ,从 开始枚举:
- 若 ,停止,当前 即为最优分界点。
- 否则,令 ,若 则 。
- 计算 (即 个
1的二进制数), 为后缀 的二进制值。 - 输出 的二进制表示。
该算法时间复杂度为 ,可处理大规模输入。
::::
很遗憾没能看懂。那让我说点人话吧。这之前大家可以手玩一下样例,以便更好地理解。
证明引理一:
考虑现在的 存在一部分前导 ,这时候答案显然不变,不需要考虑。
考虑现在的 中间掺杂了一些 ,而 的结构不变。这种情况会使 的位数变多, 显然更大,也不优。
证明引理二:
大家都知道:设二进制数 的位数分别为 ,则 .
所以我们一定要让 的位数尽可能接近(注意 要除去前导 )。
考虑调整法证明。不妨将 中最后一个 放到 里,大致有以下两种情况:


发现后者答案一定是不优于前者的。
::::success[code]
#include <bits/stdc++.h> #define gt getchar #define pt putchar typedef long long ll; const int MAXN = 1e6 + 5; ll read() { ll x = 0, f = 1;char ch = gt(); while (ch < '0' || ch > '9') {if (ch == '-') f = -1;ch = gt();} while (ch >= '0' && ch <= '9') {x *= 10;x += ch - '0';ch = gt();} return x * f; } void ckmax(ll &a, ll b) {a = std::max(a, b);} void ckmax(double &a, double b) {a = std::max(a, b);} void ckmin(ll &a, ll b) {a = std::min(a, b);} void ckmin(double &a, double b) {a = std::min(a, b);} std::string s, a, b; ll ans[MAXN]; void solve() { ll n = read(); std::cin >> s; s = " " + s; for (int i = 0; i <= n + 1; i++) ans[i] = 0; a = "", b = ""; ll nxt; for (int i = 1; i <= n; i = nxt) { //不断跳到下一个 1 的位置 if(s[i] == '1') { a += s[i];//a 存的是前缀里面所有的 1 int j; for (j = i + 1; j <= n; j++) { if(s[j] == '1') break; } // 找到下一个 1 nxt = j; if(nxt > n) break; if(a.size() >= n - nxt + 1) { // 如果满足引理二的条件 b = s.substr(nxt, n - nxt + 1);//取出后半段所有的数字 break; } } else nxt = i + 1; } reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); //翻转方便计算 ll m = 0; for (int i = 0; i < std::max(a.size(), b.size()); i++) { ll p = 0, q = 0; if(i < a.size()) { p = a[i] - '0'; } if(i < b.size()) { q = b[i] - '0'; } ans[i] += p + q; ans[i + 1] += ans[i] / 2; ans[i] %= 2; m = i; } //二进制加法 while(ans[m + 1]) ++m; //希望大家不要像我把 while 写成 if for (int i = m; i >= 0; i--) std::cout << ans[i]; std::cout << '\n'; } int main() { ll T = read(); while(T--) solve(); return 0; }::::
- 1
信息
- ID
- 9685
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者