[XYD20241118] NOIP 模拟赛

[XYD20241118] NOIP 模拟赛

Super Math Round

因子之和

Description

定义 \(\sigma(x)\) 表示 \(x\) 的所有因数之和。

\(\sigma(x)=\sum_{d\mid x} d\)

现在有 \(Q\) 个询问,每次查询区间 \([l,r]\) 内有多少个整数,满足 \(\sigma(x)\ge 2x\)

对于 \(30\%\) 的数据,\(1\le n\le 100,1\le l,r\le 1000\)

对于 \(100\%\) 的数据,\(1\le n\le 2*10^5,1\le l,r\le 10^6\)

Solution

比较好想,直接线筛出 \(\sigma(x)\) ,然后前缀和做差。

官方题解提供了一种比较慢的做法,它是枚举每一个 \(d\) ,然后让 \(d\) 去给它的倍数的 \(\sigma()\) 做贡献,复杂度是 \(O(N \log N)\) 的。

过于简单,不放代码。

小 w 与数字游戏

Description

给定一个包含 \(n\) 个非负整数的序列 \(s\)

每次可以选择两个数 \(s_i,s_j\ (i\neq j)\) ,在序列中删去它们,并且将下列三个数之一加入序列:

  1. \(s_i+s_j\)
  2. \(|s_i-s_j|\)
  3. \(s_i*s_j\)

问,进行 \(n-1\) 次操作之后,序列中剩下来的一个数最小是多少。

多组测试数据

对于 \(30\%\) 的数据,\(n\le 7\)

对于 \(50\%\) 的数据,\(n\le 10\)

对于 \(100\%\) 的数据,\(T\le 5,n\le 3\times 10^5,0\le s_i\le 30\)

Solution

\(30\%\) 做法

直接暴力,维护一个序列,每次枚举把哪两个数删掉以及换成什么

Final Solution

首先最后答案肯定是非负整数,然后大概想一想就会发现似乎很多情况下答案都可以做到 \(0\)

具体地说,如果初始序列中包含 \(0\) 或者有某个数出现了两次及以上,那么就可以构造出一个方案使得最后答案为 \(0\) 。前者是显然的,对于后者,只需要将两个相同的数相减就可以得到一个 \(0\)

然后发现 \(s_i\) 的范围很小,根据抽屉原理,在 \(n>30\) 的情况下,要么序列里面有 \(0\) ,要么就会有至少一个数出现了两次及以上,那么这个时候答案就是 \(0\)

于是现在我们只需要解决 \(n\le 30\) 的问题。

但是似乎并没有一个 \(n\le 30\) 的能过做法,所以我们需要继续缩小 \(n\) 的范围。

然后就尝试构造一个尽可能长的序列,使得里面的任何一个元素都不能够通过别的元素之间做加减乘(减法是取绝对值的减法)操作得到。我这边构造出来最长的长度是 \(5\) 。也就是说只有 \(n\le 5\) 的情况,才可能出现答案不为 \(0\) 的情况。(这么说其实不是很严谨,但是确实如此)

但是官方题解说什么根据斐波那契数列,对于(本题背景下)所有长度大于 \(7\) 的序列 \(s\) ,里面一定可以找到三个数 \(a,b,c\) ,使得 \(a+b-c=0\) 。于是可以得到 \(n\le 7\) 的情况下才可能出现答案不为 \(0\) 的情况。但反正都差不多。

于是原问题就被归约到了 \(n\le 7\) (或者 \(n\le 5\) )的问题,所以我们可以直接使用前面 \(30\%\) 的做法。

树论

Description

给定一张 \(n\) 个节点的完全图,点的编号 \(1\sim n\) ,点 \(i\) 和点 \(j\) 之间有一条权值为 \(\frac{f(i,j)+f(j,i)}{2}\) 的无向边。

其中:

\[f(i,j)= \begin{cases} \frac{i\cdot f(j,i\bmod j)}{i\bmod j} & i\bmod j\neq 0 \\ \frac{i}{j} & i\bmod j=0 \end{cases} \]

求这张图的最小生成树。

答案四舍五入至整数。

测试点编号 $n\le $
\(1\sim 2\) 100
\(3\sim 5\) 300
\(6\sim 7\) 5000
\(8\sim 9\) 10000
\(10\) 3000000

对于所有数据,满足 \(2\le n \le 3\times 10^6\)

如果只答对了最小生成树的边权和,也可以获得该测试点 \(40\%\) 的分数。

Solution

\(50\%\) 做法

Kruskal

\(O(N^2 \log N)\)

据说有着巨大常数,无法通过第三档分,反正我赛时只过了前两档。

\(70\%\) 做法

Prim

\(O(N^2)\)

Final Solution

推式子时间到!

