1. 程式人生 > 資訊 >早 8 點的維 C :零度果坊早橙好 NFC 白桃芭樂果汁 280g * 9 瓶 29.9 元

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