【CF1458D】 Flip and Reverse 题解 (贪心 + 欧拉路径)
贪心 + 欧拉路径。
记录一下这道让我想破脑筋的题目。
Solution
记序列中 \(0\) 为 \(-1\),\(1\) 为 \(1\),然后在序列上进行一个前缀和操作。显然,目标就是让最终序列从前往后的前缀和尽可能小。
观察操作,发现操作“要求 \(0\) 和 \(1\) 数量相同的子串”实际上就是满足两端前缀和相同的一个区间所构成的字符串。这就引导我们把这些前缀和形象化成节点,每个权值对应一个节点,原序列上相邻两位的前缀和之间从前往后连一条有向边。而操作所要求的字符串就变成这个图上的某一条欧拉回路。
进一步瞪眼观察,发现一套操作的本质实际上就是把上述找到的某条欧拉回路上每条边的方向取反。回过头来,此时的题目变成了:在前缀和构建的图上,找若干条欧拉回路,将回路上的边都反向,最后再从 \(0\) 开始按照一定顺序遍历此图,要求走一条欧拉路径,若自 \(x\) 到 \(x+1\) 则输出 \(1\),否则输出 \(0\)。
欧拉路径:如果图 \(G\) 中的一个路径包括每个边恰好一次,则该路径称为欧拉路径。
又因为字典序要最小,故上述的“一定顺序”就是贪心:能走 \(0\) 就走 \(0\),否则走 \(1\)。
那怎么处理将欧拉回路上的边都反向呢?在此之前,笔者先口胡证明一下一条欧拉回路上 \(u\) 到 \(v\) 路径的数量不会因为取反方向操作而改变。理由如下:发现在欧拉回路上的每条边无非就是让权值 \(+1\) 或者 \(-1\),因为我们要从一个权值 \(x\) 又走回这个权值 \(x\),所以在这条回路上每一条 \(u\rightarrow u+1\) 的边都必然对应这一条 \(u+1 \rightarrow u\) 的边,所以取反方向之后两者互换,数量不会改变。
然后我们再回到那个问题上。上述那条结论就能让我们这么走:
if(e[res + maxn][0]/*存在 res -> res-1 这样一条路径*/ and e[res - 1 + maxn][1]/*存在 res-1 -> res 这样一条路径*/)
//在满足上述条件的情况下,说明当前可能走在一条回路上,故先走 +1 和先走 -1 实质上是等价的
//要保证字典序最小,我们当然就会选择先走 -1
e[res + maxn][0] -= 1, res -= 1, printf("0");
为了保障其可行性,@tzc_wk大佬给出了原图任意一条欧拉回路代表的字符串都可以由原字符串进行一系列操作得到的证明。简而言之就是假如两条路径走法出现分歧,那么其中一条翻转之后必然与另一条重合。
然后再根据“能走 \(0\) 就走 \(0\) 的原则”,我们就可以完善对于最终路径的走法了:
rep(i, 1, n)
if(e[res + maxn][0] and e[res - 1 + maxn][1])
e[res + maxn][0] -= 1, res -= 1, printf("0");
else if(e[res + maxn][1]) e[res + maxn][1] -= 1, res += 1, printf("1");
else e[res + maxn][0] -= 1, res -= 1, printf("0");
然后我这种菜的选手就会问:不是能走 \(0\) 就走 \(0\) 么?上面的实现中分明先走了 \(1\)。其原因是:上文提到了,我们最后一定是走一条欧拉路径,而如果一味的走 \(0\) 不走 \(1\) 只会导致后面很多的权值走不到。相比之下,能够先走 \(0\) 后走 \(1\)(即两者可以互换顺序)的情况只出现在回路上,即上述的第一个判断。
最后,复杂度 \(\mathcal{\text{O}}(\sum len(s))\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int maxn = 5e5 + 5;
int t, n, s[maxn], e[maxn << 1][2];
char c[maxn];
int main(){
scanf("%d", &t);
while(t--){ s[0] = 0;
scanf("%s", c + 1), n = strlen(c + 1);
rep(i, 1, n) s[i] = s[i - 1],
s[i] += c[i] == '0' ? -1 : 1;
rep(i, 0, n - 1) e[maxn + s[i]][c[i + 1] - '0'] += 1;
int res = 0;
rep(i, 1, n)
if(e[res + maxn][0]/*存在 res -> res-1 这样一条路径*/ and e[res - 1 + maxn][1]/*存在 res-1 -> res 这样一条路径*/)
//在满足上述条件的情况下,说明当前可能走在一条回路上,故先走 +1 和先走 -1 实质上是等价的
//要保证字典序最小,我们当然就会选择先走 -1
e[res + maxn][0] -= 1, res -= 1, printf("0");
else if(e[res + maxn][1]) e[res + maxn][1] -= 1, res += 1, printf("1");
else e[res + maxn][0] -= 1, res -= 1, printf("0");
printf("\n");
}
return 0;
}
感谢阅读。

浙公网安备 33010602011771号