题解
我觉得我剩下的日子已经不多了,这一份题解很有写的意义。
第一题——序列(sequence)
【题目描述】
- 小Z 有一个序列,定义f(x)为x 在十进制下的位数,特别地,求∑1≤i<j≤nf(ai+aj)\sum_{1\leq i <j\leq n}f(a_i+a_j)1≤i<j≤n∑f(ai+aj)
- 其中n≤106,ai≤108n\leq10^6,a_i\leq10^8n≤106,ai≤108
- 显然这个题目拿到手有点难考虑。
- 我们考虑对于f(ai+aj)f(a_i+a_j)f(ai+aj),只有当两个数相加进位的时候才会对答案做出+1的贡献,否则就是原来的位数。
- 那么将原有数列进行排序,对答案无影响。
- 对于单个的aia_iai,只有当iii之前的aja_jaj满足10k−ai≤aj(10k<ai≤10k−1)10^k-a_i\leq a_j(10^k<a_i\leq 10^{k-1})10k−ai≤aj(10k<ai≤10k−1)才能对答案做出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个人的权值值,给每一个人附上若干把钥匙使得满足,每把钥匙对应一把锁,每个钥匙可以发给多个人,每个人可以拿多把钥匙。
求满足下面条件的最少的锁的数目
- 任意权值和SSS满足S<mS<mS<m的人的集合都无法开启所有的锁。
- 任何权值和SSS满足S≤S\leqS≤的人的集合能够开启所有的锁。
- 这个题目真的不是很好打,最开始乱搞只拿了20分。看了题解才知道是集合数学建模,但大家都不会也没什么亏的。
- 考虑两个满足S<mS<mS<m的不同的集合,如果两个集合的权值恰好比mmm小,再加上一个不包含的最小值比mmm要大的情况下,不能同时缺同一把钥匙,简单证明就是
满足S1<m,m≤S1+min1S_1<m,m\leq S_1+min_1S1<m,m≤S1+min1S2<m,m≤S2+min2S_2<m,m\leq S_2+min_2S2<m,m≤S2+min2 - 那么m≤S1+S2m\leq S_1+S_2m≤S1+S2
- 但是两个集合的人还是缺一把钥匙,不满足条件2,假设就不能成立
- 那么就可以知道,答案的个数就是满足S<m,m≤S+minS<m,m\leq S+minS<m,m≤S+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(T≤106)个n(n≤107)n(n\leq 10^7)n(n≤107),求出∏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=1∏nj=1∏nilcm(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=1∏nj=1∏nilcm(i,j)∗jlcm(i,j)=i=1∏nj=1∏nigcd(i,j)i∗j∗jgcd(i,j)i∗j
=∏i=1n∏j=1ni∗jgcd(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,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=1∏nj=1∏igcd(i,j)i∗j)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=1n∏j=1ngcd(i,j)2∏i=1n∏j=1ni∗j -
我们发现对于上面的分子来说,可以写成
∏i=1nin∗n!=(n!)2n\prod_{i=1}^{n}i^n*n!=(n!)^{2n}i=1∏nin∗n!=(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=1∏nj=1∏ngcd(i,j)2
-
题解上面就一句“我们单独考虑每个素数对答案的贡献,对于一个素数x,它在kx处会对答案有(2k-1)的贡献,求一遍前缀积即为答案”,但这对于读者来说过于草率,非常难以理解。在此进行展开
-
考虑素数筛出每一个质数,对于每一个nnn的质因子pip_ipi,nnn之前每pip_ipi个数当中就会有一个数与nnn的gcdgcdgcd是pip_ipi,那么可以通过倍数法求出这些数的乘积,而对于与nnn的gcdgcdgcd是pi2,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);
}
}