但是先通过打表猜测 \(\DeclareMathOperator{\lcm}{lcm}f(i,j)=\frac{ij}{\gcd(i,j)^2}=\frac{\lcm(i,j)}{\gcd(i,j)}\)

然后考虑归纳,\(i\bmod j=0\) 时显然,

\[\begin{eqnarray} f(i,j)&=&\frac{i\cdot f(j,i\bmod j)}{i\bmod j} \\ &=& \frac{i\cdot \frac{j\cdot (i\bmod j)}{\gcd(j,i\bmod j)^2}}{i\bmod j} \\ &=&\frac{ij}{\gcd(j,i\bmod j)^2} \\ &=&\frac{ij}{\gcd(i,j)^2} \\ &=&\frac{\lcm(i,j)}{\gcd(i,j)} \end{eqnarray} \]

然后是两个引理

对于两个点 \(i,j\ \ (i<j)\) ,若 \(i\nmid j\) 或者 \(\frac{j}{i}\) 不是质数,那么边 \((i,j)\) 一定不会出现在最小生成树中。

\(\forall 1<i\le n\) ,边 \((i,\frac{i}{mn_i})\) 在最小生成树中,其中 \(mn_i\)\(i\) 的最小质因子。

显然这些边不会构成环,所以这些就是 MST 全部的边。

