【考试总结】2022-05-29

s1mple

将长度为 \(n-1\)\(0/1\) 串压成二进制数字,预处理所有询问的答案 \(Ans_q\)

尝试求出 $\displaystyle B(S)=\sum\limits_{S\subseteq Q} Ans_Q $,也就是说钦定一些位置为 \(1\) ,剩下的位置 \(0/1\) 不限,最后做 \(\rm iFMT\) 得到答案

使用钦定的 \(1\) 将元素连成一些段,由于没被钦定的 \(0\) 在排列确定时的数值的确定的,那么某个 \(B_S\) 就是 \(S\) 分成的若干段的方案数的乘积

那么对于分成的段的集合相同的 \(S,T\)\(B(S)=B(T)\),所以本质不同的 \(S\) 只有 \(\pi(n)\)

做平凡 \(\rm DP\) 求出来每个集合 \(S\) 能形成的单链数 \(f_S\),用占位幂级数的思想设集合幂级数 \(F_{k}(S)=[|S|=k]f_S\)

此时对于每个集合划分将每个段长对应的集合幂级数做或卷积,不难发现直接使用 全集处的系数 就是结果

直接实现复杂度是 \(\Theta(\pi(n)2^nn+2^nn^2)\) ,如果不每个划分做 \(iFMT\) 而是用定义式求单点应该可以往下降

Code Display
const int N=17;
char s[N+2][N+2],q[N+2];
int n,Q,U;
int ans[1<<N];
int one[N+1],id[1<<N];
int F[N+1][1<<N],met[1<<N][N+1];
inline void FWT_Or(int *f,int lim){
	for(int p=2;p<=lim;p<<=1){
		int len=p>>1;
		for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l) f[l+len]+=f[l];
	}
	return ;
}
inline void iFWT_And(int *f,int lim){
	for(int p=2;p<=lim;p<<=1){
		int len=p>>1;
		for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l) f[l]-=f[l+len];
	}
	return ;
}
inline void iFWT_Or(__int128 *f,int lim){
	for(int p=2;p<=lim;p<<=1){
		int len=p>>1;
		for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l) f[l+len]-=f[l];
	}
	return ;
}
inline ull get_hs(vector<int> &x){
	sort(x.begin(),x.end());
	ull hs=0;
	for(auto t:x) hs=hs*13331+t;
	return hs;
}
map<ull,int> mp;
__int128 f[1<<N];
inline int solve(int S){
	vector<int> sta;
	int cnt=0;
	for(int i=1;i<=n-1;++i){
		if(S>>(i-1)&1) ++cnt;
		else{
			sta.emplace_back(cnt+1);
			cnt=0;
		}
	}
	sta.emplace_back(cnt+1);
	ull curhs=get_hs(sta);
	if(mp.count(curhs)) return mp[curhs];
	rep(i,0,U) f[i]=1;
	for(auto k:sta){
		rep(i,0,U) f[i]*=F[k][i];
	}
	iFWT_Or(f,U+1);
	return mp[curhs]=f[U];
}
signed main(){
	freopen("s1mple.in","r",stdin); freopen("s1mple.out","w",stdout);
	n=read();
	U=(1<<n)-1;
	for(int i=1;i<=n;++i) id[1<<(i-1)]=i;
	rep(i,1,n){
		scanf("%s",s[i]+1);
		for(int j=1;j<=n;++j){
			if(s[i][j]=='1') one[i]|=1<<(j-1);
		}
	}
	rep(i,1,n) met[(1<<(i-1))][i]=1;
	for(int s=1;s<U;++s){
		for(int j=1;j<=n;++j) if(met[s][j]){
			int S=(U^s)&one[j];
			while(S){
				int lb=S&(-S),cur=id[lb];
				met[s|lb][cur]+=met[s][j];
				S-=lb;
			}
		}
	}
	for(int s=1;s<=U;++s){
		int pc=__builtin_popcount(s);
		for(int j=1;j<=n;++j) F[pc][s]+=met[s][j];
	}
	for(int i=1;i<=n;++i) FWT_Or(F[i],U+1);
	for(int i=0;i<(1<<(n-1));++i) ans[i]=solve(i);
	iFWT_And(ans,1<<(n-1));
	Q=read();
	while(Q--){
		scanf("%s",q+1);
		int num_q=0;
		rep(i,1,n-1) num_q|=(q[i]-'0')<<(i-1);
		print(ans[num_q]);
	}
	return 0;
}

