[CSP-S 2023] 消消乐 & CF1223F 题解

发布时间 2023-11-09 09:43:42作者: ft61

LG9753 CF1223F


我们称一个字符串是可消除的,当且仅当可以对这个字符串进行若干次操作,使之成为一个空字符串。其中每次操作可以从字符串中删除两个相邻的相同字符,操作后剩余字符串会拼接在一起。

You are trying to push array elements to the stack in the order \(s_1,s_2,s_3,…,s_l\). Moreover, if the stack is empty or the element at the top of this stack is not equal to the current element, then you just push the current element to the top of the stack. Otherwise, you don't push the current element to the stack and, moreover, pop the top element of the stack. If after this process the stack remains empty, the array s is considered stack exterminable.

空串合法
如果 \(A\) 合法,那么 \(xAx\) 合法
如果 \(A,B\) 合法,那么 \(AB\) 合法

题意是可以相互转化的。和括号匹配很像,区别是不分左右

sol 1

\(f[i]\) 表示以 \(i\) 结尾的合法子串数量,\(g[i]\) 为以 \(i\) 为右端点的最短合法子串的左端点,则 \(f[i]=f[g[i]-1]+1\)

剩下的问题是计算 \(g[i]\)。暴力:从 \(j=g[i-1]\) 开始不断跳 \(g[j]\)\(s_{j-1}=s_{j}\),则 \(g[i]=j-1\)

考虑维护一个哈希表 \(h[j,x]\) 表示从 \(j\) 开始跳,最大的 \(j\) 满足 \(s_{j-1}=x\),则 \(g[i]=h[i-1,s_{i}]-1\)\(h[i]\) 可以先从 \(h[g[i]]\) 继承过来,再令 \(h[i,s_{i-1}]=i\)

时间复杂度 \(O(n)\)

sol 2

结论:模拟 CF 题意,\([l,r]\) 合法 \(\iff[1,l-1]\)\([1,r]\) 对应的栈相等

证明:如果 \([l,r]\) 中有字符和 \([1,l-1]\) 对应的栈中字符消了,那么由于 \([1,l-1]\)\([1,r]\) 对应的栈相等,\([l,r]\) 中一定又有字符补上了,且顺序是对的

用哈希表维护 \([1,l-1]\) 对应的栈的哈希值即可统计答案

code
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std; using namespace __gnu_pbds; using namespace __gnu_cxx;
#define For(i,x,y,...) for(int i=x,##__VA_ARGS__;i<=(y);++i)
#define rFor(i,x,y,...) for(int i=x,##__VA_ARGS__;i>=(y);--i)
#define Rep(i,x,y,...) for(int i=x,##__VA_ARGS__;i<(y);++i)
#define pb emplace_back
#define sz(a) int((a).size())
#define all(a) begin(a),end(a)
#define fi first
#define se second
typedef long long LL; typedef vector<int> Vi; typedef pair<int,int> Pii;
auto ckmax=[](auto &x,auto y) { return x<y ? x=y,true : false; };
auto ckmin=[](auto &x,auto y) { return y<x ? x=y,true : false; };
sfmt19937 mt(chrono::steady_clock::now().time_since_epoch().count());
int rnd(int l,int r) { return uniform_int_distribution<>(l,r)(mt); }
template<typename T=int>T read() { T x; cin>>x; return x; }

typedef unsigned long long uLL;
const int N = 2e6+5;
int n,tp;
uLL pre[N],hsh[N];
char s[N],stk[N];
gp_hash_table<uLL,int> cnt;

signed main() {
#ifdef D
	freopen("in","r",stdin); freopen("out","w",stdout);
#endif
	ios::sync_with_stdio(0);cin.tie(0);
	cin>>n>>s+1;
	For(i,1,n) {
		if( tp && stk[tp] == s[i] ) --tp;
		else stk[++tp] = s[i], pre[tp] = pre[tp-1] * 131 + stk[tp];
		hsh[i] = pre[tp];
	}
	LL ans = 0;
	cnt[0] = 1; For(i,1,n) ans += cnt[hsh[i]], ++cnt[hsh[i]];
	cout<<ans;
	return 0;
}

sol 3

注意到消除满足“结合律”(先消哪里无所谓),不满足“交换律”(不能交换字符再消),可以(?)联想到矩阵乘法

引理:\(MM^{-1}=M^{-1}M=I\)\(I\) 是单位矩阵

对于每种字符,随机一个矩阵 \(M\),第奇数个对应 \(M\),第偶数个对应 \(M^{-1}\)\([l,r]\) 合法 \(\iff[l,r]\) 对应的矩阵乘积为 \(I\)