CF1458D Flip and Reverse
一、题目
二、解法
没有什么好的想法,就从图论的角度入手吧。
要根据题目特性来建图,首先要考虑把什么当做点的问题,如果把字符串的元素当成点是不好表示 子串必须包含同样数量的字符0与1
这个限制的。但是前缀和可以方便地表示这个限制,令 \(1\) 为 \(1\),\(0\) 为 \(-1\),那么如果 \(sum_l=sum_r\) 就说明这是一个可以操作的区间,那么我们把前缀和建成点。
还要考虑怎么表示字符串里的元素,直接当成边建上去,对于元素 \(s_i\),将点 \(sum_{i-1}\) 和 \(sum_i\) 连一条标记为 \(s_i\) 的边,考虑原始字符串就对应了这张图里的某一条欧拉回路(因为要把所有的边访问完)
那么把转换和翻转操作对应到图上,因为有边相连的两个点编号相差 \(1\),所以这张图是很特别的。首先选出一条起点终点相同的路径,然后把沿路经过的边换方向即可,你会发现新的路径正好对应操作之后的字符串。
现在的问题是最小化字典序,有一个结论:所有欧拉路径都对应着操作后的合法字符串。考虑走到 \(x\) 之后出现了两种存在欧拉路径的选择 \(x-1\) 和 \(x+1\),因为都是欧拉路径所以往一边走一定能走回来,那么说明存在边 x-1->x
和 x+1->x
,如果应该走 x+1
(即是对应原字符串的走法)那么是 x->x+1->x->x-1->x
,可以通过操作换成 x->x-1->x->x+1->x
,这正好对应走 x-1
的方法,虽然它并不对应原字符串但是合法的。
那么找到原图定起点定终点,经过标记字典序最小的欧拉回路即可,根据图的特性可以设计如下贪心。
- 如果
x-1
走过去并且能走回来,那么无脑走x-1
即可。 - 否则
x-1
走过去就回不来了,那么走x+1
;如果走不了x+1
是可以直接走x-1
的。
代码非常好写,时间复杂度 \(O(n)\)
三、总结
套路:差分,前缀和。差分可以在区间打标记修改之类的问题使用,前缀和在有区间权值限制的问题使用。
如果要用图论,思考每类元素的意义(是建成边还是建成点),学会把限制建在图上是很重要的。
#include <cstdio>
#include <cstring>
const int M = 500005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,a[2*M][2];char s[M];
void work()
{
scanf("%s",s+1),n=strlen(s+1);
int x=n;
for(int i=1;i<=n;i++)
{
a[x][s[i]-'0']++;
if(s[i]=='0') x--;
else x++;
}
x=n;
for(int i=1;i<=n;i++)
{
if(a[x][0] && a[x-1][1]) a[x][0]--,x--,printf("0");
else if(a[x][1]) a[x][1]--,x++,printf("1");
else a[x][0]--,x--,printf("0");
}
puts("");
}
signed main()
{
T=read();
while(T--) work();
}