早 8 點的維 C :零度果坊早橙好 NFC 白桃芭樂果汁 280g * 9 瓶 29.9 元
一、題目
二、解法
沒有什麼好的想法,就從圖論的角度入手吧。
要根據題目特性來建圖,首先要考慮把什麼當做點的問題,如果把字串的元素當成點是不好表示 子串必須包含同樣數量的字元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(); }