Binary Subsequence

Description

对于所有长度为 \(n\)\(2^n\) 个 01 串,对每个求出形如 000...111 的最长的子序列的长度,求出长度和。

Solution

\(a_i\) 表示前 \(i\) 个数中 \(0\) 的个数,\(b_i\)表 示 \(i\) 及其以后 \(1\) 的个数。那么对于一个 01 串,它的最长合法子序列长度就是

\[\max_{i=1}^{n+1}\{a_{i-1}+b_{i}\} \]

对每个位置,我们都求出 \(a_{i-1}+b_{i}\) 的值,就得到一个新的数列。于是我们建立了一个 01 串到这个序列的双射。

容易发现这个序列的特点,首项就是 1 的个数,末项就是 0 的个数。所以总有 \(首项+末项=n\)。以及,相邻两项会且仅会相差 1(卡特兰既视感)。所以我们可以考虑枚举这个序列的最大值,以及首尾项,再通过组合计数解决。

假设峰值是 \(j(j\leq n)\),首项是 \(i\),那么问题就等价于从 \((0,i)\) 走到 \((n,n-i)\) 且最大不超过 \(j\),最小不低于 \(0\) 的方案数。作 \((\frac{x-y}{2},\frac{x+y}{2})\) 的变换,再平移一下。问题就转化为从 \((0,0)\) 开始走,不接触 \(y=x+k_1\)\(y=x+k_2\) 且最终到达 \((X,Y)\) 的方案数。转化为经典的双直线翻折法 [JLOI2015] 骗我呢

#include<stdio.h>
#include<algorithm>
using namespace std;

#define ll long long

inline int read(){
	int x=0,flag=1; char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
	return flag*x;
}

const int N=5e3+7;

int Mod;
ll fac[N<<2],ifac[N<<2],inv[N<<2],f[N][N];

inline ll C(int x,int y){return x<y? 0:fac[x]*ifac[y]%Mod*ifac[x-y]%Mod;}

inline void fold(int &x,int &y,bool typ,int op,ll &ans,int &j,int &k){
    swap(x,y); int dt=typ? -k+1:-(j+1);
    x+=dt,y-=dt; ans=(ans+Mod+op*(x>=0&&y>=0? C(x+y,x):0))%Mod;
}

inline ll calc(int X,int Y,int j,int k){
	ll ans=C(X+Y,X);
    int x=X,y=Y;
	while(x>=0&&y>=0)
		fold(x,y,0,-1,ans,j,k),fold(x,y,1,1,ans,j,k);
    x=X,y=Y;
	while(x>=0&&y>=0)
		fold(x,y,1,-1,ans,j,k),fold(x,y,0,1,ans,j,k);
	return ans;
}

ll F(int n,int i,int j,int k){
	if(i>j) return 0;
	if(~f[i][j]) return f[i][j];
	return f[i][j]=calc((n-i)/2,(n+i)/2,j,k); 
}

ll qpow(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1) ret=ret*x%Mod;
		y>>=1,x=x*x%Mod;
	}
	return ret;
}

int main(){
	int T=read();
	fac[0]=inv[1]=ifac[0]=1;
	while(T--){
		int n=read(); Mod=read();
		for(int i=1;i<=2*n;i++) fac[i]=fac[i-1]*i%Mod;
	    for(int i=2;i<=2*n;i++) inv[i]=(Mod-Mod/i)*inv[Mod%i]%Mod;
	    for(int i=1;i<=2*n;i++) ifac[i]=ifac[i-1]*inv[i]%Mod;
	    ll ans=0;
	    for(int i=0;i<=n;i++)
	    	for(int j=0;j<=n;j++) f[i][j]=-1;
	    for(int i=0;i<=n;i++){
	    	int I=abs(i-(n-i)),rg=min(i,n-i);
	    	for(int j=max(i,n-i);j<=n;j++)
	    		ans=(ans+(F(n,I,j-rg,-rg)-F(n,I,j-rg-1,-rg)+Mod)%Mod*j%Mod)%Mod;
		}
		printf("%lld\n",ans);
		printf("%lld\n",ans*qpow(qpow(2ll,Mod-2),n)%Mod);
	}
}

update 9.18

发现有简单的递推做法,令 \(f_n\) 为长为 \(n\) 的所有串的答案和。分两部分考虑。对于前 \(n-1\) 位,当新增加一位,串的个数会翻倍,所以加上 \(2f_{i-1}\)。下面考虑新增的一位对答案的贡献。对于 \(1\),无论前面是什么,这一位肯定会加入答案序列,所以答案加上 \(2^{i-1}\) ,即串的个数。对于 \(0\),它会在答案子序列里,当且仅当在前 \(n-1\) 位的每一个后缀中,\(0\) 的个数要大于等于 \(1\) 的个数。否则,一定会以 \(1\) 结尾。这样的串有(卡特兰计数法)

\[\sum_{\lceil \frac{n}{2} \rceil}^{n} \binom{n}{i}-\binom{n}{i+1}=\binom{n}{\lceil \frac{n}{2}\rceil} \]

于是得到递推式

\[f_n=2f_{n-1}+2^{n-1}+\binom{n}{\lceil \frac{n}{2}\rceil} \]

posted @ 2023-09-12 21:20  Kreap  阅读(69)  评论(0编辑  收藏  举报