题意:f[0] = "0", f[1] = "1",接下来f[i] = f[i-1] + f[i-2],相当于字符串拼接。然后给出一个n和一个串s,问f[n]里面有多少个s。
思路:在int范围内的f[n]是n=31的时候,但是匹配的s的长度只有1e5,这时候n=27刚好大于它,因此可以先求解出n<=27的串的情况,然后由这些串来得到n>27的时候的情况。
比赛的时候想着递归算,结果爆栈。
看别人的代码之后我的理解是这样的:
(1)当n<=28的时候,可以直接kmp算。
(2)当n>28的时候,我们可以得到下面的递推式:(ans[i]表示f[i]里面有多少个s.可以先kmp求出ans[i](25 <= i <= 28) ).
先定义odd = ans[27] - ans[26] - ans[25](即得到s[27]由s[26]和s[25]拼接起来的时候能多得到的答案).
even = ans[28] - ans[27] - ans[26](即得到s[28]由s[27]和s[26]拼接起来的时候能多得到的答案).
因为len(s[27])和len(s[28])一定大于匹配的s,因此不用每次都暴力求解拼接起来的时候能多得到的答案,只需要在i为奇数的时候加上odd,i为偶数的时候加上even就可以得到拼接起来能多得到的答案了。
一开始以为奇偶是一样的情况,但是WA8之后问别人才知道是不一样的。
找6号串和找5号串的情况,可以发现6号串可以出现在8号串连接处而不能出现在7和9号串连接处。5号串能出现在7和9号的连接处但是不能出现在8号串的连接处,所以应当是分开考虑的。
这就是WF最水的题目之一...
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define N 105 4 #define M 100010 5 typedef long long LL; 6 int nxt[M], len[N], sz; 7 string str, s[N]; 8 LL ans[N]; 9 10 void Make_Next() { 11 int i, j; j = nxt[0] = -1; i = 0; 12 while(i < sz) { 13 while(j != -1 && str[i] != str[j]) j = nxt[j]; 14 if(str[++i] == str[++j]) nxt[i] = nxt[j]; 15 else nxt[i] = j; 16 } 17 } 18 19 LL Kmp(int id) { 20 int i = 0, j = 0, ans = 0; 21 while(i < len[id]) { 22 while(-1 != j && s[id][i] != str[j]) j = nxt[j]; 23 i++; j++; 24 if(j >= sz) { ans++; j = nxt[j]; } 25 } 26 return ans; 27 } 28 29 int main() { 30 int n, cas = 1; 31 s[0] = "0", s[1] = "1"; len[0] = len[1] = 1; 32 for(int i = 2; i <= 28; i++) s[i] = s[i-1] + s[i-2], len[i] = len[i-1] + len[i-2]; 33 while(~scanf("%d", &n)) { 34 cin >> str; 35 sz = str.length(); Make_Next(); 36 if(n <= 28) { printf("Case %d: %lld ", cas++, Kmp(n)); continue; } 37 for(int i = 25; i <= 28; i++) ans[i] = Kmp(i); 38 LL odd = ans[27] - ans[26] - ans[25], eve = ans[28] - ans[27] - ans[26]; // 拼接起来能够得到的贡献 39 for(int i = 29; i <= n; i++) { 40 ans[i] = ans[i-1] + ans[i-2]; 41 if(i & 1) ans[i] += odd; 42 else ans[i] += eve; 43 } 44 printf("Case %d: %lld ", cas++, ans[n]); 45 } 46 return 0; 47 }