【考试总结】2022-05-29

s1mple

将长度为 n10/1 串压成二进制数字,预处理所有询问的答案 Ansq

尝试求出 B(S)=SQAnsQ,也就是说钦定一些位置为 1 ,剩下的位置 0/1 不限,最后做 iFMT 得到答案

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

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

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

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

直接实现复杂度是 Θ(π(n)2nn+2nn2) ,如果不每个划分做 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[lr] 扩展成 T=pre+s[lr]+suf ,若 T 仍然是 s 的子串,那么给答案贡献 1,也就是说统计可空的字符串对 <pre,suf> 的个数

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

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

使用拓扑排序求出来后者 val,对其做子树的 val×(lenxlenfa) 的和

使用倍增找到 s[l,r] 对应的节点,注意这个点本身对应的所有字符串不一定都是比 s[l,r] 长的,所以要找到长度不小于 rl+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

fl,r,x 表示 al1,ar+1 都大于 fl,r 中元素的情况下 i=lrvi=x 的方案数

注意到前两维对 f 的决定作用只和 rl+1 有关,所以将状态定义为 fn,x 再转移:

fn,x=j=0n1(n1j)v=0xmin(j+1,nj+1)fj,vfnj1,xvmin(j+1,nj+1)

写成 OGF 的形式有 Fn=j=0n1(n1j)FjFnj1xmin(j+1,nj+1)

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

那么使用拉格朗日插值还原多项式的方法得到答案即可,即先带入 x=1735 做上面的 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 @   没学完四大礼包不改名  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示