思路分析

首先明确一点,反异或操作并不能使字符串变长,所以你本来要加多少数字,现在还是要加那么多。但是观察样例我们可以发现,反异或操作可以使要加的 \(1\) 的数量变少。

比如样例:

\[\texttt{00111011} \]

我先手动添加 \(\texttt{11000}\),然后对字符串进行反异或操作。

\[\begin{array}{r} \texttt{11000} \\ \oplus\ \texttt{00011} \\ \hline \texttt{11011} \end{array} \]

然后我再手动添加 \(\texttt{001}\) 就行了,总共只添加了 \(3\)\(1\)

由于我只能从两边添加数字,所以我在反异或之后添加的数字一定不会加到我的中间,换言之,进行反异或的序列一定是原字符串的一个子串。

但是怎么知道我们枚举到的某个子串是不是一个串反异或来的呢?如果是,那么那个串 \(1\) 的数量又是多少呢?

首先,是不是所有的字符串都可以成为反异或的产物呢?

比如这个字符串:

\[\texttt{01001} \]

根据反异或不改变字符串长度的的性质,我们知道原串的长度为 \(5\),现称原串为 \(s\),其元素为 \([s_1,s_2,s_3,s_4,s_5]\)

根据异或相同为 \(0\),不同为 \(1\),的性质,我们可以得出以下等式:

\[ \left\{ \begin{aligned} s_1 &= s_5 \\ s_2 &\neq s_4 \\ s_3 &= s_3 \\ s_2 &= s_4 \\ s_5 &\neq s_1 \end{aligned} \right. \]

显然,这是矛盾的。矛盾的原因是异或串的第一位和第五位不相同。由第一位为 \(0\) 得出 \(s_1 = s_5\),但是由第五位为 \(1\) 得出 \(s_5 \neq s_1\),矛盾。

同理,异或串的第二位和第四位不同导致了 \(s_2\)\(s_4\) 的矛盾。

这说明,异或串必定是一个回文串。

但是来看看这个字符串:

\[\texttt{00100} \]

第三位是 \(1\),说明 \(s_3 \neq s_3\),这显然是不对的!

因此还有一项规定:当回文串长度为奇数时,中间那一位不能是 \(1\)

  • 当回文串的某一位是 \(1\) 时,说明一定有两位不同,这两位一定有一个 \(1\)
  • 当回文串的某一位时 \(0\) 时,说明有两位是相同的,那么这两位都是 \(0\) 多好啊。

但是如果枚举一遍所有的子串肯定超时,怎么办呢?

注意到对于固定中心的最长回文,向左右同时扩展时,如果两侧新增的字符是 \(0\),则左半边不增加 \(1\),节省量不变;如果是 \(1\),则左半边增加 \(1\),节省量增大。因此最长回文对应的方案一定不劣于任何较短回文。

还是看样例 \(\texttt{00111011}\),明明 \(\texttt{101}\) 已经是回文串,可以省一个 \(1\) 了,但是更长的 \(\texttt{11011}\) 可以省两个 \(1\)。就算是两边加上了 \(0\),省下的 \(1\) 的数量依然不变。

问题最终转化为:找到字符串中的最长回文串中含 \(1\) 数量最多的子串。

于是可以枚举中心点,然后二分找到最长的以当前点为中心的回文子串,用字符串 hash 快速判断回文串,然后用该回文方案计算最少需手动添加的 \(1\) 的个数,最后取最小值即可。

由于对固定中心来说,向两侧扩展不会减少左半边的 \(1\) 的个数,因此最长回文一定不劣。最后在所有中心的最长回文中取最小值,就等价于在所有合法回文子串中挑选左半边 \(1\) 最多的子串。

复杂度分析

\(O(n)\) 枚举中心点,\(O(\log n)\) 二分找最长回文子串,总时间复杂度 \(O(n \log n)\)

空间复杂度 \(O(n)\)

实现细节

  • 你的哈希很有可能会被卡,这里提供一组能过的底数和模数:\((31,1610612741)\)
  • 二分需要分回文串长度为奇数和回文串长度为偶数两种情况讨论,回文串长度为奇数的情况不能允许中心点为 \(1\)
  • 假设当前中心点为 \(c\)。二分的左边界是 \(0\)(可能没有),偶数情况 \(\min(c, n-c)\);奇数情况 \(\min(c-1, n-c)\),因为奇数中心本身不参与扩展。
  • 注意回文串的左右端点在奇数偶数情况下不同的计算方式。

至此思路完结,上代码。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define div() cout << "-----------------------\n";
#define debug(n) cout << #n << " = " << n << "\n";
#define deb(n) cout << #n << " = " << n << "    ";
int n;string s;int a[1000005];int ans = 1e18;
int h[1000005];int fh[1000005];int base[1000005];
int sum[1000005];
const int p = 31,mod = 1610612741;
inline void work(int center){
	// even
	int lb = 0,rb = min(center,n-center),mid;int len = 0;
	while(lb <= rb){
		mid = (lb+rb) >> 1;
		int l = center-mid+1;
		int r = center+mid;
		int h1 = (h[r]-h[l-1]*base[r-l+1]%mod+mod)%mod;
		int h2 = (fh[l]-fh[r+1]*base[r-l+1]%mod+mod)%mod;
		if(h1 == h2){
			len = mid;
			lb = mid+1;
		}
		else{rb = mid-1;}
	}
	/*
	手动添加 1 = 左外部 1 + 右外部 1 + 左半边 1
	左外部 + 左半边恰好是 sum[center],右外部是 sum[n] - sum[center+len]。
	*/
	ans = min(ans,sum[center]+sum[n]-sum[center+len]);
	
	// odd
	if(a[center] == 1){return;}
	lb = 0;rb = min(center-1,n-center);len = 0;
	while(lb <= rb){
		mid = (lb+rb) >> 1;
		int l = center-mid;
		int r = center+mid;
		int h1 = (h[r]-h[l-1]*base[r-l+1]%mod+mod)%mod;
		int h2 = (fh[l]-fh[r+1]*base[r-l+1]%mod+mod)%mod;
		if(h1 == h2){
			len = mid;
			lb = mid+1;
		}
		else{rb = mid-1;}
	}
	ans = min(ans,sum[center]+sum[n]-sum[center+len]);
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin >> s;n = s.size();
    for(int i = 1;i <= n;i++){a[i] = s[i-1]-'0';sum[i] = sum[i-1]+a[i];}
    base[0] = 1;
    for(int i = 1;i <= n;i++){base[i] = base[i-1]*p%mod;}
    for(int i = 1;i <= n;i++){h[i] = (h[i-1]*p+a[i])%mod;}
    for(int i = n;i >= 1;i--){fh[i] = (fh[i+1]*p+a[i])%mod;}
    for(int i = 1;i <= n;i++){work(i);}
    // 有一种情况是不操作
    cout << min(ans,sum[n]);
    return 0;
}