【AT3963】[AGC024F] Simple Subsequence Problem(子序列自动机上状压DP)
- 有一个\(01\)串集合\(S\),通过\(n+1\)个\(01\)串给出:第\(i\)个串长度为\(2^{i-1}\),它的第\(j\)位表示\(j-1\)长度为\(i-1\)的二进制表示是否出现在\(S\)中。
- 求一个最长的\(01\)串(多解则字典序最小),满足至少是\(S\)中\(k\)个串的子序列。
- \(n\le20\)
子序列自动机
感觉这道题才是真正意义上的子序列自动机,和某模板题完全不是一个难度的。
这里相当于是把序列的剩余部分看作节点,那么加上一个字符就是贪心找到剩下的序列中的第一个相同字符,将它连同之前的部分完全删去。
容易发现任意串在子序列自动机中的匹配路径是唯一的(其实是自动机的通用性质)。
而这个性质的存在就意味着我们不会算重或是算漏情况。
状压\(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;//先枚举长度,然后尽可能小
}
待到再迷茫时回头望,所有脚印会发出光芒