题目描述
唐克新入手了一款PS333平台的游戏,在游戏中从起点到终点共有n个得分点,编号为1~n,需要使用灵能手套才能获得得分点分数,灵能手套经过充能后只可以使用3次。
游戏最终成绩的计算规则如下:假设通过灵能手套获取了三个不同的编号分别为i,j,k的得分点的分数:ai,aj,ak,最终成绩为3个得分点分数总和先减去所选取三点中编号最小值再减去所选取三点中编号最大值,即ai + aj + ak - min(i,j,k) - max(i,j,k)。请你编写程序帮助唐克获得最好成绩。
注:min(i,j,k)表示i,j,k中的最小值,max(i,j,k)表示i,j,k中的最大值。
输入
第一行包含一个整数t,表示有t组测试数据
每组测试数据包含两行:
第一行包含一个整数n,表示游戏中得分点的数量
第二行包含n个整数,表示从起点到终点n个得分点的分数,第i个得分点的分数为ai。
输出
每组测试数据一行,输出唐克所能够获得的最好成绩。
样例
样例输入
【样例1】
1
6
1 2 2 3 3 3
【样例2】
2
6
19 13 4 27 7 11
8
8 7 6 5 4 3 2 1
【样例3】
1
10
1 2 3 9 9 9 10 10 10 10
样例输出
【样例1】
1
【样例2】
54
17
【样例3】
17
提示
对于20%的数据:1<t≤10,3≤n≤100;
对于60%的数据:1≤t≤20,3≤n≤1000
对于100%的数据:1≤t≤100,3≤n≤1e5,1≤ai≤1e8。输入文件的大小不超过10MB.
分析
求 ai + aj + ak - min(i,j,k) - max(i,j,k)
的最大值
假定 i < j < k
,转化为ai + aj + ak - i - k
,即(ai - i) + aj + (ak - k)
故可以枚举j,
预处理出 b[i] = a[i] - i
,通过ST表查 b[1]~b[j - 1]
和 b[j + 1]~b[n]
的最大值,
ST表
用途:用于解决可重复贡献问题,适用于静态区间,不支持修改操作
条件:对于运算opt,满足 x opt x = x 且 opt 满足结合律
时间复杂度:预处理 O(nlogn) 和 查询 O(1)
经典应用:RMQ,区间GCD
三者的和即为局部最优解,
不断取max即为全局最优解
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5 + 10,M = 18 + 10;
int n;
int a[N];
int lg[N],f[N][M];
void init(){
lg[1] = 0,lg[2] = 1;
for(int i = 3;i < N;i++) lg[i] = lg[i >> 1] + 1;
}
void st_pre(){
for(int i = 1;i <= n;i++)
for(int j = 1;j < M;j++)
f[i][j] = INT_MIN;
for(int i = 1;i <= n;i++) f[i][0] = a[i] - i;
for(int j = 1;j < M;j++)
for(int i = 1;i + (1 << j) - 1 <= n;i++)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
int query(int l,int r){
int k = lg[r - l + 1];
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
void solve(){
cin >> n;
for(int i = 1;i <= n;i++) cin >> a[i];
st_pre();
int res = INT_MIN;
for(int i = 2;i <= n - 1;i++) res = max(res,a[i] + query(1,i - 1) + query(i + 1,n));
cout << res << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
init();
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}