题意 : 小Z决定向女神表白,但性格腼腆的小Z决定隐晦一点,截取一段包含'L'、'O'、'V'、'E'的英文。(顺序不限)小Z想起之前小D送给他一本英文书,决定在这里面截取一段话,小Z发现有好多种方案来截取这段话。你能知道小Z能有多少种方案截取这段话么?为了简化问题,英文文本讲不会出现空格、换行、标点符号及只有大写的情况。
分析 : 最原始的想法有两个,一个是粗暴地枚举所有大于等于四的子区间判断是否满足条件最后计算贡献,另一个就是枚举左端点 L,假设使得其包含 "LOVE" 的第一个右端点为 R,那么以这个“左端点的贡献”就是 len - R ( len 是主串长度 ),这样做就避免了枚举到重复的部分且降低了第一种做法的复杂度,但是依旧过不了这题。现在就来优化第二种做法,假设当前有一个子区间( L, R ) 是符合条件的则其贡献为 len - R ,那么枚举下一个的时候即 L + 1,对于这个左端点我们会发现符合条件的右端点( 令为 R' )肯定大于或等于 R 即 R' ≥ R,为什么? 首先想到一点!如果当前使得左端点 L 满足条件的最左右端点是 R ,也就是说加上了 R 才使得 ( L, R ) 满足条件,即 R 这个位置必定是 "LOVE" 的其中一个,那么对于 L + 1 来说其区间元素相较 L 少了 L 这个位置的字符元素,而即使多了一个字符元素的 L 都要枚举到 R 才能使得 ( L, R ) 满足条件,那使 L + 1 满足条件的右端点 R' 必定是满足 R' ≥ R 的,故尺取法可行!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int num[4]; char s[maxn]; int Which(char ch) { if(ch == 'L') return 0; if(ch == 'O') return 1; if(ch == 'V') return 2; if(ch == 'E') return 3; return -1; } bool IsOk() { for(int i=0; i<4; i++) if(num[i] == 0) return false; return true; } int main(void) { int nCase; scanf("%d", &nCase); while(nCase--){ scanf("%s", s); int len = strlen(s); if(len < 4){ puts("0"); continue; } int Now = 0; int L, R; long long ans; L = R = 0; ans = 0; memset(num, 0, sizeof(num)); int idx = Which(s[L]); if(idx!=-1) num[idx]++, Now++; while(L <= R && R < len){ if(IsOk()){/// Judge interval (L, R) if have "LOVE" ans += len - R; idx = Which(s[L++]);/// Calculate Next one if(idx != -1)/// if s[L] is one of "LOVE" num[idx]--; }else{ idx = Which(s[++R]); if(idx != -1) num[idx]++; } } printf("%lld ", ans); } return 0; }