题意:求长度为m的,字符集大小为4的,字符串,中,与字符串S(|S|<=15)的最长公共子序列长度=i的字符串数量。 i ∈ 0 → ∣ S ∣ i \ \in 0 \to |S| i ∈0→∣S∣
发现这个状态十分不好定义,考虑
O
(
n
2
)
O(n^2)
O(n2)LCS的过程:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
−
1
]
∗
(
S
[
i
]
=
=
T
[
j
]
)
)
f[i][j] = max(f[i-1][j] , f[i][j-1] , f[i-1][j-1] * (S[i] == T[j]))
f[i][j]=max(f[i−1][j],f[i][j−1],f[i−1][j−1]∗(S[i]==T[j]))
发现
f
[
i
]
[
j
]
=
f
[
i
]
[
j
−
1
]
f[i][j] = f[i][j-1]
f[i][j]=f[i][j−1] 或
f
[
i
]
[
j
]
=
f
[
i
]
[
j
−
1
]
+
1
f[i][j] = f[i][j-1] + 1
f[i][j]=f[i][j−1]+1
那么我们完全可以把长度为i的字符串与S,DP后
f
[
i
]
[
j
]
−
f
[
i
]
[
j
−
1
]
f[i][j]-f[i][j-1]
f[i][j]−f[i][j−1]的值用二进制状态压缩起来,每次枚举新加入的字符,再进行LCS的dp,得到新状态转移。
意思就是,LCS和计数这两个dp不是不可得兼,用状态存状压后LCS的dp值,实际的dp值计数。类似的还有用
n
log
n
n\log n
nlogn单调栈求LIS的奇妙贪心做状态,实际的dp值计数的题目(HDU 4352 LIS + 数位DP)。
这类题目的存在说明了dp套dp不是ddp,不是动态2规划,但也是毒瘤题。
AC Code:
#include<bits/stdc++.h>
#define maxn 16
#define mod 1000000007
using namespace std;
char s[maxn],mc[5]="ATGC";
int m,nxt[30][1<<15],f[2][1<<15],ans[maxn];
void add(int &a,int b){ a=a+b;a>=mod?a-=mod:0; }
int main()
{
int T;
for(scanf("%d",&T);T--;)
{
scanf("%s%d",s,&m);
int n = strlen(s);
for(int i=0;i<4;i++)
for(int sta=0;sta<(1<<n);sta++)
{
int nsta=0;
for(int j=0,fpd=0,fd=0,fu=0,fpu=0;j<n;j++)
{
fpd=fd;
fd += (sta>>j&1);
fpu=fu;
fu = max(fu , fd);
if(mc[i] == s[j]) fu = max(fu,fpd+1);
nsta |= (fu-fpu)<<j;
}
nxt[i][sta]=nsta;
}
int now = 1,pre = 0;
memset(f[pre],0,sizeof f[pre]);
f[pre][0] = 1;
for(;m--;swap(now,pre))
{
memset(f[now],0,sizeof f[now]);
for(int sta=0;sta<(1<<n);sta++)
if(f[pre][sta])
for(int i=0;i<4;i++)
add(f[now][nxt[i][sta]] , f[pre][sta]);
}
memset(ans,0,sizeof ans);
for(int sta=0;sta<(1<<n);sta++)
add(ans[__builtin_popcount(sta)] , f[pre][sta]);
for(int i=0;i<=n;i++)
printf("%d\n",ans[i]);
}
}