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;
}