「考试总结」2022-02-06

A.我醉

分奇数和偶数二分回文串长度,判断是不是存在使用点分治

现在问题是判断是不是存在一个跨过当前分治重心的长度为 len 的回文串,可以先扫出来每个联通块中心到根正向/反向的哈希值

再扫一次联通块里面的每个点,求出来剩下需求的长度 (lendep) 中是不是有和自己根链上相同哈希值的串,再判断自己根链上剩下的一部分是不是回文串即可

直觉上是 3log,不太能跑过,所以乱搞做法就是随机一个排列,逐个枚举点作为回文串中心,暴力判断是不是可以扩展

Code Display
const int N=2e5+10;
int tim,mark[N],n,id[N];
vector<pair<int,int> > G[N];
unordered_map<ull,int>mp,dou;
inline int solve(int cen){
	mark[cen]=++tim;
	vector<tuple<ull,int,int> > now,pre;
	for(auto e:G[cen]) now.emplace_back((ull)e.sec,e.fir,e.fir),mark[e.fir]=tim;
	for(int len=0;len<2*n;++len){
		mp.clear(); dou.clear();
		swap(now,pre); now.clear();
		for(auto tmp:pre){
			int top,nowt;
			ull hs;
			tie(hs,top,nowt)=tmp;
			if(!mp.count(hs)) mp[hs]=top;
			else if(mp[hs]!=top) dou[hs]=1;
		}
		for(auto tmp:pre){
			int top,nowt;
			ull hs;
			tie(hs,top,nowt)=tmp;
			if(dou[hs]) now.emplace_back(tmp);
		}
		if(!now.size()) return len;
		swap(now,pre); now.clear();
		for(auto tmp:pre){
			int top,nowt;
			ull hs;
			tie(hs,top,nowt)=tmp;
			for(auto e:G[nowt]){
				int t=e.fir; if(mark[t]==tim) continue;
				mark[t]=tim;
				now.emplace_back(hs*133331+(ull)e.sec,top,t);
			}
		}
	} return 2*n;
}
mt19937 Rand(260823);
signed main(){
    freopen("name.in","r",stdin); freopen("name.out","w",stdout);
    n=read();
    for(int i=1;i<n;++i){
        int x=read(),y=read(),z=read();
        G[x].push_back({i+n,z});
        G[y].push_back({i+n,z});
    	G[i+n].push_back({x,z});
    	G[i+n].push_back({y,z});
    }
    if(G[1].size()==n-1){
        bool vis[1001]={};
        for(auto t:G[1]){
            if(vis[t.sec]) print(2),exit(0);
            else vis[t.sec]=1;
        }
        print(1); exit(0);
    }
    for(int i=1;i<2*n;++i) id[i]=i; random_shuffle(id+1,id+(n<<1));
    int ans=0;
    for(int i=1;i<2*n;++i){
    	ckmax(ans,solve(id[i]));
    	if(clock()/CLOCKS_PER_SEC>=5.97) break;
    }
    print(ans);
    return 0;
}

B.梧桐依旧

将所有满秩的 n 阶矩阵视作一个群 G,那么原问题就是求解在群 G 作用下的不动点个数

根据 Burnside 引理:

|X/G|=1|G|gG|Xg|

也就是说等价类个数等于不动点个数的平均值,那么不动点总数就是等价类个数乘群的大小

第一个部分就是求满秩矩阵的个数,本质上就是选出来 n 个线性无关的向量的方案

选择第一个向量时方案是 pn1,因为这个前面的向量所形成的维度是 0,所以这个向量可以向任意方向扩展(也就是说 Veci[0,p1] 即可),同时不能是 0 向量

在选择第二个向量的时候不能和第一个向量线性相关,也就是 Vec2=λVec1,而 λ 的选值是 [0,p1],所以方案数是 pnp

那么第三个向量的选值方案数和上面类似,这里 λ2 个,所以不合法方案是 p2,选向量的方案数是 pnp2

依次类推,群的大小就是 i=0n1pnpi

