题解
我觉得我剩下的日子已经不多了,这一份题解很有写的意义。
第一题——序列(sequence)
【题目描述】
- 小Z 有一个序列,定义f(x)为x 在十进制下的位数,特别地,求 ∑ 1 ≤ i < j ≤ n f ( a i + a j ) \sum_{1\leq i <j\leq n}f(a_i+a_j) 1≤i<j≤n∑f(ai+aj)
- 其中 n ≤ 1 0 6 , a i ≤ 1 0 8 n\leq10^6,a_i\leq10^8 n≤106,ai≤108
- 显然这个题目拿到手有点难考虑。
- 我们考虑对于 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 < a i ≤ 1 0 k − 1 ) 10^k-a_i\leq a_j(10^k<a_i\leq 10^{k-1}) 10k−ai≤aj(10k<ai≤10k−1)才能对答案做出 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个人的权值值,给每一个人附上若干把钥匙使得满足,每把钥匙对应一把锁,每个钥匙可以发给多个人,每个人可以拿多把钥匙。
求满足下面条件的最少的锁的数目
- 任意权值和 S S S满足 S < m S<m S<m的人的集合都无法开启所有的锁。
- 任何权值和 S S S满足 S ≤ S\leq S≤的人的集合能够开启所有的锁。
- 这个题目真的不是很好打,最开始乱搞只拿了20分。看了题解才知道是集合数学建模,但大家都不会也没什么亏的。
- 考虑两个满足
S
<
m
S<m
S<m的不同的集合,如果两个集合的权值恰好比
m
m
m小,再加上一个不包含的最小值比
m
m
m要大的情况下,不能同时缺同一把钥匙,简单证明就是
满足 S 1 < m , m ≤ S 1 + m i n 1 S_1<m,m\leq S_1+min_1 S1<m,m≤S1+min1 S 2 < m , m ≤ S 2 + m i n 2 S_2<m,m\leq S_2+min_2 S2<m,m≤S2+min2 - 那么 m ≤ S 1 + S 2 m\leq S_1+S_2 m≤S1+S2
- 但是两个集合的人还是缺一把钥匙,不满足条件2,假设就不能成立
- 那么就可以知道,答案的个数就是满足 S < m , m ≤ S + m i n S<m,m\leq S+min S<m,m≤S+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(T≤106)个 n ( n ≤ 1 0 7 ) n(n\leq 10^7) n(n≤107),求出 ∏ 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=1∏nj=1∏nilcm(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=1∏nj=1∏nilcm(i,j)∗jlcm(i,j)=i=1∏nj=1∏nigcd(i,j)i∗j∗jgcd(i,j)i∗j
= ∏ 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=1∏nj=1∏ngcd(i,j)2i∗j -
考虑对于每一个 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=1∏nj=1∏igcd(i,j)i∗j)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=1n∏j=1ngcd(i,j)2∏i=1n∏j=1ni∗j -
我们发现对于上面的分子来说,可以写成
∏ i = 1 n i n ∗ n ! = ( n ! ) 2 n \prod_{i=1}^{n}i^n*n!=(n!)^{2n} i=1∏nin∗n!=(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=1∏nj=1∏ngcd(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);
}
}