(这东西我不会证,所以搬了题解证明。好了,我们先不管它

然后线筛求出 \(mn_i\) 就做完了。

至于这题能否在推不出引理的情况下,通过 Boruvka 做出,还不知道。

Proof

引理 1

考虑模拟 Kruskal 的过程,从小到大枚举 \(x\) ,加入权值为 \(x\) 的边。

对于 \(i,j\ \ (i<j)\) ,若 \(i\nmid j\)\(\frac{j}{i}\) 不为质数,则一定可以找到一个长为 \(k\) 的序列 \(c\) 满足:

  1. \(c_1=i,c_k=j\)
  2. \(\forall 1\le w<k,c_w\mid c_{w+1}\ \text{或}\ c_{w+1}\mid c_w\)
  3. \(\forall 1\le w<k, \frac{\lcm(c_w,c_{w+1})}{\gcd(c_w,c_w+1)}<\frac{\lcm(i,j)}{\gcd(i,j)}\)

如果存在这样一个序列,则意味着 \(i,j\) 在边 \((i,j)\) 加入之前已经连通了。

考虑构造这样的序列 \(c\) 。先将 \(i,j\) 分解质因子。初始令 \(x=i\) ,然后将 \(i\) 多出的质因子除掉,并加入 \(j\) 多出的质因子,使得最后 \(x=j\) ,将这个过程中的 \(x\) 排列出来得到序列 \(c\)

引理 2

继续考虑模拟 Kruskal 。从小到大枚举质数 \(p\) ,将 \(x\)\(xp\) 相连。

显然,刚枚举到质数 \(p=mn_i\) 时,\(i\)\(\frac{i}{mn_i}\) 一定不连通,故将所有满足 \(mn_i=p\)\((i,\frac{i}{p})\) 全部连起来。

基础数论练习题

Description

定义 \(d(x)\) 表示 \(x\) 的约数个数,即 \(d(x)=\sum_{i\mid x}1\)

给定 \(n,m\) ,考虑所有长度为 \(n\) ,值域为 \([1,m]\) 的由正整数构成的序列 \(a\) ,定义其权值为 \(d(\prod_{i=1}^n a_i)\) ,求所有的 \(m^n\) 个序列的权值之和对 \(p\) 取模的结果。

有多次问询,每次询问给出一个 \(n\) ,求上述问题的答案。但是对于所有问询,其 \(m\) 是相同的。

测试点编号 \(T\le\) \(n\le\) $m\le $
\(1,2\) \(1\) \(7\) \(10\)
\(3\sim 6\) \(10\) \(10^{18}\) \(16\)
\(7 \sim 10\) \(10^5\) \(10^{18}\) \(60\)
\(11\sim 14\) \(10^5\) \(10^{18}\) \(100\)
\(15\sim 20\) \(10^5\) \(10^{18}\) \(10^3\)

对于 \(100\%\) 的数据,\(1\le T\le 10^5,1\le n \le 10^{18},1\le m\le 1000,10^8 \le p\le 10^9+9\)不保证 \(p\) 为质数。

Solution

\(10\%\) 做法

暴搜

\(30\%\) 做法

首先对于约数个数函数,有这样一个观察: \(d(xy)=\sum_{i\mid x}\sum_{j\mid y}[\gcd(i,j)=1]\)

然后这个性质可以推广到多个数的情况:

\[d(a_1a_2\dots a_k)=\sum_{b_{1\dots k},b_i\mid a_i} [\forall 1\le i<j\le k,\gcd(b_i,b_j)=1] \]

然后 \(m\) 非常的小,所以我们可以考虑枚举序列 \(b\) 然后计算每一个合法的序列 \(b\) 对应到多少序列 \(a\) ,因为序列 \(b\) 上面有更强的约束,所以这个比起枚举序列 \(a\) 应该是好做的。

考虑枚举 \(S\subseteq \{2,3,\dots,m\}\) ,表示 \(S\) 中的数在序列 \(b\) 中出现过。由于序列 \(b\) 自身的约束,所以序列 \(b\) 中所有大于 \(1\) 的数只能够出现一次,其他的位置就只能都填 \(1\) 。对于序列 \(b\) 中一个不为 \(1\) 的数 \(x\) ,这个位置对应的序列 \(a\) 中的数可以是任何 \(x\) 的倍数,所以提供一个 \(\lfloor \frac{m}{x} \rfloor\) 的贡献;而对于 \(1\) 则是给出 \(m\) 的贡献。再加上定序的贡献,总贡献为:

\[(\sum_{x\in S} \lfloor \frac{m}{x} \rfloor ) \frac{n!}{(n- |S|)!} m^{n- |S|} \]

然后 \(|S|\) 的上界是 \(\pi(m)\) (其中 \(pi(m)\) 表示 \([1,m]\) 以内的质数个数)

所以预处理一些东西之后,复杂度可以做到 \(O(2^m+T(\pi(m)+\log n))\)

Final Solution

考虑优化 \(30\%\) 做法的计算 \(S\) 的贡献那一部分

\(T(x)\) 表示 \(x\) 的质因子集合,按照顺序加入 \([2,m]\)

考虑 dp :

\(f_{i,S}\) 表示现在序列里面有 \(i\) 个不为 \(1\) 的数,并且质因子出现的集合为 \(S\) 的总贡献

\(T(x)\&S=0\) 时,有转移 \(f_{i+1,S|T(x)}\gets f_{i,S}\times \lfloor \frac{m}{x}\rfloor\)

直接写的复杂度是 \(O(m\pi(m)2^{\pi(m)})\)

大概可以拿到 50 分,稍微优化一下可以拿到 70 分

然后注意到复杂度瓶颈在 \(S\) 的状态数上面。而且你注意到对于一个数,它的质因子里面最多只会出现一个数 \(>\sqrt m\) ,所以其实我们没有必要把 \(>\sqrt m\) 的质因子纳入到状态里面,我们只要对于每个 \(x\) ,算出它的最大的质因子 \(mx_x\) ,然后把所有的 \(x\) 按照 \(mx_x\) 升序排列并依次加入,就可以不需要额外记录 \(>\sqrt m\) 的质因子了。

复杂度降到 \(O(m\pi(m)2^{\pi(\sqrt m)})\)

Code

Code for \(30\%\) Solution

namespace subtask1{
	ll ans=0;
	bool vst[10];
	ll fact[20];
	ll pb[20];
	
	void precompute(){
		fact[0]=1;
		for(int i=1;i<=6;i++)fact[i]=fact[i-1]*((n-i+1)%mod)%mod;
		pb[6]=qpow(m,n-6);
		for(int i=5;i>=0;i--)pb[i]=pb[i+1]*m%mod;
	}
	
	void solve(){
		int nPtn=(1<<(m-1));
		ans=0;
		for(int ptn=0;ptn<nPtn;ptn++){
			ll mul=1;bool tg=1;int cnt=0;
			for(int i=1;i<=6;i++)vst[i]=0;
			for(int i=2;i<=m;i++)if((ptn>>(i-2))&1){
				mul*=m/i;cnt++;
				mul%=mod;
				if(i%2==0){
					if(vst[1]){tg=0;break;}
					vst[1]=1;
				}
				if(i%3==0){
					if(vst[2]){tg=0;break;}
					vst[2]=1;
				}
				if(i%5==0){
					if(vst[3]){tg=0;break;}
					vst[3]=1;
				}
				if(i%7==0){
					if(vst[4]){tg=0;break;}
					vst[4]=1;
				}
				if(i%11==0){
					if(vst[5]){tg=0;break;}
					vst[5]=1;
				}
				if(i%13==0){
					if(vst[6]){tg=0;break;}
					vst[6]=1;
				}
			} 
			if(cnt>n)continue;
			if(tg){
//				cout<<"ptn: "<<ptn<<" mul: "<<mul<<"\n";
				for(int i=0;i<cnt;i++)mul=mul*((n-i)%mod)%mod;
				mul=mul*qpow(m,n-cnt)%mod;
				ans=(ans+mul)%mod;
			}
		}
		cout<<ans<<"\n";
	}
}

Code for Final Solution

namespace subtaskf{
	const int smallPr[11]={2,3,5,7,11,13,17,19,23,29,31};
	int mxp[1005];
	const int R=1e3;
	vector<int> pr;int nP;
	bool isnPrime[1005];
	int ord[1005];
	ll t[1005];
	vector<int> toAdd[180];
	int nTier=0;
	ll f[180][(1<<12)+5];ll sum[180];
	
	bool cmp(int x,int y){return mxp[x]<mxp[y];}
	
    ll fact[1005];
	void precompute(){
		
		for(int i=2;i<=R;i++){
			if(!isnPrime[i]){
				pr.emplace_back(i);
				nP++;
			}
			for(int j=0;j<nP&&1ll*pr[j]*i<=R;j++){
				isnPrime[i*pr[j]]=1;
				if(i%pr[j]==0)break;
			}
		}
		
		for(int i=2;i<=R;i++){
			for(int j=nP-1;j>=0;j--)if(i%pr[j]==0){
				mxp[i]=pr[j];
				break;
			}
			for(int j=0;j<11;j++)if(i%smallPr[j]==0)t[i]|=(1<<j);
		}
		
		for(int i=2;i<=m;i++)ord[i]=i;
		sort(ord+2,ord+1+m,cmp);
		int lst=-1;
		for(int i=2;i<=m;i++){
			int x=ord[i];
			if(mxp[x]!=lst){
				++nTier;
				toAdd[nTier].emplace_back(x);
				lst=mxp[x];
			}else toAdd[nTier].emplace_back(x);
		}
		
//		cout<<"check t: "<<t[37]<<" "<<t[41]<<"\n"; 
		
//		cout<<"nTier: "<<nTier<<"\n";
//		for(int i=1;i<=nTier;i++,cout<<"\n"){
//			cout<<"mxp: "<<mxp[toAdd[i][0]]<<"\n";
//			for(auto x:toAdd[i])cout<<x<<" ";
//		}
			
//		for(int i=2;i<=m;i++)cout<<t[i]<<" ";cout<<"\n";
		
		int nPtn=(1<<11);
		for(int i=1;i<=nTier;i++)
			for(int ptn=0;ptn<nPtn;ptn++)f[i][ptn]=0;
		f[0][0]=1;
		for(int i=1;i<=nTier;i++)
			for(int j=i-1;j>=0;j--)
				for(int ptn=0;ptn<nPtn;ptn++){
//					f[j+1][ptn]+=f[j][ptn];
					for(auto x:toAdd[i]){
						if((ptn&t[x])==0){
							f[j+1][ptn|t[x]]+=(m/x)*f[j][ptn]%mod;
							f[j+1][ptn|t[x]]%=mod;
						}
					}
				}
		
		for(int i=0;i<=nTier;i++)
			for(int ptn=0;ptn<nPtn;ptn++)
				sum[i]=(sum[i]+f[i][ptn])%mod;
				
//		for(int i=0;i<=nTier;i++,cout<<"\n")
//			for(int ptn=0;ptn<nPtn;ptn++)cout<<f[i][ptn]<<" ";
	}
	
	ll pm[1005];	
  
	void solve(){
		fact[0]=1;
		for(int i=1;i<=min(n,1000ll);i++)fact[i]=(ll)((n%mod-i+1)%mod+mod)%mod*fact[i-1]%mod;	
//		int nPtn=(1<<11);
		ll lim=min(n,nTier*1ll);
		pm[lim]=qpow(m,n-lim);
		for(ll i=lim-1;i>=0;i--)pm[i]=pm[i+1]*m%mod;
		ll ans=0;
		/*
		for(int i=0;i<=min(n,1ll*nTier);i++)	
			for(int ptn=0;ptn<nPtn;ptn++)if(f[i][ptn]){
				ll mul=f[i][ptn];
//				if(mul)cout<<"ptn: "<<ptn<<" ISI: "<<i<<" mul: "<<mul<<"\n";
				mul=mul*fact[i]%mod;
				mul=mul*qpow(m,n-i)%mod;
				ans=(ans+mul)%mod;
//				cout<<"add: "<<mul<<"\n";
			}*/
		for(int i=0;i<=min(n,1ll*nTier);i++)if(sum[i]){
			ll mul=sum[i];
			mul=mul*fact[i]%mod;
			mul=mul*pm[i]%mod;
			ans=(ans+mul)%mod;
		}
		cout<<ans<<"\n";
		
//		for(int i=0;i<=nTier;i++,cout<<"\n")
//			for(int ptn=0;ptn<(1<<4);ptn++)cout<<f[i][ptn]<<" ";
	}
}
posted @ 2024-11-21 23:31  hsfzbzjr  阅读(3)  评论(0编辑  收藏  举报