#双指针#洛谷 7521 [省选联考 2021 B 卷] 取模
分析
将 \(a\) 排序后从大到小枚举 \(a_k\),注意枚举的时候重复的只考虑一次,那么可以将其它数按照模 \(a_k\) 后排序,
答案只可能来自最大值与次大值之和取模或者之和最接近 \(a_k\)(后者利用双指针)
如果答案已经不小于 \(a_k\) 就退出整个循环,这样复杂度就是 \(O(n\log n\log a_i)\)
浅浅地证明一下,如果最终答案 \(ans\) 介于 \(a_{i-1}\) 和 \(a_i\) 之间。
那么对于任意 \(j\geq i\),\(ans\geq (a_j+a_{j+1})\bmod {a_{j+2}}\),因为一定能枚举到 \(a_{j+2}\) 计算答案。
可以发现取模是一定能模掉的,否则不可能比 \(a_i\) 小,所以 \(ans+a_{j+2}\geq a_j+a_{j+1}\)
移项得到 \(a_{j+2}-ans\geq (a_j-ans)+(a_{j+1}-ans)\),那么 \(\{a_i-ans\}\) 其实增长速度比斐波那契数列还要快,
在 \(10^8\) 内其实只有 \(\log\) 级别,复杂度大概就是这么证明的。
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int N=200011;
int n,m,a[N],b[N],c[N],ans;
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int main(){
n=iut();
for (int i=1;i<=n;++i) b[i]=a[i]=iut();
sort(b+1,b+1+n),m=unique(b+1,b+1+n)-b-1;
for (int i=m;i;--i){
if (b[i]<=ans) break;
for (int j=1;j<=n;++j) c[j]=a[j]%b[i];
sort(c+1,c+1+n);
if (c[n]+c[n-1]>=b[i]) ans=max(ans,c[n]+c[n-1]-b[i]);
else ans=max(ans,c[n]+c[n-1]);
for (int j=2,k=n;j<=n;++j){
while (k>1&&(j==k||c[j]+c[k]>=b[i])) --k;
if (k>1) ans=max(ans,c[j]+c[k]);
}
}
return !printf("%d",ans);
}