题意:
给出n,m
给出一个长度为n的01串 S,
问你能构造出多少个01串, 这些01串的某一个字串与S至多只有一个位置不同。
题解:
首先想到dp[i][j][0/1] 表示 构造了前i个字符,与S的前j个字符 有0/1 字符不相同的方案数。
显然 ,无法很方便的转移,因为只要有一个字串满足条件就行了。
然后,有一个ac自动机很套路的用法.
这题跟hdu2858有些类似。
点我看题 最后面
设dp[i][j]表示构造前i个字符,最终状态到达i的不合法状态数。
因为最多只能有1个字符不同,那最多只有n+1个不同的串,把这n+1个串全部加入ac自动机当中。
除结尾的点外其他的点都为不合法点。
这样的话就很好转移了。
很套路的一道题。
PS。 北京五题有金,感觉很真实。
#include<bits/stdc++.h>
using namespace std;
const int SIGMA_SIZE = 2;
const int MAXNODE = 2500;
const int MAXS = 1005;
char str[1005];
long long dp[50][2000];
struct AC{
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE];
int val[MAXNODE];
int last[MAXNODE];
int sz;
void init(){
sz = 1;
memset(ch[0],0,sizeof (ch[0]));
}
inline int idx(char c){
return c-'0';
}
void insert(char *s,int ids){
int u =0 ,n= strlen(s);
for(int i=0;i<n;i++){
int c= idx(s[i]);
if(!ch[u][c]){
memset(ch[sz],0,sizeof ch[sz]);
val[sz]=0;
ch[u][c]= sz++;
}
u = ch[u][c];
}
val[u]=ids;
}
void getfail(){
queue<int> Q;
f[0]=0;
for(int c= 0;c<SIGMA_SIZE;c++){
int u=ch[0][c];
if(u){
f[u]=0;
Q.push(u);
last[u]=0;
}
}
while(!Q.empty()){
int r= Q.front();
Q.pop();
for(int c = 0;c<SIGMA_SIZE;c++){
int u = ch[r][c];
if(!u){
ch[r][c]=ch[f[r]][c];
continue;
}
Q.push(u);
int v = f[r];
while(v && !ch[v][c]) v= f[v];
f[u] = ch[v][c];
last[u] = val[f[u]]?f[u]:last[f[u]];
}
}
}
} ac;
int main(){
int T;
scanf("%d",&T);
while(T--){
ac.init();
long long n,m;
scanf("%lld %lld",&n,&m);
scanf("%s",str);
ac.insert(str,1);
for(int i=0;i<n;i++){
if(str[i]=='0'){
str[i]='1';
ac.insert(str,1);
str[i]='0';
}else{
str[i]='0';
ac.insert(str,1);
str[i]='1';
}
}
ac.getfail();
for(int i=0;i<=m;i++){
for(int j=0;j<=ac.sz;j++){
dp[i][j]=0;
}
}
dp[0][0]=1;
for(int i=0;i<m;i++){
for(int j=0;j<ac.sz;j++){
if(!dp[i][j]) continue;
for(int k=0;k<=1;k++){
int v=ac.ch[j][k];
if(ac.val[v])continue;
dp[i+1][v]+=dp[i][j];
}
}
}
long long sum = 0;
for(int i=0;i<ac.sz;i++){
sum+=dp[m][i];
}
printf("%lld\n",((1ll<<m)-sum));
}
return 0;
}