2022-7-22 #16 P7275 & P5362 & uoj425
海伊生日呀😘。
typora 导出不了 pdf 可以查看 print spooler 是否启用,然后关闭所有 typora 进程继续尝试。
040 P7275 计树
我们需要钦定出若干条编号连续且长度大于 \(1\) 的链,然后将这些链拼在一起。实际上就是将 \([1,n]\) 划分成若干个长度大于 \(1\) 的段,然后将这些段拼接。
由于我们无法保证链之间不能合并,可以先用 Prufer 计算合并方案数,再采用集合划分容斥。(这个命名实际上不严谨,这种容斥方式只是类似集合划分容斥)
令容斥系数为 \(F(x)\),第 \(i\) 项系数对应长度为 \(i\) 的段的容斥系数。
注意集合划分容斥中的“合并等价类”在本题中会变为“拼接连续段”,所以我们应得到:
那么我们可以直接列出答案的生成函数:
多项式求逆即可,复杂度 \(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. 黄忠庆功宴
很有趣的题目!