这次的codeforces真是无与伦比的水,但是即使如此,这道几百个人做出来的D我依然没有过,实在是sad story。
具体来说题目的话,题目是说给定每个字母的权值(一共只有26个小写英文字母的组合单词),以及一个字符串,找出这个字符串的子字符串中的满足
1. 除去第一个和最后一个字母中间加和为0的。
2. 第一个字母和最后一个字母相同的。
输出这样的字符串有几个。
刚开始我竟然想用线段树+RMQ的思想去做,真是罪过罪过,主要是当时看到区间的东西就一下子想到线段树了卧槽。
后来比赛中被一位好友说了一句可以用前缀和做,我灵光一闪,写出了一个O(n^2)的答案,妈蛋这不是跟暴力枚举一样的复杂度嘛。后来比赛结束看题解的时候才发现了问题所在。我的做法是检测当前前缀和,如果这个前缀和的数值以前出现过,那么就依次比较这些前缀和的对应字母和自己的对应字母是不是相等,于是这样就导致了O(n^2)的TLE。
后来看到了题解的思想是这样的。扫一遍的过程中,检测当前字母,记录字母所对应的前缀和,记录该前缀和之前出现的次数。举个例子比较好说,比如说现在扫到字母a对应的前缀和为36,并且36之前出现过1次(这一次也是字母a的,因为我们先枚举的是字母),那么这个时候明显的,满足条件的sub string多了一个。如果36出现了两次呢?那思考一下容易得出,sub string应该这时候是加了两个的,这个也就是处理的精妙之处所在。
所以总的来说,数据结构就该是一个map数组,把每一个前缀和对应的次数存在map里面,然后每一个字母对应一个map。
#include <iostream> #include <string> #include <stdio.h> #include <vector> #include <queue> #include <algorithm> #include <functional> #include <map> #include <string.h> #include <iomanip> #include <utility> #define INF 0x3fffffff using namespace std; int n, m, k, t, p, q, x, y; string s; int w[30]; long long sum[100005]; map<long long, int> mymap[30]; int main() { for (int i = 0; i < 26; i++) { cin >> w[i]; } long long res = 0; cin >> s; for (int i = 0; i < s.size(); i++) { sum[i] = sum[i - 1] + w[s[i] - 'a']; } for (int i = 0; i < s.size(); i++) { int index = s[i] - 'a'; if (i == 0) { mymap[index][sum[i]]++; continue; } if (mymap[index].count(sum[i - 1])) { res += mymap[index][sum[i - 1]]; } mymap[index][sum[i]]++; } cout << res << endl; return 0; }