[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;
}
posted @ 2021-06-25 09:13  oisdoaiu  阅读(80)  评论(0编辑  收藏  举报