剩下要求的就是等价类个数,这里有一个结论是左乘矩阵等价于做初等行变换,即 B 左乘 A 的含义就是将矩阵 B 的第 i 行乘 Ai,j 加到第 j 行上(i=j 时附加系数是 Ai,i1

那么这里所有形式的初等行变换就可以视作一个置换群

不难发现秩不同的矩阵一定不在一个等价类 {A} 中,那么枚举矩阵的秩 k 逐个计算

考虑对于一个特定的秩 k,矩阵的个数的计算是和群大小计算类似的 i=0k1pnpi,只考虑线性无关的这些行就行了

接下来计算 k×k 的行变换 {B} 种数,这个和数一个 k 阶满秩矩阵是等价的,所以方案数是 i=0k1pkpi

考虑一个可重集合 {C} ,其中元素满足 yx(xA,yB) 形式,不难发现这就是特定秩的矩阵总数,那么发现其每个 {A} 中的元素都在 {C} 出现了 |{B}| 次,这是因为全集是每个等价类是通过不同行变换得到的

不要漏记了全 0 矩阵作为一个等价类!

综上所述:答案为

i=0n1pnpi(1+i=1nj=0i1pnpjpipj)

发现分子是一段 pk1 的后缀乘积,分母是前缀乘积,所以先算后缀积,再维护前缀积的逆元就可以 Θ(n) 做了

Code Display
const int N=3e7+10;
int pre[N],suf[N],pw[N],n,p;
signed main(){
	freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
	n=read(); p=read();
	pw[0]=1; rep(i,1,n) pw[i]=mul(pw[i-1],p);
	int siz_G=1;
	for(int i=0;i<n;++i) ckmul(siz_G,del(pw[n],pw[i]));
	int sum=1;
	pre[0]=suf[n+1]=1;
	for(int i=n;i>=1;--i) suf[i]=mul(suf[i+1],del(pw[i],1));
	pre[n]=ksm(suf[1],mod-2);
	Down(i,n,2) pre[i-1]=mul(pre[i],del(pw[i],1));
	for(int i=1;i<=n;++i) ckadd(sum,mul(suf[n-i+1],pre[i]));
	print(mul(siz_G,sum));
	return 0;
}

C.卿且去

(整数,倍数) 定义为偏序集,这里可比就是 x|y,反之不可比

那么原题中 选出来一组质数之后 就是要求得到集合的最长反链,使用 Dilworth定理 就可以知道是要求最小的链划分

这时候可以贪心得到选数字策略:每次选出来当前集合里面最小的数 x,并取 x,2x,4x 作为一组新的链划分

按照上述策略证明这些数字都没被覆盖过是容易的,同时如果在选择 x 之后不选择 2x 那么 2x 又要新开一组链来覆盖之,所以该策略一定能得到最小链划分也是正确的

问题至此变成了按上述策略拆集合后链划分的总数量,将每个划分的 1 的贡献挂到链首元素处,再考虑每个整数 x 的贡献:

简记 π(n)n 的奇数个数,ω(x) 表示 x 的质因子个数

  • x{odd} 那么只要选出其因子的一个就会产生贡献,数值是 2π(n)ω(x)(2ω(x)1)

  • x 是偶数但是不是 4 的倍数时,如果 x 的质因子中有且只有 2 在挑出来的质数集合中那么贡献,否则不贡献,这等价于钦定了 x 的每个质因子出现在 "挑出来的质数集合" 与否,而对其它因子是否出现并不关注,所以方案数为 2π(n)ω(x)

问题转化成了求奇数的 12ω(x) 的前缀和,12ω(x) 显然是积性函数,使用 min25 筛即可

Code Display
const int N=6e6+10;
int n,pin=-1;
namespace Min_25{
	int pri[N/10],g[N],cnt,Basnum,id1[N],id2[N],tot,R[N],block;
	bool fl[N];
	inline int getid(int x){return x<=block?id1[x]:id2[n/x];}
	inline int S(int n,int i){
		if(pri[i]>=n) return 0;
		int sum=del(g[getid(n)],mul(Basnum,i-1));
		for(int j=i+1;j<=cnt&&pri[j]*pri[j]<=n;++j){
			int prd=pri[j],Enum=1;
			while(n>=prd){
				ckadd(sum,mul(Basnum,add(Enum!=1,S(n/prd,j))));
				Enum++;
				prd*=pri[j];
			}
		}
		return sum;
	}
	inline int solve(int NN,int basnum){
		tot=cnt=0;
		memset(pri,0,sizeof(pri));
		memset(g,0,sizeof(g));
		memset(id1,0,sizeof(id1));
		memset(id2,0,sizeof(id2));
		memset(R,0,sizeof(R));
		memset(fl,0,sizeof(fl));
		n=NN; Basnum=basnum; block=sqrt(n);
		for(int i=2;i<=block;++i){
			if(!fl[i]) pri[++cnt]=i;
			for(int j=1;j<=cnt&&pri[j]*i<=block;++j){
				fl[i*pri[j]]=1;
				if(i%pri[j]==0) break;
			}
		}
		for(int l=1,r;l<=n;l=r+1){
			R[++tot]=r=n/(n/l);
			g[tot]=((R[tot]+1)/2-1)%mod;
			if(R[tot]<=block) id1[R[tot]]=tot;
			else id2[n/R[tot]]=tot;
		}
		for(int j=2;j<=cnt;++j){
			for(int i=tot;pri[j]*pri[j]<=R[i];--i){
				int pos=getid(R[i]/pri[j]);
				ckdel(g[i],del(g[pos],j-2));
			}
		}
		if(pin==-1) pin=add(g[getid(n)],1);
		rep(i,1,tot) ckmul(g[i],basnum);
		return S(NN,1); 
	}
}
using Min_25::solve;
signed main(){
	freopen("yyds.in","r",stdin); freopen("yyds.out","w",stdout);	
	n=read();
	int tmp=solve(n,(mod+1)/2),val=ksm(2,pin);
	int ans1=mul(val,del(((n+1)/2-1)%mod,tmp));
	ckmul(val,(mod+1)>>1);
	int ans2=mul(val,add(1,solve(n/2,(mod+1)/2)));
	print(add(ans1,ans2));
	return 0;
}
posted @   没学完四大礼包不改名  阅读(141)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示