P6786 「SWTR-6」GCDs & LCMs

题意:

小 A 有一个长度为 \(n\) 的序列 \(a_1,a_2,\cdots,a_n\)

他想从这些数中选出一些数 \(b_1,b_2,\cdots,b_k\) 满足:对于所有 \(i\ (1\leq i\leq k)\)\(b_i\) 要么是序列 \(b\) 中的最大值,要么存在一个位置 \(j\) 使得 \(b_j>b_i\)\(b_i+b_j+\gcd(b_i,b_j)=\mathrm{lcm}(b_i,b_j)\)

小 A 想让选出的数之和尽量大。请求出这个最大值。

\(1\le n\le 3e5,1\le a_i\le 1e9\)

解题思路:

太有意思啦😅

推出结论以后其实就不难做了。

首先关于这个式子:

\[b_i+b_j+\gcd(b_i,b_j)=\mathrm{lcm}(b_i,b_j) \]

\(k=gcd(b_i,b_j),b_i=xk,b_j=yk\),代入上式得:

\[k(x+y+1)=kxy \]

\[x+y+1=xy \]

然后发现满足这个式子的 \((x,y)\) 只能是 \((2,3)\) 或者 \((3,2)\)

考虑怎么选才能使所选的数字之和最大。

显然要选的数除去 \(2,3\) 的其中某一个因子一定存在两两相同的情况,且另一个数除去的因子与该数不同,最大的数是 \(3\) 的倍数(除非只选一个数)。

所以我们进行去重,对序列中 \(2\) 的倍数与 \(3\) 的倍数进行分类。排序之后从大到小遍历整个序列中 \(3\) 的倍数,并在 \(2\) 的倍数之中二分查找到除去 \(3\) 后因子相同的部分,在代表两个数的编号之间连一条有向边。

统计答案时就直接找入度为 \(0\) 的编号 \(\operatorname{dfs}\) 即可。算上二分查找复杂度应该是 \(O(n\log n)\) 的。

快得一批。甚至拿到了最优解

代码:

#include <cstdio>
#include <algorithm>
#define Reg register
#define ll long long
using namespace std;
const int maxn=310000;
int n,cnt,tot,ftot;
int a[maxn],head[maxn],ru[maxn];
int t[maxn],id[maxn];
ll ans;
struct node{
    int v,cnt;
}b[maxn];
struct ED{
    int to,nxt;
}e[maxn<<1];
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<1)+(s<<3)+(ch^48);
        ch=getchar();
    }
    return s*w;
}
inline void add(int u,int v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
inline void dfs(int u,ll S){
    S+=1ll*b[u].cnt*b[u].v;
    ans=max(S,ans);
    for(Reg int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        dfs(v,S);
    }
}
int main(){
    n=read();
    for(Reg int i=1;i<=n;++i) a[i]=read();
    sort(a+1,a+1+n);
    for(Reg int i=1;i<=n;++i){
        if(b[tot].v==a[i]) ++b[tot].cnt;
        else b[++tot].v=a[i],b[tot].cnt=1;
    }
    for(Reg int i=1;i<=tot;++i){
        if(b[i].v%2!=0) continue;
        t[++ftot]=b[i].v/2;
        id[ftot]=i;
    }
    for(Reg int i=tot;i>=1;--i){
        if(b[i].v%3!=0) continue;
        int v=b[i].v/3;
        int p=lower_bound(id+1,id+1+ftot,i)-id;--p;
        int k=lower_bound(t+1,t+1+ftot,v)-t;
        if(t[k]==v) add(i,id[k]),++ru[id[k]];
    }
    for(Reg int i=tot;i>=1;--i){
        if(!ru[i]) dfs(i,0);
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2022-11-08 06:44  Broken_Eclipse  阅读(37)  评论(0编辑  收藏  举报

Loading