LGP7521 [PUTS 2021-B] 取模 学习笔记

LGP7521 [PUTS 2021-B] 取模 学习笔记

Luogu Link

前言

妙妙题。

题意简述

给定 \(n\) 个正整数 \(a_i\)。请选出三个数 \(a_i,a_j,a_k\),最大化 \((a_i+a_j)\bmod a_k\)

\(3\le n\le 2\times 10^5\)\(a_i\le 10^8\)

做法解析

你看到这个题要选取三元组的同时数据范围还开到了 \(2\times 10^5\)。“我打两劳嗝,真的假的?”

别急。先考虑考虑 \(O(n^2)\) 上下的东西试试。你可能会想着先把所有 \((a_i+a_j)\) 预处理出来,但是接下来你在枚举 \(a_k\) 的时候就犯难了,因为你不太容易在 \(O(n)\) 左右找出来对 \(a_k\) 最优秀的 \((a_i+a_j)\)。一个原因是这些 \((a_i+a_j)\) 的值域太大,你不知道对它们的取模要减掉几个 \(a_k\),也就不好讨论问题。

于是你转身向枚举 \(a_k\) 走去,问题一下豁然开朗:因为你最外层枚举 \(a_k\) 的话,就可以 \(O(n)\) 先把每个 \(a_i\) 变成模 \(a_k\) 意义下的,这样 \((a_i+a_j)\) 的值域就缩到了 \([0,2\times a_k)\)

那我们就经典地分类讨论。\(a_i+a_j<a_k\) 时,要最大化答案我们就用双指针扫一遍(这个不需要我教吧)。\(a_i+a_j>a_k\) 时候我们直接选最大的两个 \(a_i\) 就行了(显然 \(a_i+a_j\) 不可能在取模时被减去两个 \(a_k\),所以这是对的)

好了,现在我们已经可以 \(O(n^2\log n)\) 干掉此问题了。正解是什么?

不知道,除了剪枝之外我找不到任何本质优化。

第一个剪枝是我们每种 \(a_k\) 只考虑一遍。

无事发生,似乎。

第二个剪枝是我们从大到小枚举 \(a_k\),如果当前答案未能更新之前答案就直接break。

无事发生……吗?

好了,我们现在的时间复杂度来到了 \(O(n\log n\log V)\)

为什么?

不妨认为原序列元素没有重复。可以写出下柿:

\[a_1<\dots<a_{c-1}\le ans<a_c<\dots<a_n \]

由于 \(ans\) 是最优答案,所以有:

\[\forall i\in[c,n-2],(a_i+a_{i+1})\bmod a_{p+2}\le ans \]

又显然有 \(a_i+a_{i+1}<2a_{i+2}\),所以 \(\bmod\) 最多减一个 \(a_{i+2}\),可以把上面的柿子换成 \(a_i+a_{i+1}-a_{i+2}\le ans\)。进一步将柿子变形可得:

\[(a_{i+2}-ans)\ge(a_{i+1}-ans)+(a_i-ans) \]

然后拿 \(h_i=(a_i-ans)\) 代换掉可得:

\[h_{i+2}\le h_{i+1}+h_i$$。 显然 $h_i$ 的范围是 $O(V)$ 的,而增速是斐波拉契数列程度的,大于指数级别。所以它的复杂度就是 $O(n\log n\log V)$。 ### 代码实现 ```cpp #include <bits/stdc++.h> using namespace std; using namespace obasic; const int MaxN=2e5+5; int N,A[MaxN],B[MaxN],fans; int main(){ readi(N); for(int i=1;i<=N;i++)readi(A[i]); sort(A+1,A+N+1),reverse(A+1,A+N+1); for(int k=1,cans;k<=N;k++){ if(fans>A[k]-1)break; if(A[k]==A[k-1])continue; for(int i=1;i<=N;i++)B[i]=A[i]%A[k]; sort(B+1,B+N+1);B[0]=B[N+1]=-1,cans=(B[N]+B[N-1])%A[k]; for(int i=2,j=N;;i++){ while(B[i]==B[i-1])i++; while(B[j]==B[j+1]||B[i]+B[j]>=A[k])j--; if(i>=j)break;maxxer(cans,B[i]+B[j]); } maxxer(fans,cans); } writil(fans); return 0; } ```\]

posted @ 2025-07-25 11:02  矞龙OrinLoong  阅读(7)  评论(0)    收藏  举报