P9753 [CSP-S 2023] 消消乐 题解

分析

弱智题。

这题很像。

想到 DP。定义状态函数 \(\mathit{f}_{i,j}\) 表示区间 \([i,j]\) 中的答案。这玩意直接暴力 \(O(n^3)\) 算区间 DP,能有 \(35\) 分。

考虑优化,如果定义 \(\mathit{f}_{i,j}=0/1\) 表示子串 \([i,j]\) 能否是一个匹配的,则能用栈搞到 \(O(n^2)\)。能有 \(50\) 分。

优化不下去了。

放弃这个思路,考虑把状态函数转移到一维的。再重新定义一下:\(\mathit{f}_{i}\) 表示以 \(i\) 为某个字串开头时,能得到的匹配的子串数量。有转移方程:\(\mathit{f}_{i}=\mathit{f}_{j+1}+1\),其中 \([i,j]\) 是一个匹配的子串。必须保证 \(j\)\(i\) 右边的第一个能够匹配的位置,因为在转移方程中的 \(+1\) 代表区间 \([i,j]\) 的一个匹配子串贡献。若 \(j\) 不是,可能出现如 \(abbaabba\) 的情况(\(i=1,j=8\))。可以自己理解一下。

对于找第一个 \(j\),如果暴力,可以得到代码:

il void solve(){
	cin>>n>>s;int ans=0;
	for(re int l=0;l<n;++l){
		stack<char> st;
		for(re int r=l;r<n;++r){
			if(st.empty()){
				st.push(s[r]);
				continue;
			}
			char now=st.top();
			if(now==s[r]) st.pop();
			else st.push(s[r]);
			if(st.empty()) vis[l][r]=1;
		}
	}
	for(re int i=n-1;i>=0;--i){
		for(re int j=i+1;j<n;++j){
			if(vis[i][j]){
				f[i]=f[j+1]+1;break;
			}
		}
	}
	for(re int i=0;i<n;++i) ans+=f[i];
	cout<<ans; 
}

很好的一个代码,\(\mathit{vis}\) 竟然和第一次优化时候的 \(\mathit{f}\) 的作用一样。

考虑预处理每一个 \(i\) 对应的 \(j\)。定义 \(\mathit{nxt}_{i}\) 表示 \(i\) 右边第一个 \(j\),使得子串 \([i,j]\) 能够匹配。这里有一个明显但是我觉得不明显的东西,就是 \(\mathit{nxt}_{i} \ge \mathit{nxt}_{i+1}+1\)。其中保证 \(\mathit{nxt}_{i+1}\) 有答案且 \(s_i \ne s_{i+1}\)。那么我们就可以从 \(i+1\) 一直跳,每次跳到 \(\mathit{nxt}_{j}+1\),直到判定为无答案或配对成功。求这个的复杂度是 \(O(n |\sum|)\) 的,能过。但是在某道加强版上你就会被卡飞。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register
#define il inline

const int N=2e6+10;
int n;
string s;
int f[N],nxt[N];

il void solve(){
	cin>>n>>s;int ans=0;
	nxt[n-1]=nxt[n]=-1;
	for(re int i=n-2;i>=0;--i){
		int now=i+1;
		while(s[i]!=s[now]&&~nxt[now]) now=nxt[now]+1;
		if(s[i]==s[now]) nxt[i]=now;
		else nxt[i]=-1;
	}
	for(re int i=n-1;i>=0;--i) if(~nxt[i]) f[i]=f[nxt[i]+1]+1;
	for(re int i=0;i<n;++i) ans+=f[i];
	cout<<ans; 
}

signed main(){
	solve();
	return 0;
}
posted @ 2024-03-07 13:42  harmis_yz  阅读(167)  评论(0)    收藏  举报