hohor學習日記:hiho一下 第五十八週
http://hihocoder.com/contest/hiho58/problem/1
題意分析
給定字串S,判定S是否存在子串S’滿足"aa…abb…bcc…c"的形式。其中abc為連續的三個字母,且a,b,c的數量相同。
原題目中數量相等的連續n(n>3)個字母也是可行的,而實際上當n>3時一定包含有n=3的情況。比如"abcd"就包含有"abc"和"bcd"兩個合法子串。
演算法分析
最基本的思路為對S的每一個子串進行判定是否滿足要求。列舉子串的起點、終點以及檢查是否合法。
假設S的長度為N,則時間複雜度為O(N^3)。
For i = 0..N- 1
For j = 0..N-1
check(S[i..j])
End For
End For
這樣的做法對於N稍大的資料來說就會超過時限。
進一步考慮,由於合法子串中相同的字母總是連續的,我們不妨用(c,n)來表示一串連續相同的字母,比如"aaa"表示(a,3),"bb"表示為(b,2)。
我們將整個字串S用(c,n)表示,得到{(c[1], n[1]),(c[2],n[2]),…,(c[t],n[t])}的序列。其中我們合法的子串也可以表示為{(a,n),(b,n),(c,n)}。
則演算法改變為在序列{(c[1], n[1]),(c[2],n[2]),…,(c[t],n[t])}中判定是否存在連續的3個元素滿足c[i],c[i+1],c[i+2]連續且n[i] == n[i+1] == n[i+2]。
預處理時間為O(N),得到的序列長度最大為N,所以整體的時間複雜度降低為O(N)。
For i = 1 .. t-2
If (c[i]+1 == c[i+1] and c[i+1]+1 == c[i+2]) and (n[i] == n[i+1] == n[i+2])
Return True
End If
End For
然而實際執行會發現,這個演算法是不正確的。比如:“aaaabbccc”,其對應的序列為{(a,4),(b,2),(c,3)},根據我們上面的演算法並不能找到合法子串。但實際上存在合法子串"aabbcc"。
很顯然,問題出在我們對於n[i],n[i+1],n[i+2]的判定上。通過上面的反例我們可以發現,在子串中n[i],n[i+2]的值其實是可以變動的,唯一固定的是n[i+1]的值。當n[i]>n[i+1]時,我們只要刪去前面的若干個字母,就能夠使得n[i]==n[i+1]。同理對於n[i+2]>n[i+1]時,我們刪去後面的字母。因此只要有n[i]>=n[i+1],n[i+2]>=n[i+1],就一定能夠通過變換使得n[i] == n[i+1] == n[i+2]。
改正後我們的演算法程式碼為:
For i = 1 .. t-2
If (c[i]+1 == c[i+1] and c[i+1]+1 == c[i+2]) and (n[i] >= n[i+1] and n[i+1] <= n[i+2])
Return True
End If
End For
結果分析
在實際的比賽中,該題目的通過率僅為26%。
但根據賽後的統計結果,大部分的選手都使用了樸素的演算法通過了規模較小的資料點。在該題目上獲取了10~60不等的分數。
其中比較有意思的是有一位選手僅僅判定連續3個字母是否連續,也獲得了60的分數。
而分佈在70~90分數段的程式,隨機抽取了若干樣本,發現大多數都是想到了正確演算法的。而導致他們丟分的主要原因則是多組資料產生的初始化問題。
AC程式碼:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int Mod = 1e9 + 7;
const int maxn = 1e7 + 5;
const double eps = 0.00000001;
const int INF = 0x3f3f3f3f;
int c[maxn], m[maxn];
bool solve() {
LL n;
memset(c, 0, sizeof(c));
memset(m, 0, sizeof(m));
cin >> n;
string ss;
cin >> ss;
int cnt = 0;
c[0] = ss[0];
m[0] = 1;
for (LL i = 1; i < n; i ++) {
if(ss[i] != ss[i - 1]) {
cnt ++;
c[cnt] = ss[i];
}
m[cnt]++;
}
for (LL i = 1; i <= cnt - 1; i ++)
if(c[i - 1] + 1 == c[i] && c[i] + 1 == c[i + 1] && m[i - 1] >= m[i] && m[i] <= m[i + 1]) return true;
return false;
}
int main()
{
int T;
cin >> T;
while(T--) {
if(solve()) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}