队内欢乐赛
2022/4/10 队内对抗赛
禁忌の魔法
解法1:
\(\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}lcm(a_i,a_j)=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\cfrac{a_ia_j}{\gcd(a_i,a_j)}\)
但是这样还是没有办法去解决问题。
设\(tot=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\cfrac{a_ia_j}{\gcd(a_i,a_j)}\),则答案\(ans=\cfrac{tot-\sum_{i=1}^{n}a_i}{2}\)
来化简\(tot\),困难之处在于\(\gcd(a_i,a_j)\)不可分,我们先假设这是一个常量\(k\)。
考虑到\(k\)难以分割,那么是不是可以枚举\(k\),看哪些符合条件?
设\(g_k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1,k|\gcd(a_i,a_j)}^{mx}\cfrac{a_ia_j}{k}\)
这里为什么枚举\(k|\gcd(a_i,a_j)\)?因为如果是\(k=\gcd(a_i,a_j)\)那么和枚举一样是不可分的。
我们看\(k|\gcd(a_i,a_j)\)可以推导出:\(k|a_i,k|a_j\)。进而可以拆分得到:
\(g_k=\left(\sum\limits_{i=1}^{n}\sum\limits_{k=1,k|a_i}^{mx}\cfrac{a_i}{k}\right)\left(\sum\limits_{j=1}^{n}\sum\limits_{k=1,k|a_j}^{mx}\cfrac{a_j}{k}\right)k\),发现有类似的部分。
可以设\(f_k=\left(\sum\limits_{i=1}^{n}\sum\limits_{k=1,k|a_i}^{mx}\cfrac{a_i}{k}\right)\),则\(g_k=f_k^2\times k\)
但是\(g_k\)也不是我们要的答案,因为存在\(qk=\gcd(a_i,a_j),q\ge2\)的情况。
设\(h_k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[k=\gcd(a_i,a_j)]\cfrac{a_ia_j}{\gcd(a_i,a_j)}\),显然满足\(tot=\sum\limits_{k=1}^{mx}h_k\)
对于\(g_k\)相当于多加了一部分东西,要把这部分减掉,去化成\(h_k\)。
其实可以写成\(g_k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[qk=\gcd(a_i,a_j),q\ge1]\cfrac{a_ia_j}{k}\)
\(\begin{aligned}h_k&=g_k-\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[2k=\gcd(a_i,a_j)]\cfrac{a_ia_j}{\gcd(a_i,a_j)}-\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[3k=\gcd(a_i,a_j)]\cfrac{a_ia_j}{\gcd(a_i,a_j)}-\cdots\\&=g_k-\sum\limits_{q=2,qk\le mx}h_{qk}\times q\end{aligned}\)
#include<bits/stdc++.h>
#define LL long long
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=1e5+5;
int n,a[N],mx;
LL ans,f[N],g[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mx=max(a[i],mx);
for(int j=1;j*j<=a[i];j++)
if(a[i]%j==0){
g[j]+=a[i]/j;
if(j*j!=a[i])g[a[i]/j]+=j;
}
}
for(int i=1;i<=mx;i++)f[i]=g[i]*g[i]*i;
for(int i=mx;i;i--){
for(int j=2;i*j<=mx;j++)
f[i]=f[i]-f[i*j]*j;
ans+=f[i];
}
for (int i=1;i<=n;i++)
ans-=a[i];
ans>>=1;
cout<<ans<<endl;
return 0;
}
解法2:
我们康到了\(lcm\)
我们想到了\(gcd\)
于是我们想到了莫比乌斯函数
于是我们做完了
化简一下原题
\(\sum_{1<=i<=n-1}\sum_{i+1<=j<=n}\frac{a_i*a_j}{gcd(a_i,a_j)}\)
枚举一手\(gcd\)
\(\sum_{d}\frac{1}{d}\sum_{1<=i<=n-1}\sum_{i+1<=j<=n}a_i*a_j[d|a_i,d|a_j][gcd(\frac{a_i}{d},\frac{a_j}{d})==1]\)
根据莫比乌斯函数的性质展开一下
\(\sum_{d}\frac{1}{d}\sum_{1<=i<=n-1}\sum_{i+1<=j<=n}[d|a_i,d|a_j]\sum_{d'|\frac{a_i}{d},d'|\frac{a_j}{d}}μ(d')\)
把枚举因数扔到前面去qwq
\(\sum_{d}\frac{1}{d}\sum_{d'}μ(d')\sum_{1<=i<=n-1,d*d'|a_i}\sum_{1<=j<=n-1,d*d'|a_j}a_ia_j\)
后面这个显然是关于\(d*d'\)的一个函数,预处理即可
然后我们就做完了
预处理的话
\(\sum a_ia_j=\frac{((\sum a_i)^2-\sum {a_i^2})}{2}\)
好了 真的做完了x
#include <bits/stdc++.h>
using namespace std;
long long f[1000010],mu[1000010],Prime[1000010],IsPrime[1000010],hs[1000010],cnt,Con[1000010],N,a[1000010];
void Init(){
mu[1]=1;
for (int i=2;i<=1000000;i++){
if (!IsPrime[i]) Prime[++cnt]=i,mu[i]=-1;
for (int j=1;i*Prime[j]<=1000000&&j<=cnt;j++){
IsPrime[i*Prime[j]]=true;
if (i%Prime[j]==0){
mu[i*Prime[j]]=0;
break;
}
mu[i*Prime[j]]=-mu[i];
}
}
}
int main(){
scanf("%lld",&N);
for (int i=1;i<=N;i++)
scanf("%lld",&a[i]),Con[a[i]]++;
Init();
for (int i=1;i<=1000000;i++)
for (int j=i;j<=1000000;j+=i)
hs[i]+=1ll*Con[j]*j,f[i]=f[i]+1ll*Con[j]*j*j;
for (int i=1;i<=1000000;i++)
f[i]=(1ll*hs[i]*hs[i]-f[i])>>1ll;
long long ans=0;
for (int k=1;k<=1000000;k++)
for (int T=k;T<=1000000;T+=k)
ans+=1ll*mu[T/k]*f[T]/k;
cout<<ans;
return 0;
}
姆q的图书馆
一段连续的gcd,随着区间长度的增加,它的gcd一定是递减的
而区间长度是递增的
于是这两个函数如果有交点一定只有一个交点
于是就对于每一个\(l\)位置,二分找一下是否有令它不合法的右端点
至于连续gcd的话,线段树随便写写就行了
如果有的话就存起来
然后考虑问题的转化
如果我修改区间中的一个数字为一个没有出现过的素数,那么这个区间的gcd一定是\(1\)
这样的话问题就变成了 ,
对于一堆线段,每条线段上都要取其中一个点,问你最少取几个点
这就是一个经典的贪心问题
然后题目要的是前缀
我们离线一下,根据\(r\)排个序
然后每次做的时候把答案标记在\(r\)上,只有\(r\)右侧是这个答案
然后扫一遍区间输出即可。
#include<bits/stdc++.h>
using namespace std;
struct Node{
int l,r;
}a[200005];
int Tree[800005],N,cnt,ans[200005];
int aa[200005];
bool temp(Node a,Node b){
return (a.r<b.r);
}
void Build(int nw,int l,int r){
if (l==r){
Tree[nw]=aa[l];
return;
}
int mid=(l+r)>>1;
Build(nw<<1,l,mid);
Build(nw<<1|1,mid+1,r);
Tree[nw]=__gcd(Tree[nw<<1],Tree[nw<<1|1]);
return;
}
int query(int Now,int L,int R,int l,int r){
if (L<=l&&r<=R) return Tree[Now];
int mid=(l+r)>>1;
if (L<=mid&&mid<R) return __gcd(query(Now<<1,L,R,l,mid),query(Now<<1|1,L,R,mid+1,r));
else if (L<=mid) return query(Now<<1,L,R,l,mid);
else if (mid<R) return query(Now<<1|1,L,R,mid+1,r);
}
void Pre(){
for (int i=1;i<=N;i++){
int l=i,r=N+1;
while (l<=r){
int mid=(l+r)>>1;
if (query(1,i,mid,1,N)>mid-i+1) l=mid+1;
else if (query(1,i,mid,1,N)==mid-i+1){
a[++cnt].r=mid;
a[cnt].l=i;
break;
}
else r=mid-1;
}
}
}
int main(){
scanf("%d",&N);
for (int i=1;i<=N;i++)
scanf("%d",&aa[i]);
Build(1,1,N);
Pre();
sort(a+1,a+cnt+1,temp);
int x=a[1].r,Ans=1;
ans[a[1].r]=Ans;
for (int i=1;i<=cnt;i++)
if (a[i].l<=x) continue;
else{
x=a[i].r;
Ans++;
ans[a[i].r]=Ans;
}
int nw=0;
for (int i=1;i<=N;i++){
if (ans[i]!=0&&ans[i]!=nw) nw=ans[i];
printf("%d ",nw);
}
return 0;
}