这题妙啊。在学长的精心讲解下大概搞懂了。
首先这个操作就很神奇。考虑将 1 1 1看成 + 1 +1 +1, 0 0 0看成 − 1 -1 −1,求出其前缀和数组,然后每次就是两个前缀和相同的位置进行翻转,更神奇的是因为位置和值同时翻转了,所以你发现相当于是把前缀和数组的区间 [ l , r ] [l,r] [l,r]拿来翻转了。
于是就有一些更神奇的操作了。考虑构造无向图 G = ( V , E ) G=(V,E) G=(V,E),其中 V = { s u m i } V=\{sum_i\} V={sumi}, E = { ( s u m i − 1 , s u m i ) ∣ 0 ≤ i < n } E=\{(sum_{i-1},sum_i)|0\le i<n\} E={(sumi−1,sumi)∣0≤i<n},发现无论怎么操作这个序列对应的 G G G都是不变的,并且一个序列恰好经过所有边一次所以对应一个欧拉路径,那么大胆猜测一个欧拉路径和序列构成双射关系(?)。或者也可以换一个角度来看,相当于一个环可以顺时针或者逆时针走。并且这个图非常神奇, i i i只可能连到 i − 1 i-1 i−1或者 i + 1 i+1 i+1,所以据此贪心的求出字典序最小的欧拉路径是容易的。
复杂度 O ( n ) O(n) O(n)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=5e5+5;
int T,n,Min,sum[N],edges[N][2];
string s;
void dfs(int x){
if(edges[x][0])assert(x);
if(!edges[x][0]&&!edges[x][1])return;
if(edges[x][0]&&edges[x-1][1]>1||!edges[x][1]){
cout<<0;edges[x][0]--,edges[x-1][1]--,dfs(x-1);
}
else {
cout<<1;edges[x][1]--,edges[x+1][0]--,dfs(x+1);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--){
cin>>s,n=s.size();Min=0;sum[0]=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+(s[i-1]=='1'?1:-1);
Min=min(Min,sum[i]);
}for(int i=0;i<=n;i++){
sum[i]-=Min;
}for(int i=0;i<=n;i++)edges[i][0]=edges[i][1]=0;
for(int i=0;i<n;i++){
if(s[i]=='1'){
edges[sum[i]][1]++;
edges[sum[i+1]][0]++;
}
else{
edges[sum[i]][0]++;
edges[sum[i+1]][1]++;
}
}
dfs(sum[0]);cout<<"\n";
}
}