1. 程式人生 > 其它 >字串魔法hard(前後綴與貪心)

字串魔法hard(前後綴與貪心)

技術標籤:基礎演算法專題三 貪心演算法一些暫未分類的題解字串演算法動態規劃貪心演算法程式人生

字串魔法hard(前後綴與貪心)

連結:https://ac.nowcoder.com/acm/contest/9680/C
來源:牛客網

description:

白淺獲得了一個僅由A和B組成的字串。他可以至多使用一次魔法來改變字串。 魔法:選擇一個子串,滿足子串中 A 的數量等於 B
的數量,然後按字典序從小到大排序這個子串,即變成形如AAA…AAABBB…BBB這樣的字串(A和B的數量均與原來的子串相同)。
他想知道,在他至多使用一次魔法後,這個字串能夠出現的最長的字典序不遞減的子串的長度為多少。

輸入描述: 第一行包含一個整數n,代表字串的長度。 接下來一行給出一個長度為n的字串。
1≤n≤200000

輸出描述: 輸出一行一個正整數表示答案。

Sample:

輸入 :
6
AABBAA
輸出:
6

解題思路:

  • 首先,我們可以很容易想到拿一個字首陣列去儲存AB的數量差,我們這裡設A為正,B為負,得到一個prefix_sum陣列,prefix_sum[i]=0即代表s到si的子串中A、B數量相等。
  • 對於字首陣列prefix_sum,它可以令我們知道哪些子串是AB數量相等的,具體怎麼實現,就是從暴力開始,最後發現其有一定的貪心性質,然後利用一個map實現優化,當然直接一個數組也行,但其下標有正有負所以需要做一些處理,我這裡就直接map了。具體看程式碼註釋。
  • 知道了哪些子串AB數量相等後,我們還要知道連續A子串和連續B子串的資料,根據題意得知最後要求的是最長的字典序不遞減的子串的長度,所以可以設定一個preA陣列sufB陣列分別表示以s[i]為末尾的連續A子串長度和以s[i]為開頭的連續B子串長度

原始碼與註釋:

#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
map<int, int> M; //M[k]存放字串s中字首和為k的最後下標i, 即最大的i使s[1]-- - s[i]的代數和為k
char s[N]; int n;//字串長度<=200000 int prefix_sum[N];//以A為正,B為負,存放字首和,和為0即代表A、B數量相等 int preA[N];//preA[i]表示以s[i]為末尾的連續A子串長度 int sufB[N];//sufB[i]表示以s[i]為開頭的連續B子串長度 //preA和sufB的前後綴性質是由題目中最終要求的 字典序不遞減 決定的。 int main() { cin >> n >> s + 1; //求字首和prefix_sum for (int i = 1; i <= n; i++) { if (s[i] == 'A' ) prefix_sum[i] = prefix_sum[i - 1] + 1; else prefix_sum[i] = prefix_sum[i - 1] - 1; } //求preA和sufB以獲得連續子串的資料 for (int i = 1, j = n; i <= n; i++, j--) { if (s[i] == 'A') preA[i] = preA[i - 1] + 1; if (s[j] == 'B') sufB[j] = sufB[j + 1] + 1; } //求出sufB和preA後,按照暴力的思想可以像下面這樣遍歷,但無奈n的規模有200000,顯然是不行的 //int MAX = -1; //for (int i = 0; i <= n; i++) { // for (int j = i + 2; j <= n; j++) { // if (prefix_sum[j] - prefix_sum[i] == 0) { // MAX = max(MAX, j - i + preA[i] + sufB[j + 1]); // } // } //} //cout << MAX << endl; //以下用一個map M存放字首和的一些資訊以做優化 for (int i = n; i >= 1; i--) { if (M[prefix_sum[i]] == 0) { M[prefix_sum[i]] = i;//其中M[k]存放字串s中字首和為k的最後下標i,即最大的i使s[1]---s[i]的代數和為k } } //這裡有點貪心的思維,即在求M時只看最後的下標 //這樣一來,我們知道s[1]---s[i]字首和為prefix_sum[i],則最長的字首和為k的子串為s[1]---s[ M[prefix_sum[i]] ], //所以有了M,我們就知道以s[i+1]為頭的最長的AB數相等的子串為s[i+1]---s[ M[prefix_sum[i]] ],其長度為M[prefix_sum[i]]-i //所以我們也不用去做過多的迴圈了,一輪貪心選擇即可完成 int MAX = -1; for (int i = 1; i <= n; i++) { //以s[i+1]為頭的最長子串及連線兩邊AB後的長度。 int nowLength = (M[prefix_sum[i]] - i) + (preA[i] + sufB[M[prefix_sum[i]] + 1]); MAX = max(MAX, nowLength); } cout << MAX << endl; return 0; }

純原始碼:

#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
map<int, int> M; 
char s[N];
int n,prefix_sum[N],preA[N],sufB[N];
int main()
{
	cin >> n >> s + 1;
	for (int i = 1; i <= n; i++) {
		if (s[i] == 'A' ) 
			prefix_sum[i] = prefix_sum[i - 1] + 1;
		else 
			prefix_sum[i] = prefix_sum[i - 1] - 1;
	}
	for (int i = 1, j = n; i <= n; i++, j--) {
		if (s[i] == 'A')
			preA[i] = preA[i - 1] + 1;
		if (s[j] == 'B')
			sufB[j] = sufB[j + 1] + 1;
	}
	for (int i = n; i >= 1; i--) {
		if (M[prefix_sum[i]] == 0) {
			M[prefix_sum[i]] = i;
		}
	}
	int MAX = -1;
	for (int i = 1; i <= n; i++) {
		int nowLength = (M[prefix_sum[i]] - i) + (preA[i] + sufB[M[prefix_sum[i]] + 1]);
		MAX = max(MAX, nowLength);
	}
	cout << MAX << endl;
	return 0;
}

結果:

最後貼一張AC圖
在這裡插入圖片描述


就醬:)