s2mple

\(s[l\dots r]\) 扩展成 \(T=pre+s[l\dots r]+suf\) ,若 \(T\) 仍然是 \(s\) 的子串,那么给答案贡献 \(1\),也就是说统计可空的字符串对 <pre,suf> 的个数

正确性在于如果 \(s[l,r]\) 在某个子串上出现了多次,那么找到所有 \(\rm endpos\) ,每个结束位置向左向右补上所缺就能得到一种方案

对于在字符串前面加字符本质上是走到其后缀 \(fail\) 树的子树里面,而在串后添加字符的方案数是 在 \(\rm SAM\) 构成的 \(\rm DAG\) 上求有多少本质不同的到达后继节点的路径数

使用拓扑排序求出来后者 \(val\),对其做子树的 \(val\times (len_x-len_{fa})\) 的和

使用倍增找到 \(s[l,r]\) 对应的节点,注意这个点本身对应的所有字符串不一定都是比 \(s[l,r]\) 长的,所以要找到长度不小于 \(r-l+1\) 的部分

Code Display
const int N=8e5+10;
int pos[N],len[N],fa[N],son[N][26],tot=1,las=1;
inline void extend(int x,int id){
	int tmp=las,np=las=++tot; pos[id]=np;
	len[np]=len[tmp]+1; 
	while(!son[tmp][x]) son[tmp][x]=np,tmp=fa[tmp];
	if(!tmp) return fa[np]=1,void();
	int q=son[tmp][x];
	if(len[q]==len[tmp]+1) return fa[np]=q,void();
	int clone=++tot; len[clone]=len[tmp]+1;
	fa[clone]=fa[q]; fa[np]=fa[q]=clone;
	rep(i,0,25) son[clone][i]=son[q][i];
	while(son[tmp][x]==q) son[tmp][x]=clone,tmp=fa[tmp];
}
int val[N],n,Q;
char s[N];
int in[N],bz[N][20];
vector<int> G[N],pre[N];
inline int find(int l,int r){
	int x=pos[r];
	for(int i=19;~i;--i) if(len[bz[x][i]]>=r-l+1) x=bz[x][i];
	return x;
}
inline void dfs(int x){
	bz[x][0]=fa[x];
	for(int i=1;bz[x][i-1];++i) bz[x][i]=bz[bz[x][i-1]][i-1];
	for(auto t:G[x]) dfs(t);
	return ;
}
int sub[N];
signed main(){
	freopen("s2mple.in","r",stdin); 
	freopen("s2mple.out","w",stdout);
	n=read(); Q=read(); scanf("%s",s+1);
	for(int i=1;i<=n;++i) extend(s[i]-'a',i);
	for(int i=2;i<=tot;++i) G[fa[i]].emplace_back(i);
	dfs(1);
	for(int i=1;i<=tot;++i){
		rep(j,0,25) if(son[i][j]){
			pre[son[i][j]].emplace_back(i);
			in[i]++;
		}
	}
	queue<int> q;
	for(int i=1;i<=tot;++i) if(!in[i]) q.push(i);
	while(q.size()){
		int fr=q.front(); q.pop();
		val[fr]++;
		for(auto t:pre[fr]){
			if(!(--in[t])) q.push(t);
			val[t]+=val[fr];
		}
	}
	function<void(int)>get_sub=[&](const int x){
		for(auto t:G[x]) get_sub(t),sub[x]+=sub[t];
		sub[x]+=(len[x]-len[fa[x]])*val[x];
	};
	get_sub(1);
	while(Q--){
		int l=read(),r=read();
		int pos=find(l,r);
		int ans=sub[pos]-(len[pos]-len[fa[pos]])*val[pos];
		print(ans+(len[pos]-(r-l+1)+1)*val[pos]);
	}
	return 0;
}

s3mple

