[AGC024F] Simple Subsequence Problem
tag:序列自动机,状压dp
一眼看上去感觉不太好做,实际上可以枚举所有串,然后问题就变为统计一个串是多少个串的子序列。
可以构建一个子序列自动机,然后将初始状态(给出的字符串)设为 \(1\),再跑一个dp即可。
状态定义如下:
\(f[len][S]\)
- 用一个01串表示状态
- 开头一个1,用来表示位数
- 后面由“已匹配部分”+“未匹配部分”组成
- \(len\) 为已匹配部分的长度
比如 \(f[2][10011]\) 表示当前已匹配部分为00
,未匹配部分为11
。
先预处理一下每个01串开头第一个1/0。
复杂度为状态数\(O(n2^{n})\)
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void Read(T &n){
char ch; bool flag=false;
while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
if(flag)n=-n;
}
int first1[1<<21], first0[1<<21], tp, n, m;
inline int second1(int S){
S -= 1<<first1[S];
return first1[S];
}
inline void check(int S){
int mx = first1[S];
for(register int i=mx; ~i; i--)putchar(S>>i&1^48);
}
int f[21][1<<21];
char a[1<<20];
inline int Save(int S, int len){return S&((1<<len)-1);}
inline void work(int len1, int S){
int len = first1[S]+1, len2 = len-len1-1, len3;
int S1 = S>>len2, S2 = Save(S,len2)|(1<<len2);
// printf("work %d ",len); check(S);puts("");
// printf("%d + %d\n",len1,len2);
// check(S1); putchar('|'); check(S2);puts("");
if(~(len3=second1(S2))){
int nxtS = ((S1<<1)|1)<<len3;
nxtS |= Save(S2,len3);
f[len1+1][nxtS] += f[len1][S];
// printf("to "); check(nxtS);puts("");
}
if(~(len3=first0[S2])){
int nxtS = S1<<1<<len3;
nxtS |= Save(S2,len3);
f[len1+1][nxtS] += f[len1][S];
// printf("to "); check(nxtS);puts("");
}
f[n][S1] += f[len1][S];
}
int main(){
Read(n); Read(m); tp = 1<<n+1;
first1[0] = -1; for(register int i=1; i<tp; i++) first1[i] = first1[i>>1]+1;
first0[0] = -1;
for(register int i=1; i<tp; i++){
first0[i] = -1;
for(register int j=first1[i]-1; ~j; j--) if(~i>>j&1){first0[i] = j; break;}
}
for(register int i=0; i<=n; i++){
int num = 1<<i;
scanf("%s",a);
for(register int j=0; j<num; j++) if(a[j]=='1') f[0][1<<i|j]++;
}
for(register int i=0; i<n; i++) for(register int j=0; j<tp; j++) if(f[i][j]) work(i,j);
int ans=0;
// for(register int i=0; i<tp; i++) if(f[n][i]){
// check(i);
// printf(" %d\n",f[n][i]);
// }
for(register int i=0; i<tp; i++) if(f[n][i]>=m) if(first1[i]>first1[ans] or (first1[i]==first1[ans] and (i^(1<<first1[i]))<(ans^(1<<first1[i])))) ans = i;
int len = first1[ans];
for(register int i=len-1; i>=0; i--) putchar(ans>>i&1^48);puts("");
return 0;
}