1 条题解

  • 0
    @ 2026-4-23 22:10:28

    根据观察得到的一些规律(?)

    引理一:设组成答案的两个数在二进制下分别是 x,yx, y,那么 xx 一定由且仅由一段前缀所有的 11 组成,yy 是一坨前导 00 和剩下部分组成。

    比如以下样例:

    10110100
    

    那么 x=111,y=(00)100x = 111, y=(00)100

    定义分界点 pp 表示 [1,p][1,p] 这段前缀里所有的 11 都在 xx 里,而 [1,p][1, p] 里面所有的 00 以及 (p,n](p, n] 这段后缀都在 yy

    size(x),size(y)size(x), size(y) 表示去除前导 00 后,x,yx, y 的长度。

    引理二:考虑从前往后枚举分界点,枚举到第一个 size(x)size(y)size(x) \ge size(y) 的分界点,此时的 x,yx, y 即为答案。

    让我想想怎么证明 2333.

    ::::info[来自deepseek]

    一、最优解的结构

    引理1:存在一个最优划分,使得其中一个数(记为 xx)恰好由原串某个前缀 [1,p][1, p] 中的所有 1 按顺序组成,而另一个数(记为 yy)由该前缀中的所有 0 以及后缀 (p,n](p, n] 的全部字符按顺序组成。

    简要证明:考虑任意最优划分,得到两个数 aabb(不妨设 aba \le b)。设 jjbb 的最高有效位在原串中的位置。则所有在 jj 之前的 1 都必须分配给 aa(否则,若某个 i<ji < jsi=1s_i = 1 分配给了 bb,则 ii 会成为 bb 的最高有效位,与 jj 的定义矛盾)。进一步,可以通过交换调整使得 aa 仅包含这些 1,且不增加和。详细论证可通过反证法或交换差值的分析完成。


    二、确定最优分界点 pp

    对于形如上引理的划分,设 kk 为前缀 [1,p][1, p]1 的个数(即 xx 的有效长度),LLyy 去除前导零后的有效长度。令 F(p)=x(p)+y(p)F(p) = x(p) + y(p),其中 x(p)=2k1x(p) = 2^k - 1y(p)y(p) 为后缀 [p+1,n][p+1, n] 的二进制值。

    pp 增加时(仅考虑 sp+1=1s_{p+1} = 1 的情形,因为 sp+1=0s_{p+1} = 0F(p)F(p) 不变),计算 F(p+1)F(p)F(p+1) - F(p) 可得:

    Δ=2k2L1\Delta = 2^k - 2^{L-1}

    其中 L=npL = n - p(因为此时 sp+1=1s_{p+1}=1,故 yy 的有效长度即为后缀长度)。因此:

    • k<L1k < L-1,则 Δ<0\Delta < 0,和随 pp 增加而减小;
    • k=L1k = L-1,则 Δ=0\Delta = 0,和不变;
    • kLk \ge L,则 Δ>0\Delta > 0,和随 pp 增加而增大。

    F(p)F(p)kLk \ge L 时开始单调不减,最小值出现在第一个满足 kLk \ge Lpp 处(或与之等价的 k=L1k = L-1 的最后一个 pp,但两者对应的和相同)。


    三、算法实现

    根据以上分析,算法步骤如下:

    1. 预处理数组 f(i)f(i),表示从位置 ii 开始的后缀去除前导零后的有效长度。
    2. 初始化 cnt=0cnt = 0,从 p=0p = 0 开始枚举:
      • cntf(p+1)cnt \ge f(p+1),停止,当前 pp 即为最优分界点。
      • 否则,令 p:=p+1p := p+1,若 sp=1s_p = 1cnt:=cnt+1cnt := cnt + 1
    3. 计算 x=2cnt1x = 2^{cnt} - 1(即 cntcnt1 的二进制数),yy 为后缀 s[p+1..n]s[p+1..n] 的二进制值。
    4. 输出 x+yx + y 的二进制表示。

    该算法时间复杂度为 O(n)O(n),可处理大规模输入。

    ::::

    很遗憾没能看懂。那让我说点人话吧。这之前大家可以手玩一下样例,以便更好地理解。


    证明引理一:

    考虑现在的 xx 存在一部分前导 00,这时候答案显然不变,不需要考虑。

    考虑现在的 xx 中间掺杂了一些 00,而 yy 的结构不变。这种情况会使 xx 的位数变多,x+yx+y 显然更大,也不优。


    证明引理二:

    大家都知道:设二进制数 a,ba, b 的位数分别为 size(a),size(b)size(a), size(b),则 size(a+b)max(size(a),size(b))+1size(a+b) \le \max(size(a), size(b))+1.

    所以我们一定要让 a,ba, b 的位数尽可能接近(注意 bb 要除去前导 00)。

    考虑调整法证明。不妨将 xx 中最后一个 11 放到 yy 里,大致有以下两种情况:

    发现后者答案一定是不优于前者的。


    ::::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

    「THUPC 2026 初赛」又一个 01 串问题

    信息

    ID
    9685
    时间
    1000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者