1111

总结一下最近做的一些题目

模拟赛20221110 猪尾巴 | CQNK

这个题很明显是一个压状态然后dp的题,场上也一直在往这个方向想,但是没有做出来。

首先,最暴力的压状态方法就是把最后k位的最小表示全部压下来,然后每次确保转移到的不是回文串即可,复杂度 \(O(nBell_k)\),明显跑不了。

然后场上的想法是类似回文树一样,维护最后k位的fail集合,但是这是每一次跳fail与fail的前一位是什么字符有关,而这是难以转移的,场上经过若干次尝试,发现不可做,就只好打了最低的暴力。

然后题解的做法就十分巧妙了,这种dp的思想以前并没有见过,所以记录下来,希望以后可以变为一个常规的套路。

首先,改变计算答案的方式,之前的做法是保证0每一次最后k位都不回文,但是这样是难以实现的。于是考虑容斥,令 F(x) 为至少有x个长度为k的回文串,那么最终的答案就是 \(\sum F(i) \times-1^i\),于是在dp的时候可以把容斥系数和方案数一起算,每次可以钦定最后k位是回文的,那么容斥系数会取反。

那么现在需要设计dp的状态:每一次转移可以是最后一位随便填,或者强行钦定最后k位是回文。

如果钦定最后k为是回文的,那么就会出现k/2对相等关系(s_1=s_k,s_2=s_k-1,……),于是可以使用令dp状态为最后k位的相等关系的最小表示(如:状态:112331 表示第1、2、6位是相等的,第4、5位是相等的),注意这个状态只记录了钦定的相等关系,即:同一块内必定相等,但是不同的块不一定不相等,所有dp算的是至少出现x次回文的方案数。

然后考虑转移,这是本题第二个之处:对方案的贡献不要在一个字符进来的时候计算,因为一个字符加入状态以后可能会在之后出现一些相等关系,使它不能独立选择;所以我们在每一个块的最后一个统计答案,即如果当前的第k+1个是它的块的最后一个,那么这个块在之后肯定不会再出现,就可以在这里统计贡献。即看当前出状态的那个,如果它是独立的,计算贡献。

然后状态dfs出来发现不是很多,就可以dp了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline void Add(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline int mul(int a,int b){return (ll)a*b%mod;}
int n,k,c,ans,tot;
typedef vector<int> V;
map<vector<int>,int> mp;
int id[100];
void trans1(V &v){
	int o=0;
	vector<int> v1;
	for(int i=0;i<k;i++){
		if(!id[v[i]])id[v[i]]=++o;
		v1.push_back(id[v[i]]-1);	
	}
	for(int i=0;i<k;i++)id[v[i]]=0;
	v=v1;
}
int fa[100];
int gf(int x){return x==fa[x]?x:fa[x]=gf(fa[x]);}
void trans2(V &v){
	for(int i=0;i<k;i++)fa[i]=i;
	for(int i=0;i<k;i++){
		int x=gf(v[i]),y=gf(v[k-i-1]);
		fa[y]=x;
	}
	for(int i=0;i<k;i++)v[i]=gf(v[i]);
	trans1(v);
}
pair<int,int> to[100005][2];
int sz[100005]; 
void dfs(V v,int id){
	V v1;
	bool flag=0;
	for(int i=1;i<k;i++)v1.push_back(v[i]),flag|=v[i]==v[0],sz[id]=max(sz[id],v[i]);
	sz[id]++;
	v1.push_back(k+1);
	trans1(v1);
	int &now=mp[v1];
	if(!now){now=++tot;dfs(v1,now);}
	to[id][0]=make_pair(now,flag?1:c);
	trans2(v1);
	int &now1=mp[v1];
	if(!now1){now1=++tot;dfs(v1,now1);}
	to[id][1]=make_pair(now1,flag?mod-1:mod-c);
}
int f[2][100005],pw[100];
int main(){
	cin>>n>>k>>c;
	V v;
	for(int i=0;i<k;i++)v.push_back(i);
	mp[v]=tot=1,f[0][1]=1,f[0][2]=mod-1;
	dfs(v,1);
	int p=0;
	pw[0]=1;
	for(int i=1;i<=k;i++)pw[i]=mul(pw[i-1],c);
	for(int i=k+1;i<=n;i++,p^=1){
		for(int j=1;j<=tot;j++){
			if(!f[p][j])continue;
			Add(f[p^1][to[j][0].first],mul(f[p][j],to[j][0].second));
			Add(f[p^1][to[j][1].first],mul(f[p][j],to[j][1].second)); 
			f[p][j]=0;
		}
	}
	int ans=0;
	for(int i=1;i<=tot;i++)Add(ans,mul(f[p][i],pw[sz[i]]));
	cout<<ans<<endl;
	return 0;
}

posted @ 2022-11-11 18:29  蒟蒻_william555  阅读(231)  评论(0)    收藏  举报