队内欢乐赛

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;
}
posted @ 2022-04-10 17:36  <NULL>  阅读(54)  评论(0编辑  收藏  举报