P9753 [CSP-S 2023] 消消乐
这题想到了 50pts,想不出来怎么优化了。
50pts:考虑枚举子串左端点,模拟操作过程,直接用栈模拟,遇到相同的则删去,如果某个时刻栈为空,那么合法子串数加一。
考场上只想到为空的时候可消除,下面的性质才是关键的。因为我们枚举左端点,每次只判断了 \([l,r]\) 的可消除子串,这样子无论如何都要 \(O(n^2)\)。所以我们需要考虑如何在枚举左端点的同时找到其他左端点的可消除子串。
栈的过程其实启发我们:对于两个时刻 \(l\) 和 \(r\),如果两个时刻栈是一样的,那么说明 \([l+1,r]\) 是可消除的。
所以我们如果知道某个时刻的出现次数 \(k\),那么贡献即为 \(C_k^2\),即取两个时刻作为左右端点的方案数。
所以我们可以以左端点为 \(1\) 跑一次模拟,把每个时刻的栈都存起来,这个过程可以用哈希实现,再放到 map 中记录次数,set 存有多少种哈希值。
可以看出,跑一次模拟可以找到所有可消除子串的左右端点。
如果用 unordered_map,复杂度为 \(O(n)\)。
总结:没有发现关键性质,转化贡献的计算。
#include <bits/stdc++.h>
typedef long long ll;
#define int unsigned long long
int read() {
int x = 0, f = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') f = -1;
c = getchar();
}
while(isdigit(c)) {
x = (x << 3) + (x << 1) + (c - '0');
c = getchar();
}
return x * f;
}
int n, p = 131;
int ans;
int hsh[2000010];
char s[2000010];
std::stack<int> st, pos;
std::map<int, int> mp;
std::set<int> S;
void Solve() {
n = read();
std::cin >> s + 1;
mp[0]++;
S.insert(0);
for(int i = 1; i <= n; i++) {
if(!st.empty() && st.top() == s[i] - 'a') {
mp[hsh[pos.top() - 1]]++;
hsh[i] = hsh[pos.top() - 1];
st.pop();
pos.pop();
}
else {
st.push(s[i] - 'a');
pos.push(i);
hsh[i] = hsh[i - 1] * p + s[i];
S.insert(hsh[i]);
mp[hsh[i]]++;
}
}
for(int num : S) ans += mp[num] * (mp[num] - 1) / 2;
std::cout << ans << "\n";
}
signed main() {
Solve();
return 0;
}