Examples

2022-7-22 #16 P7275 & P5362 & uoj425

海伊生日呀😘。

typora 导出不了 pdf 可以查看 print spooler 是否启用,然后关闭所有 typora 进程继续尝试。

040 P7275 计树

我们需要钦定出若干条编号连续且长度大于 \(1\) 的链,然后将这些链拼在一起。实际上就是将 \([1,n]\) 划分成若干个长度大于 \(1\) 的段,然后将这些段拼接。

由于我们无法保证链之间不能合并,可以先用 Prufer 计算合并方案数,再采用集合划分容斥。(这个命名实际上不严谨,这种容斥方式只是类似集合划分容斥)

令容斥系数为 \(F(x)\),第 \(i\) 项系数对应长度为 \(i\) 的段的容斥系数。

注意集合划分容斥中的“合并等价类”在本题中会变为“拼接连续段”,所以我们应得到:

\[[x^k](\frac{1}{1-F}-1)=[k>1]\\\frac{1}{1-F}-1=\frac{x^2}{1-x}\\F=\frac{x^2}{x^2-x+1} \]

那么我们可以直接列出答案的生成函数:

\[\sum_{t\geqslant 1}[x^n](\sum_{i\geqslant 1} nix^iF_i)^t\\=[x^n](\frac{1}{1-\sum_{i\geqslant 1}nix^iF_i}-1) \]

多项式求逆即可,复杂度 \(O(n\log n)\)

似乎可以通过线性递推得到更低的复杂度。

#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=1<<18,maxk=19,mod=998244353,G=3,invG=(mod+1)/3;
typedef vector<int>poly;
int n,m,lim;
int btf[maxn],w[maxk][maxn][2];
int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1)
			res=1ll*res*a%mod;
		a=1ll*a*a%mod,b>>=1;
	}
	return res;
}
void init(){
	for(int len=2,i=1;i<maxk;len<<=1,i++){
		int o0=ksm(G,(mod-1)/len),o1=ksm(invG,(mod-1)/len);
		w[i][0][0]=w[i][0][1]=1;
		for(int j=1;j<len;j++)
			w[i][j][0]=1ll*w[i][j-1][0]*o0%mod,w[i][j][1]=1ll*w[i][j-1][1]*o1%mod;
	}
}
int getlen(int n){
	int r=0;
	while((1<<r)<n)
		r++;
	for(int i=0;i<(1<<r);i++)
		btf[i]=(btf[i>>1]>>1)|((i&1)<<(r-1));
	return (1<<r);
}
void NTT(poly &X,int lim,int opt){
	X.resize(lim);
	int *x=X.data();
	for(int i=0;i<lim;i++)
		if(i<btf[i])
			swap(x[i],x[btf[i]]);
	for(int p=1,l=2;l<=lim;p++,l<<=1)
		for(int i=0;i<lim;i+=l)
			for(int j=0;j<(l>>1);j++){
				int a=x[i+j],b=1ll*x[i+j+(l>>1)]*w[p][j][opt]%mod;
				x[i+j]=(a+b)%mod,x[i+j+(l>>1)]=(a-b+mod)%mod;
			}
	if(opt==1){
		int v=ksm(lim,mod-2);
		for(int i=0;i<lim;i++)
			x[i]=1ll*x[i]*v%mod;
	}
}
void polyinv(poly &x,int deg){
	if(deg==1){
		x.resize(1),x[0]=ksm(x[0],mod-2);
		return ;
	}
	poly a=x,b=x;
	a.resize(deg),polyinv(b,(deg+1)>>1);
	int lim=getlen(deg<<1);
	NTT(a,lim,0),NTT(b,lim,0);
	x.resize(lim);
	int *X=x.data();
	for(int i=0;i<lim;i++)
		X[i]=1ll*b[i]*(2-1ll*a[i]*b[i]%mod+mod)%mod;
	NTT(x,lim,1),x.resize(deg);
}
poly F;
int main(){
	scanf("%d",&n),init();
	F.resize(n+1);
	for(int i=2;i<=n;i++)
		F[i]=(i%6==4||i%6==1)? 0:((i%6==2||i%6==3)? 1:(mod-1));
	for(int i=0;i<=n;i++)
		F[i]=1ll*(mod-n)*i%mod*F[i]%mod;
	F[0]=1;
	polyinv(F,n+1);
	printf("%d\n",(int)(1ll*F[n]*ksm(1ll*n*n%mod,mod-2)%mod));
	return 0;
}

041 P5362 [SDOI2019]连续子序列

考虑 Thue−Morse 序列的一种生成方式:先构造出长为 \(2^{n-1}\) 的 Thue−Morse 序列,然后将每个 \(0\) 变成 \(01\),每个 \(1\) 变成 \(10\)

