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

0.闲话 

没错,又是我,至于为什么这段时间我一直没有发文章。

是因为我也是一个蒟蒻,也要备战CSP,然后,就被打蒙了

为了让大家不要信心备受打击,所以发了一篇题解。

1.审题 

小 L 现在在玩一个低配版本的消消乐,该版本的游戏是一维的,一次也只能消除两个相邻的元素。

现在,他有一个长度为 n 且仅由小写字母构成的字符串。我们称一个字符串是可消除的,当且仅当可以对这个字符串进行若干次操作,使之成为一个空字符串。

其中每次操作可以从字符串中删除两个相邻的相同字符,操作后剩余字符串会拼接在一起。

小 L 想知道,这个字符串的所有非空连续子串中,有多少个是可消除的。

输入的第一行包含一个正整数 n,表示字符串的长度。

输入的第二行包含一个长度为 n 且仅由小写字母构成的的字符串,表示题目中询问的字符串。

输出一行包含一个整数,表示题目询问的答案。

样例1                                  

8                                        5

accabccb

2.读懂样例

写题最忌 不读样例就写,我们先来看看有哪5个可消除子串。

它们分别是cc acca cc bccb accabccb

样例告诉了我们什么?

他告诉我们,不同位置的两个相同字串算2个。

两个可消除子串接在一起也是一个可消除子串

3.暴力 

众所周知 ,西西弗的数据是值得相信的东西,所以果断用暴力。

3.1极端的暴力

     枚举左端点和右端点,然后判断是否可消除。

     时间复杂度O(n^3),预期得分35pts!!!

     (不愧是希希弗)

      代码就不放了

3.2暴力优化

    仔细思考,这道题像不像一个字母版的判断一个括号序列是否合法,只不过要枚举左端          点而已。代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll cnt;
string s;
stack<char> st;
int main(){
	cin>>n;
	cin>>s;
	for(int i=0;i<n;++i){
		st.push(s[i]);
	    for(int j=i+1;j<n;++j){
		    if((!st.empty()) and st.top()==s[j]){
				st.push(s[j]);st.pop();st.pop();
			}
			else
				st.push(s[j]);
			if(st.empty()){ cnt++;/*cout<<i<<" "<<j<<endl;*/}
		}
		while(!st.empty()) st.pop();
	}
	cout<<cnt;
	return 0;
}

50pts!!!

4.DP 

检查可消除字串的性质 ,我们发现暴力代码并没有用到性质2,那性质2能做什么呢?

如果字串【i,j】能被消除,那么以j为右端点的可消除字串个数=以i为右端点的可消除字串个数+1

原因:假设字串[k,i]可以被消除,那么根据性质2,[k,j]也能被消除,再加上[i,j]。就是以i为右端点的可消除字串个数+1

这时问题来了,如果从后往前遍历第一个可以使[i,j]成为可消除字串的i,那么其复杂度与暴力优化无异。

好问题!

记g[j]为第一个可以使[i,j]成为可消除字串的i

考虑两种情况:

1.如果[j-1,j]可消除,也就是说c[j-1]==c[j],那么g[j]=j-1;

2.如果不行

我们就要将c[j-1]消除,也就是说考虑c[g[j-1]-1]是否等于c[j];

重复此操作——直到

         某个g[x]==-1(无法消除)将g[j]=-1 or

         某个c[x]==c[j] 将g[j]=x

那么得到状态转移方程

       dp[j]=dp[g[j]]+1

代码实现:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,dp[2000010],g[2000010];
string s;
int main(){
	cin>>n;
	cin>>s;
	for(int i=0;i<n;++i){
		char x=s[i];
		int ni=i-1;
		while(1){
			if(s[ni]==x) {g[i]=ni;break;}
			else if(g[ni]==-1 or ni<0){ g[i]=-1;break;}
			ni=g[ni]-1;
		}
	}
	ll cnt=0;
	for(int i=0;i<n;++i){
		if(g[i]==-1) dp[i]=0;
		else dp[i]=dp[g[i]-1]+1;
		cnt+=dp[i];
	}
	cout<<cnt;
	return 0;
}

100pts,AC了

5.祝福 

unsigned long long rp=0;
rp--;

再见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值