CF 791(div2) E. Typical Party in Dorm(计数,子集DP)

CF 791(div2) E. Typical Party in Dorm

传送门
一个自然的思路是,考虑每一个区间产生了多少贡献。发现这个贡献跟可用的字符集合以及集合大小有关系,故设置一个\(ans[bit][len]\)来记录贡献。最后答案是给出字符串子集对应的\(ans[bit‘][len]\)之和(没想到多加一维len)。
所以要用子集DP来求,但是一般的子集DP复杂度是\(3^n\)
这里用一种新的枚举方法使得可以求出子集的和但不用枚举所有子集。使得复杂度降为\(n2^n\)
即按位考虑统计最高位为第i位为0时候的子集和。
代码:

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
using namespace std;
#define int long long
const int N=501000;
const int P=998244353;
char s[20];
int ans[N][20],Pow[20][2000];
int ksm(int x,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*x%P;
		b>>=1;
		x=x*x%P;
	}
	return ans;
}
signed main(){
	int n,q;
	scanf("%lld",&n);
	scanf("%s",s+1);
	for(int i=0;i<=17;i++){
		Pow[i][0]=1;
		for(int j=1;j<=n;j++)
			Pow[i][j]=Pow[i][j-1]*i%P;
	}
	int tot=0;
	for(int i=1;i<=n;i++)if(s[i]=='?')tot++;
	for(int type=1;type>=0;type--)
		for(int i=1;i<=n-(type);i++){
			int num=0,bit=0;
			int now=0;
			for(int l=i,r=i+type;l>=1&&r<=n;l--,r++){
				if(s[l]=='?'&&s[r]=='?'){
					num++;
					if(l==r)now+=1;
					else now+=2;
				}
				else if(s[l]=='?'||s[r]=='?'){
					int tmp=0;
					if(s[r]=='?')tmp=s[l]-'a'+1;
					else tmp=s[r]-'a'+1;
					bit|=1<<(tmp-1);
					now+=1;
				}
				else if(s[l]!=s[r])break;
				for(int k=1;k<=17;k++)ans[bit][k]+=Pow[k][num+tot-now],ans[bit][k]%=P;
			}
		}
	int sum=(1<<17)-1;
	for(int k=1;k<=17;k++){
		for(int j=0;j<=16;j++)
			for(int i=1;i<=sum;i++){
				if(i&1<<j){
					ans[i][k]+=ans[i^1<<j][k];
					ans[i][k]%=P;
				}
			}
	}
	scanf("%lld",&q);
	while(q--){
		scanf("%s",s+1);
		int len=strlen(s+1);
		int bit=0;
		for(int i=1;i<=len;i++){
			bit|=1<<(s[i]-'a');
		}
		cout<<(ans[bit][len])%P<<endl;
	}
	return 0;
}
posted @ 2022-05-26 16:08  Xu-daxia  阅读(87)  评论(0编辑  收藏  举报