我们想要将 \(S\) 不断“上推”,当 \(|S|\) 到达 \(O(1)\) 级别,我们分类讨论得到答案。

可以证明对于长度 \(>3\) 的串,“上推”的方案是唯一的,比如 \(1010\) 能变成 \(11,000\),而后面那种不合法。

于是直接分讨即可,我们分讨要不要把第一个位置单独拿出来。

复杂度 \(O(|S|(\log |S|+\log k))\)

#include<stdio.h>
#include<iostream>
#include<map>
using namespace std;
const int maxn=10005,mod=1000000009;
int T,tot;
string s;
long long k;
map<string,int>dic;
map<long long,int>mp[maxn];
int calc(string s,long long k){
	if(s.size()==1&&k<=2)
		return k==0? 1:(k==1? 2:3);
	if(s.size()==2&&k<=1)
		return k==0? 1:(s[0]==s[1]? 1:2);
	if(s.size()==3&&k<=0)
		return s[0]==s[1]&&s[1]==s[2]? 0:1;
	if(dic.count(s)==0)
		dic[s]=++tot;
	int t=dic[s];
	if(mp[t].count(k))
		return mp[t][k];
	int n=s.size(),res=0,flg=1;
	for(int i=0;i+1<n;i+=2)
		flg&=(s[i]!=s[i+1]);
	if(flg){
		string tmp="";
		for(int i=0;i<n;i+=2)
			tmp=tmp+s[i];
		res=calc(tmp,s.size()&1? (k/2):((k+1)/2));
	}
	flg=1;
	for(int i=1;i+1<n;i+=2)
		flg&=(s[i]!=s[i+1]);
	if(flg){
		string tmp="";
		tmp=tmp+(char)(s[0]^1);
		for(int i=1;i<n;i+=2)
			tmp=tmp+s[i];
		res+=calc(tmp,s.size()&1? ((k+1)/2):(k/2));
	}
	return mp[t][k]=res%mod;
}
int main(){
	scanf("%d",&T);
	while(T--){
		cin>>s,scanf("%lld",&k);
		printf("%d\n",calc(s,k));
	}
	return 0;
}

042 uoj#425. 【集训队作业2018】strings

比较经典的复杂度平衡方法。

首先折半一下,暴力枚举一半的二进制位,得到哪些询问能对答案贡献。一个比较 naive 的想法是用 bitset 存每个询问对应合法答案然后 or 起来,这样复杂度是 \(O(2^{\frac n2}nq+\frac{2^nq}{\omega})\) 的。

但是可以类似四毛子地分块,每个块处理每个子集对应的 bitset,复杂度就变成了 \(O(2^{\frac n2}(n+\frac{2^B}{\omega})q+\frac{2^nq}{B\omega})\),取 \(B=10\) 即可。

#include<stdio.h>
#include<bitset>
#include<iostream>
using namespace std;
const int maxq=105,B=10;
int n,q,ans,S,T;
int bl[B+5],br[B+5];
bitset<1<<15>res,b[maxq],rec[B+5][1<<B];
string s[maxq];
int main(){
	scanf("%d%d",&n,&q),S=1<<(n/2);
	for(int i=1;i<=q;i++){
		cin>>s[i];
		for(int j=0;j<S;j++){
			int flg=1;
			for(int k=0;k<n/2;k++)
				flg&=(s[i][k]=='?'||s[i][k]-48==((j>>k)&1));
			b[i][j]=flg;
		}
	}
	T=q/B;
	for(int i=1;i<=T;i++)
		bl[i]=br[i-1]+1,br[i]=i*B;
	if(br[T]<q)
		T++,bl[T]=br[T-1]+1,br[T]=q;
	for(int i=1;i<=T;i++)
		for(int j=0;j<1<<(br[i]-bl[i]+1);j++)
			for(int k=0;k<br[i]-bl[i]+1;k++)
				if((j>>k)&1)
					rec[i][j]|=b[bl[i]+k];
	for(int t=0;t<(1<<(n-n/2));t++){
		res.reset();
		for(int i=1;i<=T;i++){
			int st=0;
			for(int j=0;j<br[i]-bl[i]+1;j++){
				int flg=1;
				for(int k=0;k<n-n/2;k++)
					flg&=(s[bl[i]+j][n/2+k]=='?'||s[bl[i]+j][n/2+k]-48==((t>>k)&1));
				st|=(flg<<j);
			}
			res|=rec[i][st];
		}
		ans+=res.count();
	}
	printf("%d\n",ans);
	return 0;
}

043 AGC053F ESPers

很神秘的题啊。

044 uoj#705. 黄忠庆功宴

很有趣的题目!

posted @ 2022-07-22 14:30  xiaoziyao  阅读(67)  评论(0编辑  收藏  举报