前序
由於比賽地址是在計蒜客,所以本篇題解省略題的詳細地址,只給出比賽地址,對於題目有興趣的可以點擊下方鏈接
比賽地址
A. 盤他!
截圖
題目類型
字符串模擬
題意簡述
求解子串在首尾可以無線相接的母串中不重疊出現k次,需要經過多少個字符(從頭開始計算)
題解
本題的解答概括爲兩個部分,
- 第一個部分是KMP匹配
- 第二個部分是尋找循環節
kmp匹配的目的是爲了判斷是否有可能存在不重疊出現k次的情況,並且爲尋找循環節和最後位置提供機會
這裏先給出題中樣例,並進行一些解釋
輸入樣例
5 3 2
ououo ouo
輸出樣例
8
5 表示單一母串長度
3 表示子串長度
2 表示子串不重疊出現次數
經過頭尾鏈接子串出現 2 次的情況是如下
ououo ououo
通過取餘的方式解決頭尾無限相接的問題,這樣就可以正常的進行KMP匹配惹
代碼
const int MAXN=2e5+50;
int nex[MAXN];
int next_start[MAXN];
int start[MAXN];
int first_visit[MAXN];
LL first_visit_ans[MAXN];
//獲取母串的next數組
void get_next(string P){
//memset(nex, 0, sizeof(nex));
int len = P.size();
nex[0] = -1;
int i = 0, j = -1;
while(i < len){
if (j == -1 || P[i] == P[j]) nex[++i] = ++j;
else j = nex[j];
}
}
int KMP(string P, string T){
int Plen = P.size();
int Tlen = T.size();
//cout << "Plen " << Plen << " " << "Tlen " << Tlen << '\n';
int i = 0, j = 0;
int sum = 0;
//%Tlen就能夠枚舉所有的起始點,即使是超過了,也能回到想要的位置
while(i - Plen < Tlen - 1){
if (j == -1 || P[j] == T[i % Tlen]) i++, j++;
else j = nex[j];
if (j == Plen) start[sum++] = i - Tlen,j = nex[j];
//這裏是對尋找到的位置返回記錄匹配成功的第一個點,通過start數組就能找到所有尋找到的匹配成功的位置
}
return sum; // 查找到的匹配成功的個數
}
int main(){
int t; RD(t);
FOR_1(e, 1, t){
int n, m, k; RD(n, m, k);
string s1, s2; cin >> s1 >> s2;
get_next(s2);
int num = KMP(s2, s1);
int x, flag, round;LL ans = 0;
if (num == 0) {
OT("-1");
continue;
}//一個匹配都沒找到
for(int i = 0;i < s1.size(); i++){
next_start[i] = -1;
first_visit[i] = -1;
}
for(int i = 0; i < num; i++){
next_start[start[i]] = start[i];
}
x = -1;
for(int i = n * 2 - 1; i >= 0; i--){
if (next_start[i % n] != -1) x = next_start[i % n];
else next_start[i % n] = x;
}
x = 0, ans = 0, flag = 0; //初始化
for(LL i = 1; i <= k; i++){
ans += (next_start[x] - x + n) % n;
x = next_start[x];
if(flag == 0)
{
if(first_visit[x] != -1)
{
flag = 1;
round = i - first_visit[x]; //當前位置與出現位置的距離,即循環節
ans += ((k-i) / round) * (ans - first_visit_ans[x]);
i += (k-i)/round*round;
}
first_visit[x] = i;
first_visit_ans[x] = ans;
}
ans += m;//完整匹配的距離即爲子串的長度
x = (x + m) % n;
}
printf("%lld\n",ans);
}
}