- 有一个(01)串集合(S),通过(n+1)个(01)串给出:第(i)个串长度为(2^{i-1}),它的第(j)位表示(j-1)长度为(i-1)的二进制表示是否出现在(S)中。
- 求一个最长的(01)串(多解则字典序最小),满足至少是(S)中(k)个串的子序列。
- (nle20)
子序列自动机
感觉这道题才是真正意义上的子序列自动机,和某模板题完全不是一个难度的。
这里相当于是把序列的剩余部分看作节点,那么加上一个字符就是贪心找到剩下的序列中的第一个相同字符,将它连同之前的部分完全删去。
容易发现任意串在子序列自动机中的匹配路径是唯一的(其实是自动机的通用性质)。
而这个性质的存在就意味着我们不会算重或是算漏情况。
状压(DP)
考虑我们记录两个串(A,B),(A)表示当前已经完成匹配的子序列,(B)表示匹配剩下的序列。
因为只要知道匹配剩下的序列是什么就与原序列是谁无关了,转移的路径相同,因此只要为所有串设好初始状态,然后就可以一起转移了。
转移分为三种:直接放弃转移让(A)成为最终子序列;找到(B)中第一个(0)转移;找到(B)中的第一个(1)转移。
关于(B)中的第一个(0/1),我们可以事先预处理,只要考虑最高位是否为(0/1),如果是则第一个就是最高位,否则就是除去最高位剩余部分的第一个(0/1),显然这已经在先前求出。(其实这就是一个建子序列自动机的过程)
具体实现中,因为(|A|+|B|le n),可以使用状压(DP),把两个串接在一起记录一下分割线即可。
由于这道题我们需要知道是否存在前导(0),不妨强行在(A)的最前面添上一个(1),则(1)之后到分割线的部分就是真正的(A)了。
代码:(O(n2^n))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
using namespace std;
int n,k,f[N+5][1<<N+1],S0[N+5][1<<N],S1[N+5][1<<N];char s[N+5][(1<<N)+5];
int main()
{
RI i,j,l;for(scanf("%d%d",&n,&k),i=0;i<=n;++i) if(scanf("%s",s[i]),i) for(j=0,l=1<<i;j^l;++j)
s[i][j]&1&&(f[i][j|l]=1),S0[i][j]=j>>i-1&1?S0[i-1][j^(1<<i-1)]:i,S1[i][j]=j>>i-1&1?i:S1[i-1][j];//设上初始状态;预处理长度为i的j第一个0/1位置
RI t,A,B;for(l=1<<n+1,i=n;i;--i) for(j=0;j^l;++j) A=j>>i,B=j&((1<<i)-1),f[0][A]+=f[i][j],//解压出A,B;放弃转移作为最终串
(t=S0[i][B])&&(f[t-1][A<<t|(B&((1<<t-1)-1))]+=f[i][j]),(t=S1[i][B])&&(f[t-1][A<<t|(1<<t-1)|(B&((1<<t-1)-1))]+=f[i][j]);//与第一个0匹配;与第一个1匹配
for(i=n;i;--i) for(l=1<<i,j=0;j^l;++j) if(f[0][1<<i|j]>=k) {for(--i;~i;--i) putchar(48|(j>>i&1));return 0;}return 0;//先枚举长度,然后尽可能小
}