\(f_{l,r,x}\) 表示 \(a_{l-1},a_{r+1}\) 都大于 \(f_{l,r}\) 中元素的情况下 \(\sum\limits_{i=l}^r v_i=x\) 的方案数

注意到前两维对 \(f\) 的决定作用只和 \(r-l+1\) 有关,所以将状态定义为 \(f_{n,x}\) 再转移:

\[f_{n,x}=\sum_{j=0}^{n-1}\binom{n-1}j\sum_{v=0}^{x-\min(j+1,n-j+1)}f_{j,v}f_{n-j-1,x-v-\min(j+1,n-j+1)} \]

写成 \(\rm OGF\) 的形式有 \(F_n=\sum_{j=0}^{n-1}\binom{n-1}jF_{j}F_{n-j-1}x^{\min(j+1,n-j+1)}\)

观察到 \(x\) 最大是 \(n\log n\) 级别,写一个 \(\rm DP\) 可以发现在 \(n=200\) 只有 \(735\)

那么使用拉格朗日插值还原多项式的方法得到答案即可,即先带入 \(x=1\dots 735\) 做上面的 \(\rm DP\) 再用公式展开还原

还原时有除多项式的问题,可以用大除法模拟

一个降低多项式次数的方法是发现转移的过程中必然有 \(+1\) ,那么把所有转移的距离中的 \(+1\) 删掉,让询问的 \(x\) 也减少 \(n\),那么次数上界变成 \(535\)

Code Display
const int N=210,M=600;
int inv[M],ifac[M],pw[M][N],C[N][N];
int n,x;
vector<int> f[N];
vector<int> pre[M],suf[M];
int lim[N];
signed main(){
	freopen("s3mple.in","r",stdin); freopen("s3mple.out","w",stdout);
	mod=read();
	if(mod==1){
		while(~scanf("%lld%lld",&n,&x)) puts("0");
		exit(0);
	}
	C[0][0]=1;
	rep(i,1,200){
		C[i][0]=1;
		rep(j,1,i){
			C[i][j]=add(C[i-1][j],C[i-1][j-1]);
			ckmax(lim[i],lim[j-1]+lim[i-j]+min(j-1,i-j));
		}
	}
	inv[0]=inv[1]=ifac[0]=ifac[1]=1;
	rep(i,2,550){
		inv[i]=mod-mul(mod/i,inv[mod%i]);
		ifac[i]=mul(ifac[i-1],inv[i]);
	}
	for(int i=1;i<=550;++i){
		pw[i][0]=1;
		for(int j=1;j<=200;++j) pw[i][j]=mul(pw[i][j-1],i);
	}
	rep(i,0,200) f[i].resize(551);
	rep(i,1,550) f[0][i]=1;
	for(int i=1;i<=200;++i){
		for(int j=0;j<i;++j){
			for(int k=1;k<=550;++k){
				int coef=mul(C[i-1][j],pw[k][min(j,i-j-1)]);
				int val=mul(f[j][k],f[i-j-1][k]);
				ckadd(f[i][k],mul(coef,val));
			}
		}
	}
	while(~scanf("%lld%lld",&n,&x)){
		x-=n;
		if(x<0||x>lim[n]){
			puts("0");
			continue;
		}
		auto Mul=[&](const vector<int> a,const vector<int> b){
			vector<int> c; c.resize(a.size()+b.size()-1);
			for(int i=0;i<a.size();++i){
				for(int j=0;j<b.size();++j) ckadd(c[i+j],mul(a[i],b[j]));
			}
			return c;
		};
		int ans=0;
		pre[0]=suf[551]={1};
		for(int i=1;i<=550;++i) pre[i]=Mul(pre[i-1],{mod-i,1});
		for(int i=550;i>=1;--i) suf[i]=Mul(suf[i+1],{mod-i,1});

		for(int i=1;i<=550;++i){
			int coef=f[n][i];
			ckmul(coef,mul(ifac[550-i],ifac[i-1]));
			if((550-i)&1) ckmul(coef,mod-1);
			ckadd(ans,mul(coef,Mul(pre[i-1],suf[i+1])[x]));
		}
		print(ans);
	}
	return 0;
}

posted @ 2022-05-30 08:55  没学完四大礼包不改名  阅读(104)  评论(0)    收藏  举报