【考试总结】2022-01-03

young

推广 m4 的部分分发现求最小生成树的过程就是从高到低考察每一位,对于点权在这位为 0 的点会被连成一个连通块而点权为 0 的点连成一个连通块,最后使用一条边将这两个连通块连起来,同时连接的两个点是两个连通块里面 xor 值最小的一对

f(n,m) 表示 n 个点,每个点点权在 [0,2m) 的生成树的边权和,那么执行上面的叙述进行计数:枚举这位点权是 0 的集合大小 s,另 t=ns 即第 m 位点权为 1 的集合大小

此时最小生成树边权和被拆成了 两个点集内部 和 两个点集之间 的两个部分,点集内部的求解便是一个子问题,会给 f(n,m) 贡献 f(t,m1)×2(m1)×s+f(s,m1)×2(m1)×t

待解决的问题是两个集合之间的边权和,设 g(s,t,m) 表示两个大小为 s,t 集合,权值为 [0,2m) 的集合间连边权值和

使用经典转化:n=i=1n[ni] 也就是说;另外计算 p(s,t,m,k) 表示同上述含义的局面中最终最小边的权值 k方案数

统计的方式和 f 类似,枚举 s 中在第 m 位为 0/1 的点数 s0/1t 中第 m 位为 0/1 的点数 t0/1

k 的第 m 位为 1 时要求不能让 s0,t0 或者 s1,t1 同时出现,这等价于直接求 p(s,t,m1,k2m1) 这个子问题,由于 s0,t1s1,t0 也是等价的,所以甚至可以直接乘 2 记为 p(s,t,m,k)

k 的第 m 位为 0 时就要实打实的枚举大小了,这里与 f 求解处不同的是要求 s0,t0s1,t1 之间每个点的连边都要 k 所以要递归两侧,分别是 p(s0,t0,m1,k),g(s1,t1,m1,k)

注意 f,p 求解的时候显然需要乘组合数,边界除了 p 中是 m=0 判断 k0 和一个为 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|len(T) | len(S)TS 的周期),其所有循环移位的最小者会被计入答案,也就是说任意一个长度为 len 非周期串对 f(len) 的贡献为 1len

其实这就是最朴素的 Lyndon Word 的结论,赛时没有推出的原因是一直在给 n104 的档编 DP,甚至编了不少于两个小时也没能编出

关于计算非周期串可以使用最平凡的莫比乌斯反演,即设 F(n) 表示长度为 n 的串的个数,G(n) 表示长度为 n 的非周期串的个数

F(n)=d|nG(d)G(n)=d|nμ(nd)F(d)

将推导带回答案并推导,写出表达式如下:

Ans=i=1ni2×1i×G(i)=i=1nid|iμ(id)10d=d=1nd10dj=0nij×μ(j)

最后一步使用了精彩的 i=j×d,那么后面部分的求解是杜教筛模板,其中 f=μ×id,g=id,h=ϵ

前面一部分是等差乘等比数列求和的计算,是一个历史遗留问题,我今天花了 30s 推导了一下得到了非常简洁的形式:

S=i=0ni10i10S=i=1n+1(i1)×10i9S=n10n+1+i=1n10iS=n10n+110n+1199

这就能做了,只需要会写快速幂!非常简单是不是!

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

使用最基础的 exLucas 即可解决,那么写这么长长的一篇博客就是来写一份 exLucas 学习笔记的!

记录 Lucas 定理的证明吧

对于一个质数 ppn(pn)modp 不为 0 当且仅当 n=p 或者 n=0

这时可以发现

(ax+bx)pbpxp+apxpmodpbxp+axpmodp

那么考察 (nm)=[xm](1+x)n 并展开 (1+x)n 得到:

(1+x)n(1+x)pnp(1+x)nmodpmodp(1+xp)np(1+x)nmodpmodp

发现前半部分在 xpi 处有值,后半部分只在 xk(k[0,p) 处有值,那么直接变成两个子问题,写作

(npmp)(nmodpmmodp)

根据唯一分解定理,问题变成了求解 (nm)modpkk 不一定为 1,剩下的使用朴素 CRT 合并即可

(nm) 展开,并提出 n,m,(nm) 的阶乘中 p 的指数(逐个除就是 Θ(log) 的),剩下的也可以求逆元了

剩余的项中包含且限于:npk[1,pk] 中所有不是 p 的倍数的数的乘积,[1,nmodpk] 中不是 p 的倍数的乘积 和 np 的阶乘留下递归

做完了!

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