1. 程式人生 > 實用技巧 >【CF578E】Walking!(貪心)

【CF578E】Walking!(貪心)

點此看題面

  • 給定一個長度為\(n\)的由LR構成的字串\(s\)
  • 求構造一個排列\(p_i\),滿足\(\forall i\in[1,n),s[p_i]\not=s[p_{i+1}]\),且滿足\(p_i>p_{i+1}\)\(i\)的個數最小。
  • \(n\le10^5\)

可以證偽的貪心

考慮一個貪心,就是每次儘可能選取長度最大的符合條件的子序列,然後重複取若干次直至把整個序列取完。

這一過程的實現是非常簡單的,只要對LR兩種字元各開一個\(set\),每次\(lower\_bound\)找後繼就好了。

然而,這個貪心是會被卡掉的。

唯一一種\(Hack\)資料

會被卡掉是毋庸置疑的,但能卡掉這個貪心的資料實際上只有一種。

L為例,如果它選中的R後面全都是R,且在此之前還有R沒有匹配,這時候貪心就會有問題。

因為根據貪心的思路我們會先匹配後面的R再折回去找之前的LR,那麼不如先回去匹配前面的R再一路匹配下來。

只要特判一下這種情況就可以了。

程式碼:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,v[N+5];char s[N+5];set<int> S[2];
I int Solve(RI x)//第一個字元為x
{
	RI i,t=0;for(i=1;i<=n;++i) S[s[i]=='R'].insert(i);//分字元插入set
	#define Nxt(x,p) S[x].lower_bound(p)//後繼
	#define Mn(x) *S[x].begin()//最小值
	RI p=0;for(i=1;i<=n;S[x].erase(v[i]=p),++i,x^=1)
		p=Nxt(x,p)==S[x].end()?(++t,Mn(x)):*Nxt(x,p),//找到下一位
		Nxt(x^1,p)==S[x^1].end()&&p^Mn(x)&&Mn(x)<Mn(x^1)&&(p=Mn(x),++t);//如果之後全是同樣的字元,不如先選前面的
	return t;
}
int main()
{
	RI i,t=0;for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i) s[i]=='L'?++t:--t;//比較兩種字元誰更多
	for(printf("%d\n",t?Solve(t==-1):Solve(s[1]=='R')),i=1;i<=n;++i) printf("%d ",v[i]);return 0;
}