HGOI11.1集训题解

本文是HGOI2021集训的题解,包括三道题目:序列、锁和正方形。序列题通过排序和二分查找解决;锁题利用集合数学建模,暴力枚举解决;正方形题通过数学转化和前缀积处理,涉及素数贡献和快速幂计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题解

我觉得我剩下的日子已经不多了,这一份题解很有写的意义。


第一题——序列(sequence)

【题目描述】

  • 小Z 有一个序列,定义f(x)为x 在十进制下的位数,特别地,求∑1≤i&lt;j≤nf(ai+aj)\sum_{1\leq i &lt;j\leq n}f(a_i+a_j)1i<jnf(ai+aj)
  • 其中n≤106,ai≤108n\leq10^6,a_i\leq10^8n106,ai108

  • 显然这个题目拿到手有点难考虑。
  • 我们考虑对于f(ai+aj)f(a_i+a_j)f(ai+aj),只有当两个数相加进位的时候才会对答案做出+1的贡献,否则就是原来的位数。
  • 那么将原有数列进行排序,对答案无影响。
  • 对于单个的aia_iai,只有当iii之前的aja_jaj满足10k−ai≤aj(10k&lt;ai≤10k−1)10^k-a_i\leq a_j(10^k&lt;a_i\leq 10^{k-1})10kaiaj(10k<ai10k1)才能对答案做出f(ai)+1f(a_i)+1f(ai)+1的贡献,否则贡献为f(ai)f(a_i)f(ai)
  • 然后二分查找满足条件的aja_jaj的个数就可以了
  • 注意数据,要开longlong,不然你连10410^4104的数据都过不了

#include <bits/stdc++.h> 
#define LL long long
using namespace std;
void fff(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
}
LL read(){
	LL x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x;
}
const int N=1000010;
LL n;
LL a[N];
int main(){
//	fff();
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+n+1);
	LL ans=0;
	LL t=10;
	for(int i=1;i<=n;i++){
		LL tt=a[i],siz=0;
		while(tt){
			siz++;
			tt/=10;
		}
		while(t-a[i]<=0) t*=10;
		LL temps=lower_bound(a+1,a+i,t-a[i])-a;
		ans+=(LL)(i-temps)*(siz+1);
		ans+=(LL)siz*(temps-1);
	}
	cout<<ans<<'\n';
}

第二题——锁(lock)

【题目描述】

  • 给出n个人,m和n个人的权值值,给每一个人附上若干把钥匙使得满足,每把钥匙对应一把锁,每个钥匙可以发给多个人,每个人可以拿多把钥匙。
    求满足下面条件的最少的锁的数目
  1. 任意权值和SSS满足S&lt;mS&lt;mS<m的人的集合都无法开启所有的锁。
  2. 任何权值和SSS满足S≤S\leqS的人的集合能够开启所有的锁。

  • 这个题目真的不是很好打,最开始乱搞只拿了20分。看了题解才知道是集合数学建模,但大家都不会也没什么亏的。
  • 考虑两个满足S&lt;mS&lt;mS<m的不同的集合,如果两个集合的权值恰好比mmm小,再加上一个不包含的最小值比mmm要大的情况下,不能同时缺同一把钥匙,简单证明就是
    满足S1&lt;m,m≤S1+min1S_1&lt;m,m\leq S_1+min_1S1<m,mS1+min1S2&lt;m,m≤S2+min2S_2&lt;m,m\leq S_2+min_2S2<m,mS2+min2
  • 那么m≤S1+S2m\leq S_1+S_2mS1+S2
  • 但是两个集合的人还是缺一把钥匙,不满足条件2,假设就不能成立
  • 那么就可以知道,答案的个数就是满足S&lt;m,m≤S+minS&lt;m,m\leq S+minS<m,mS+min的集合个数
  • 那就暴力枚举集合就可以了。
  • 如果所有人的权值和都比mmm小,那就只要1把锁,没有人有钥匙就可以了。
  • 非常不错的一道题

#include <bits/stdc++.h> 
#define LL long long
using namespace std;
void fff(){
	freopen("lock.in","r",stdin);
	freopen("lock.out","w",stdout);
}
LL read(){
	LL x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x;
}
const int N=40;
LL n,m;
LL a[N];
const LL INF=1e9;
int main(){
//	fff();
	LL sum,minn;
	n=read(),m=read();
	LL ans=0;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=0;i<(1<<n);i++){
		sum=0;minn=INF;
		for(int j=1;j<=n;j++){
			if((i&(1<<(j-1)))!=0)sum+=a[j];
			else minn=min(minn,a[j]);
		}
		if(sum<m&&sum+minn>=m) ans++;
	}
	ans=max(ans,(LL)1);
	cout<<ans;
}

第三题——正方形(square)

