CF245H Queries for Number of Palindromes(快读+容斥原理 + 记忆化)(好题捏

*题目传送门

分析:

其实这道题关键在于状态转椅上(好像是句废话?)。

一开始的想法就是枚举一个长度 \(l\),计算出两端点 \(i\)\(j\),判断一下合并出来的这一段是不是一个回文串,然后有如下的转移方程:

\[dp[i][j] = max(dp[i][j],dp[i][k] + dp[k + 1][j] + ispar(i,j)) \]

显然它是错的。这个方程没有注意到在合并两个区间的过程中可能会产生新的回文串,并且时间复杂度为 \(O(n^3)\),是过不了的。

那么如何合理合并两区间时产生的回文串呢?似乎无解了

这里就需要运用到容斥原理了。(我也是第一次见)对于一个端点为 \(i\)\(j\) 的区间,取

\[dp[i][j] = dp[i + 1][j] + dp[i][j - 1] \]

很明显,\(dp[i + 1][j - 1]\) 被加了两次,因此只需要减去一次,就可以去重,得到正确答案了。

\[dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] + ispar(i,j) \]

多提一句,在判断回文串时,采用传统的 \(O(n/2)\) 会超时,用个记忆化即可。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
int dp[MAXN][MAXN],l,r,t;
string s;
int par[MAXN][MAXN];
bool check(int l,int r){
	if(par[l][r] != -1)return par[l][r];
	if(l > r)return 1;
	if(s[l] == s[r]){
		return par[l][r] = check(l + 1,r - 1);
	}
	else{
		return par[l][r] = 0;
	}
	return par[l][r];
}

template <class T>
void read(T &x){
	x=0;char c=getchar();bool f=0;
	while(!isdigit(c)) f=c=='-',c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	x=f? (-x):x;
}
int main(){
	memset(par,-1,sizeof par); 
	cin >> s;
	for(int i = 0; i < s.size(); i++){
		dp[i][i] = 1;
		par[i][i] = 1;
	}
	for(int l = 1; l < s.size(); l++){
		for(int i = 0; i + l < s.size(); i++){
			int j = i + l;
			dp[i][j] = max(dp[i][j],dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j  - 1]);
			if(check(i,j)) dp[i][j]++;
		}
	}
	read(t);
	while(t--){
		read(l);read(r);
		l--;
		r--;
		printf("%d\n",dp[l][r]);
	}
	return 0;
}
posted @ 2022-08-10 16:40  腾云今天首飞了吗  阅读(33)  评论(0)    收藏  举报