「NOIP 模拟赛 20230706」T2 - 偷 WiFi 题解
题目大意
给定长为 \(n\) 的序列 \(a\),现在要标记其中的若干个数,记每个数左右两边(不包括本身)第一个被标记的数之和为这个数的满意度,求所有数满意度之和的最大值。\(n\le 2\times 10^6\)。
题解
假设我们选了 \(a_i\) 与 \(a_j\) 两个数且没有选中间的其它数(假设 \(i<j\)),那么 \(i\sim j\) 之间数对答案的贡献就是 \((j-i)\times|a_j-a_i|\),可以看做是 \((i,0),(i,a_i),(j,0),(j,a_j)\) 四个点所围成的梯形面积的两倍。于是,想要所有的面积和最大,只需要求点集 \(\{(i,a_i)|a_i 被标记\}\) 与 \(x\) 轴围成的凸包面积,可以用单调栈 \(\mathcal{O}(n)\) 求得。
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 2000005
#define ll long long
using namespace std;
int n,a[maxn],s[maxn],top=0; ll ans=0LL; struct node{ll x,y;};
ll cross(node aa,node bb,node cc){return (aa.x-bb.x)*(cc.y-bb.y)-(aa.y-bb.y)*(cc.x-bb.x);}
// (bb->aa) cross (bb->cc)
int main(){
// freopen("wifi.in","r",stdin); freopen("wifi.out","w",stdout);
scanf("%d",&n); for(int i=1;i<=n;i++){
scanf("%lld",&a[i]); if(i==1||i==2){s[++top]=i; continue;}
while(top>=2&&cross((node){s[top-1],a[s[top-1]]},(node){s[top],a[s[top]]},(node){i,a[i]})<=0)
top--; s[++top]=i;
} for(int i=2;i<=top;i++) ans+=1LL*(s[i]-s[i-1])*(a[s[i-1]]+a[s[i]]); printf("%lld",ans);
return 0;
}