【题目描述】

  • 给出T(T≤106)T(T\leq 10^6)T(T106)n(n≤107)n(n\leq 10^7)n(n107),求出∏i=1n∏j=1nlcm(i,j)i∗lcm(i,j)j\prod_{i=1}^{n}\prod_{j=1}^{n}{\frac{lcm(i,j)}{i}*\frac{lcm(i,j)}{j}}i=1nj=1nilcm(i,j)jlcm(i,j)

  • 这个题目一看就知道不是很和谐了,数学题放到最后面都没什么人做出来orz。打打暴力好像还有40分。

  • 题解里好像各种打法都有,50-75不等,反演,欧拉函数等等,都可以进行优化,此处不赘述

  • 正解:

  • 先将式子化简∏i=1n∏j=1nlcm(i,j)i∗lcm(i,j)j=∏i=1n∏j=1ni∗jgcd(i,j)i∗i∗jgcd(i,j)j\prod_{i=1}^{n}\prod_{j=1}^{n}{\frac{lcm(i,j)}{i}*\frac{lcm(i,j)}{j}}=\prod_{i=1}^{n}\prod_{j=1}^{n}{\frac{\frac{i*j}{gcd(i,j)}}{i}*\frac{\frac{i*j}{gcd(i,j)}}{j}}i=1nj=1nilcm(i,j)jlcm(i,j)=i=1nj=1nigcd(i,j)ijjgcd(i,j)ij
    =∏i=1n∏j=1ni∗jgcd(i,j)2=\prod_{i=1}^{n}\prod_{j=1}^{n}{\frac{i*j}{gcd(i,j)^2}}=i=1nj=1ngcd(i,j)2ij

  • 考虑对于每一个i,ji,ji,j是相互等价的,那么可以减少运算次数,变成:
    (∏i=1n∏j=1ii∗jgcd(i,j))2(\prod_{i=1}^{n}\prod_{j=1}^{i}\frac{i*j}{gcd(i,j)})^2(i=1nj=1igcd(i,j)ij)2

  • 由于上下可以相互分离不影响,那么就可以改写成:
    ∏i=1n∏j=1ni∗j∏i=1n∏j=1ngcd(i,j)2\frac{\prod_{i=1}^{n}\prod_{j=1}^{n}i*j}{\prod_{i=1}^{n}\prod_{j=1}^{n}gcd(i,j)^2}i=1nj=1ngcd(i,j)2i=1nj=1nij

  • 我们发现对于上面的分子来说,可以写成
    ∏i=1nin∗n!=(n!)2n\prod_{i=1}^{n}i^n*n!=(n!)^{2n}i=1ninn!=(n!)2n

  • 这一部分可以O(n)O(n)O(n)前缀积处理。

  • 我们考虑分母∏i=1n∏j=1ngcd(i,j)2\prod_{i=1}^{n}\prod_{j=1}^{n}gcd(i,j)^2i=1nj=1ngcd(i,j)2

  • 题解上面就一句“我们单独考虑每个素数对答案的贡献,对于一个素数x,它在kx处会对答案有(2k-1)的贡献,求一遍前缀积即为答案”,但这对于读者来说过于草率,非常难以理解。在此进行展开

  • 考虑素数筛出每一个质数,对于每一个nnn的质因子pip_ipinnn之前每pip_ipi个数当中就会有一个数与nnngcdgcdgcdpip_ipi,那么可以通过倍数法求出这些数的乘积,而对于与nnngcdgcdgcdpi2,pi3p_i^2,p_i^3pi2,pi3等等的,由于之前已经筛过了pip_ipi的倍数,之后只要叠加再筛pip_ipi就可以了。代码实现较为难理解。

if(!visited[i]){//如果i是质数
			LL t=i;//t是质数
			for(int k=1;t<=T;k++,t*=i){//此处设T是最大值N,t逐渐变成p1^2,p1^3...
				for(LL tmp=t,num=i;tmp<=T;tmp+=t,num=1ll*num*i%MOD*i%MOD)//由于产生的gcd(i,i)等价,所以只筛一次
				//从前往后寻找
					sum[tmp]=1ll*sum[tmp]*num%MOD;//类乘
			}
			for(int k=i;k<=T;k+=i) visited[k]=true;//素数筛
		}

然后这一部分处理好之后就可以前缀积求出分母了。
然后暴力快速幂求出分子和分母的逆元(存在取模)。


#pragma GCC optimize(2)
#pragma G++ optimize(2)
#include <bits/stdc++.h> 
#define LL long long
using namespace std;
void fff(){
	freopen("square.in","r",stdin);
	freopen("square.out","w",stdout);
}
int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x;
}
const int MOD=19260817;
const int N=1e7+10;
const int T=1e7;
int n;
int prime[N],cnt;
LL sum[N],mul[N];
LL f[N];
int maxx=0;
bool visited[N];
LL power(int x,int y){
	int i=x;x=1;
	while(y>0){
		if(y%2==1)x=1ll*x*i%MOD;
		i=1ll*i*i%MOD;
		y/=2;
	}
	return x;
}
int main(){
//	fff();
	for(int i=0;i<=T;i++) sum[i]=1;
	for(int i=2;i<=T;i++){
		if(!visited[i]){
			LL t=i;
			for(int k=1;t<=T;k++,t*=i){
				for(LL tmp=t,num=i;tmp<=T;tmp+=t,num=1ll*num*i%MOD*i%MOD)
					sum[tmp]=1ll*sum[tmp]*num%MOD;
			}
			for(int k=i;k<=T;k+=i) visited[k]=true;
		}
	}
	for(int i=1;i<=T;i++) 
		sum[i]=1ll*sum[i-1]*sum[i]%MOD;
	for(int i=1;i<=T;i++) 
		sum[i]=sum[i]*sum[i]%MOD;
	mul[0]=1;
	for(int i=1;i<=T;i++)
		 mul[i]=1ll*mul[i-1]*i%MOD;
	int t;t=read();
	while(t--){
		n=read();
		int ans=1ll*power(mul[n],2*n)*power(sum[n],MOD-2)%MOD;
		printf("%d\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值