HGOI11.1集训题解

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

题解

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


第一题——序列(sequence)

【题目描述】

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

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

#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. 任意权值和 S S S满足 S &lt; m S&lt;m S<m的人的集合都无法开启所有的锁。
  2. 任何权值和 S S S满足 S ≤ S\leq S的人的集合能够开启所有的锁。

  • 这个题目真的不是很好打,最开始乱搞只拿了20分。看了题解才知道是集合数学建模,但大家都不会也没什么亏的。
  • 考虑两个满足 S &lt; m S&lt;m S<m的不同的集合,如果两个集合的权值恰好比 m m m小,再加上一个不包含的最小值比 m m m要大的情况下,不能同时缺同一把钥匙,简单证明就是
    满足 S 1 &lt; m , m ≤ S 1 + m i n 1 S_1&lt;m,m\leq S_1+min_1 S1<m,mS1+min1 S 2 &lt; m , m ≤ S 2 + m i n 2 S_2&lt;m,m\leq S_2+min_2 S2<m,mS2+min2
  • 那么 m ≤ S 1 + S 2 m\leq S_1+S_2 mS1+S2
  • 但是两个集合的人还是缺一把钥匙,不满足条件2,假设就不能成立
  • 那么就可以知道,答案的个数就是满足 S &lt; m , m ≤ S + m i n S&lt;m,m\leq S+min S<m,mS+min的集合个数
  • 那就暴力枚举集合就可以了。
  • 如果所有人的权值和都比 m m m小,那就只要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 ≤ 1 0 6 ) T(T\leq 10^6) T(T106) n ( n ≤ 1 0 7 ) n(n\leq 10^7) n(n107),求出 ∏ i = 1 n ∏ j = 1 n l c m ( i , j ) i ∗ l c m ( 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 = 1 n ∏ j = 1 n l c m ( i , j ) i ∗ l c m ( i , j ) j = ∏ i = 1 n ∏ j = 1 n i ∗ j g c d ( i , j ) i ∗ i ∗ j g c d ( 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 = 1 n ∏ j = 1 n i ∗ j g c d ( 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 , j i,j i,j是相互等价的,那么可以减少运算次数,变成:
    ( ∏ i = 1 n ∏ j = 1 i i ∗ j g c d ( 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 = 1 n ∏ j = 1 n i ∗ j ∏ i = 1 n ∏ j = 1 n g c d ( 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 = 1 n i n ∗ n ! = ( n ! ) 2 n \prod_{i=1}^{n}i^n*n!=(n!)^{2n} i=1ninn!=(n!)2n

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

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

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

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

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、付费专栏及课程。

余额充值