young
推广 \(m\le 4\) 的部分分发现求最小生成树的过程就是从高到低考察每一位,对于点权在这位为 \(0\) 的点会被连成一个连通块而点权为 \(0\) 的点连成一个连通块,最后使用一条边将这两个连通块连起来,同时连接的两个点是两个连通块里面 \(\rm xor\) 值最小的一对
设 \(f(n,m)\) 表示 \(n\) 个点,每个点点权在 \([0,2^m)\) 的生成树的边权和,那么执行上面的叙述进行计数:枚举这位点权是 \(0\) 的集合大小 \(s\),另 \(t=n-s\) 即第 \(m\) 位点权为 \(1\) 的集合大小
此时最小生成树边权和被拆成了 两个点集内部 和 两个点集之间 的两个部分,点集内部的求解便是一个子问题,会给 \(f(n,m)\) 贡献 \(f(t,m-1)\times 2^{(m-1)\times s}+f(s,m-1)\times 2^{(m-1)\times t}\)
待解决的问题是两个集合之间的边权和,设 \(g(s,t,m)\) 表示两个大小为 \(s,t\) 集合,权值为 \([0,2^m)\) 的集合间连边权值和
使用经典转化:\(n=\sum_{i=1}^n[n\ge i]\) 也就是说;另外计算 \(p(s,t,m,k)\) 表示同上述含义的局面中最终最小边的权值 \(\ge k\) 的 方案数
统计的方式和 \(f\) 类似,枚举 \(s\) 中在第 \(m\) 位为 \(0/1\) 的点数 \(s_{0/1}\),\(t\) 中第 \(m\) 位为 \(0/1\) 的点数 \(t_{0/1}\)
当 \(k\) 的第 \(m\) 位为 \(1\) 时要求不能让 \(s_0,t_0\) 或者 \(s_1,t_1\) 同时出现,这等价于直接求 \(p(s,t,m-1,k-2^{m-1})\) 这个子问题,由于 \(s_0,t_1\) 和 \(s_1,t_0\) 也是等价的,所以甚至可以直接乘 \(2\) 记为 \(p(s,t,m,k)\)
\(k\) 的第 \(m\) 位为 \(0\) 时就要实打实的枚举大小了,这里与 \(f\) 求解处不同的是要求 \(s_0,t_0\) 及 \(s_1,t_1\) 之间每个点的连边都要 \(\ge k\) 所以要递归两侧,分别是 \(p(s_0,t_0,m-1,k),g(s_1,t_1,m-1,k)\)
注意 \(f,p\) 求解的时候显然需要乘组合数,边界除了 \(p\) 中是 \(m=0\) 判断 \(k\) 为 \(0\) 和一个为 \(0\) 了剩下随便选 之外都是点集大小/位数不够了返回 \(0\)
代码中的变量含义和叙述中的保持一致。同时为了不超过 80 符被 D 线,刻意增加了行数
Code Display
int pw[5010],n,m;
int f[52][10],g[52][52][10],p[52][52][10][70],C[110][110];
inline int P(int s,int t,int m,int k){
if(s>t) swap(s,t);
if(m==0) return k==0;
if(s==0) return pw[m*t];
if(~p[s][t][m][k]) return p[s][t][m][k]; p[s][t][m][k]=0;
if(k>>(m-1)&1){
p[s][t][m][k]=mul(2,P(s,t,m-1,k^(1<<(m-1))));
return p[s][t][m][k];
}
rep(i,0,s) rep(j,0,t){
if(j==t&&i==0) continue;
if(i==s&&j==0) continue;
int addi=mul(C[s][i],C[t][j]);
ckadd(p[s][t][m][k],mul(addi,mul(P(i,j,m-1,k),P(s-i,t-j,m-1,k))));
}
ckadd(p[s][t][m][k],pw[(s+t)*(m-1)+1]); // s==0 && t==0
return p[s][t][m][k];
}
inline int G(int s,int t,int m){
if(s>t) swap(s,t);
if(!s||!m) return 0;
if(~g[s][t][m]) return g[s][t][m]; g[s][t][m]=0;
for(int i=1;i<(1<<m);++i) ckadd(g[s][t][m],P(s,t,m,i));
return g[s][t][m];
}
inline int F(int n,int m){
if((n<=1)||!m) return 0;
if(~f[n][m]) return f[n][m];
f[n][m]=0;
for(int s=0;s<=n;++s){
int v1=mul(F(s,m-1),pw[(n-s)*(m-1)]); // S self
int v2=mul(pw[s*(m-1)],F(n-s,m-1)); // T self
int v3=G(s,n-s,m-1);
int v4=(s==0||s==n)?0:pw[(n+1)*(m-1)]; // between
ckadd(f[n][m],mul(C[n][s],(v1+v2+v3+v4)%mod));
}
return f[n][m];
}
signed main(){
pw[0]=1; for(int i=1;i<=5000;++i) pw[i]=add(pw[i-1],pw[i-1]);
C[0][0]=1;
for(int i=1;i<=100;++i){
C[i][0]=1;
for(int j=1;j<=i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
}
memset(f,-1,sizeof(f));
memset(g,-1,sizeof(g));
memset(p,-1,sizeof(p));
n=read(); m=read();
print(mul(ksm(pw[n*m],mod-2),F(n,m)));
return 0;
}
simple
对于一个长度为 \(n\) 的非周期串 \(S\)(即不存在 \(T\),\(|T|<|S|\) 且 \(\rm len(T)\ |\ len(S)\),\(T\) 是 \(S\) 的周期),其所有循环移位的最小者会被计入答案,也就是说任意一个长度为 \(len\) 非周期串对 \(f(len)\) 的贡献为 \(\frac 1{len}\)
其实这就是最朴素的 \(\rm Lyndon\ Word\) 的结论,赛时没有推出的原因是一直在给 \(n\le 10^4\) 的档编 \(\rm DP\),甚至编了不少于两个小时也没能编出
关于计算非周期串可以使用最平凡的莫比乌斯反演,即设 \(F(n)\) 表示长度为 \(n\) 的串的个数,\(G(n)\) 表示长度为 \(n\) 的非周期串的个数
将推导带回答案并推导,写出表达式如下:
最后一步使用了精彩的 \(i=j\times d\),那么后面部分的求解是杜教筛模板,其中 \(f=\mu\times id,g=id,h=\epsilon\)
前面一部分是等差乘等比数列求和的计算,是一个历史遗留问题,我今天花了 \(30s\) 推导了一下得到了非常简洁的形式:
这就能做了,只需要会写快速幂!非常简单是不是!
Code Display
const int N=1e7+10,inv2=ksm(2,mod-2);
int pre[N],n,ans,pri[N],mu[N],cnt;
map<int,int> mp;
bool fl[N];
inline int S(int n){
if(n<=1e7) return pre[n];
if(mp.count(n)) return mp[n];
int res=1;
for(int l=2,r;l<=n;l=r+1){
r=n/(n/l);
res-=(r-l+1)%mod*((l+r)%mod)%mod*inv2%mod*S(n/l)%mod;
}
return mp[n]=(res%mod+mod)%mod;
}
const int inv9=ksm(9,mod-2);
inline int F(int n){
int kk=ksm(10,n+1);
int r1=mul(n%mod,kk),r2=mul(del(kk,1),inv9);
return mul(del(r1,r2),inv9);
}
signed main(){
n=1e7; mu[1]=1;
for(int i=2;i<=n;++i){
if(!fl[i]) mu[i]=-1,pri[++cnt]=i;
for(int j=1;i*pri[j]<=n&&j<=cnt;++j){
fl[i*pri[j]]=1;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}else{
mu[i*pri[j]]=-mu[i];
}
}
}
for(int i=1;i<=n;++i){
pre[i]=i*mu[i]+pre[i-1];
pre[i]=(pre[i]%mod+mod)%mod;
}
n=read();
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=mul(del(F(r),F(l-1)),S(n/l));
}
print(ans%mod);
return 0;
}
meet
使用最基础的 \(\rm exLucas\) 即可解决,那么写这么长长的一篇博客就是来写一份 \(\rm exLucas\) 学习笔记的!
记录 \(\rm Lucas\) 定理的证明吧
对于一个质数 \(p\) 和 \(\le p\) 的 \(n\), \(\binom pn\mod p\) 不为 \(0\) 当且仅当 \(n=p\) 或者 \(n=0\)
这时可以发现
那么考察 \(\binom nm=[x^m](1+x)^n\) 并展开 \((1+x)^n\) 得到:
发现前半部分在 \(x^{pi}\) 处有值,后半部分只在 \(x^{k}(k\in [0,p)\) 处有值,那么直接变成两个子问题,写作
根据唯一分解定理,问题变成了求解 \(\binom nm\mod p^k\) 且 \(k\) 不一定为 \(1\),剩下的使用朴素 \(\rm CRT\) 合并即可
将 \(\binom nm\) 展开,并提出 \(n,m,(n-m)\) 的阶乘中 \(p\) 的指数(逐个除就是 \(\Theta(\log)\) 的),剩下的也可以求逆元了
剩余的项中包含且限于:\(\lfloor\frac{n}{p^k} \rfloor\) 个 \([1,p^k]\) 中所有不是 \(p\) 的倍数的数的乘积,\([1,n\bmod p^k]\) 中不是 \(p\) 的倍数的乘积 和 \(\frac{n}{p}\) 的阶乘留下递归
做完了!
Code Display
int n,Mod,x,y;
const int N=1e6+10;
map<int,int> fac;
map<int,vector<int> >ee;
inline int calc(int n,int pi,int pk){
if(n<=1) return 1;
return ksm(fac[pk],n/pk,pk)*ee[pk][n%pk]%pk*calc(n/pi,pi,pk)%pk;
}
inline void prepare(int pi,int pk){
if(n>=pk){
int res=1;
for(int i=2;i<pk;++i) if(i%pi) ckmul(res,i,pk);
fac[pk]=res;
}
vector<int> ii; ii.resize(min(n+1,pk));
ii[0]=ii[1]=1;
int up=ii.size();
for(int i=2;i<up;++i){
if(i%pi) ii[i]=ii[i-1]*i%pk;
else ii[i]=ii[i-1];
}
ee[pk]=ii;
return ;
}
inline void exgcd(int a,int b,int &x,int &y){
if(!b) return x=1,y=0,void();
exgcd(b,a%b,y,x);
y-=(a/b)*x;
return ;
}
inline int getinv(int n,int pk){
int x,y; exgcd(n,pk,x,y);
x=(x%pk+pk)%pk;
return x;
}
inline int C(int n,int m,int pi,int pk){
int up=calc(n,pi,pk),k=0;
int d1=calc(m,pi,pk),d2=calc(n-m,pi,pk);
for(int i=n;i;i/=pi) k+=i/pi;
for(int i=m;i;i/=pi) k-=i/pi;
for(int i=n-m;i;i/=pi) k-=i/pi;
return up*getinv(d1,pk)%pk*getinv(d2,pk)%pk*ksm(pi,k,pk)%pk;
}
inline int CRT(int b,int mod){
return (b*getinv(Mod/mod,mod)%Mod*(Mod/mod))%Mod;
}
int pi[N],pk[N],num;
inline int exLucas(int n,int m){
int res=0;
rep(i,1,num){
res+=CRT(C(n,m,pi[i],pk[i]),pk[i]);
res%=Mod;
}
return res;
}//sigma a_i M_i t_i
signed main(){
n=read(); Mod=read(); x=abs(read()),y=abs(read());
int tmp=Mod;
for(int i=2;i*i<=tmp;++i) if(tmp%i==0){
pk[++num]=1; pi[num]=i;
while(tmp%i==0) pk[num]*=i,tmp/=i;
prepare(pi[num],pk[num]);
}
if(tmp>1) pi[++num]=tmp,pk[num]=tmp,prepare(tmp,tmp);
int ans=0;
for(int lef=0;lef<=n;++lef){
int rig=lef+x,sy=n-lef*2-x;
if(((y+sy)&1)||y>sy) continue;
int up=(y+sy)/2,down=sy-up;
ckadd(ans,mul(exLucas(n,up),mul(exLucas(n-up,down),exLucas(n-sy,lef),Mod),Mod),Mod);
}
print(ans);
return 0;
}