1. 程式人生 > 實用技巧 >題解 CF1458D 【Flip and Reverse】

題解 CF1458D 【Flip and Reverse】

題面

\(T\) 組詢問,每次給定一個字串,每次可以選擇一個 10 數量相等的字串,然後把字串前後翻轉並 01 翻轉。求最後得到的字典序最小的字串。

資料範圍 : \(T, n \le 5 \times 10^5, \sum n \le 5 \times 10^5\)

題解

剛才有個群友問我 Z 菜雞發生腎摸事了,我說怎麼回事?給我發了幾張 CF 分數對比圖,我一看!嗷!原來是昨天,我打了一場 CF,爆零了,掉分到 newbie ,又被嘲諷了。

首先假設我們有一個 \(x\) 值,遇到 \(0\), 讓 \(x\) 減少 \(1\);遇到 \(1\)\(x\) 增加 \(1\)

考慮按照原字串建立一張圖。對於每一個 \(x\) 值建立一個點。例如說現在的 \(x\) 值為 \(t\), 遇到了一個 \(1\), 然後我們從 \(t\)\(t + 1\) 連一條無向邊。

選擇一個 10 數量相等的字串,前後的 \(x\) 值一定相等。於是這就形成了一個環。

考慮將這個字串取反,其實相當於從這個點繞著這個環走一圈。

然後我們要求的是這張圖的最小字典序的尤拉路徑。

可以考慮貪心,能向小的數走就往小數的走。

怎麼判定數 \(t\) 能不能往小數 \(t - 1\) 走?首先一定要有 \(t\)\(t - 1\) 的這條邊,如果 \(t\) 有到 \(t + 1\)

的邊那麼 \(t\)\(t - 1\) 的邊數至少為 \(2\) (肯定要返回 \(t\))。

程式碼:

#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++) 
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long 
#define db double
#define pii pair<int, int>
#define mkp make_pair
using namespace std;
const int N = 5e5 + 7;
const int inf = 1e9 + 7;
int n, cnt[N << 1];
char s[N];
void Main() {
	scanf("%s", s + 1), n = strlen(s + 1);
	int now = 0;
	L(i, 1, n) {
		if(s[i] == '1') cnt[N + now] ++, now ++; 
		else now --, cnt[N + now] ++;
	}
	now = 0;
	L(i, 1, n) {
		if(cnt[N + now - 1] > 0 && (!cnt[N + now] || cnt[N + now - 1] > 1)) --now, cnt[N + now] --, putchar('0');
		else cnt[N + now] --, now ++, putchar('1');
	}
	puts("");
	L(i, -n, n) cnt[N + i] = 0;
}
int main() {
	int T; scanf("%d", &T);
	while(T--) Main();
	return 0;
}