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->xx+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();
}

posted @ 2021-07-03 21:26  C202044zxy  阅读(187)  评论(0编辑  收藏  举报