「KDOI-06-J」贡献系统
题目描述
洛谷贡献系统上线了!
现在有 n n n 个人即将参加一场洛谷月赛,每个人的等级分互不相同。第 i i i 个人的等级分是 r i r_i ri,贡献值是 c i c_i ci。
假设第 i i i 个人的等级分在这 n n n 个人中的排名是 a i a_i ai(排名按等级分从大到小排序),且在月赛中的排名是 b i b_i bi,没有两个人的排名相同。也就是说, a a a 和 b b b 都是 1 1 1 到 n n n 的排列。比赛结束后,每个人都会执行以下操作:
- 若 a i = b i a_i=b_i ai=bi,则第 i i i 个人的等级分不会发生任何变化,因此第 i i i 个人不会进行任何操作;
- 若 a i > b i a_i>b_i ai>bi,则第 i i i 个人的等级分会上升,因此第 i i i 个人会给出题人点赞,导致出题人的贡献值上升 c i c_i ci( c i c_i ci 可能是负数,此时会导致出题人的贡献值下降);
- 若 a i < b i a_i<b_i ai<bi,则第 i i i 个人的等级分会下降,因此第 i i i 个人会给出题人点踩,导致出题人的贡献值下降 c i c_i ci( c i c_i ci 可能是负数,此时会导致出题人的贡献值上升)。
作为这场月赛唯一的出题人,初始时你的贡献值为 0 0 0。你想知道,对于所有可能的排列 b b b(显然,排列 a a a 在比赛前已经被确定),在比赛结束后你的贡献值最大是多少。
输入格式
从标准输入读入数据。
本题有多组测试数据。
输入的第一行包含一个正整数 T T T,表示数据组数。
对于每组测试数据,第一行一个正整数 n n n,表示参赛选手人数。
第二行包含 n n n 个非负整数 r 1 , r 2 , … , r n r_1,r_2,\ldots,r_n r1,r2,…,rn,表示参赛选手的等级分。保证对于任意 1 ≤ i < n 1\le i< n 1≤i<n, r i > r i + 1 r_i>r_{i+1} ri>ri+1。
第三行包含 n n n 个整数 c 1 , c 2 , … , c n c_1,c_2,\ldots,c_n c1,c2,…,cn,表示参赛选手的贡献值。
输出格式
输出到标准输出。
对于每组测试数据,输出一行一个整数,表示最大的贡献值。
样例 #1
样例输入 #1
3
5
3816 3738 3726 3621 3582
111 109 -50 -22 208
8
8 7 6 5 4 3 2 1
128 1 0 0 0 0 1 0
10
10 9 8 7 6 5 4 3 2 1
1 1 4 5 1 4 1 9 1 9
样例输出 #1
280
1
34
提示
【样例解释 #1】
对于第一组测试数据,设五个人按输入顺序分别为 A,B,C,D,E,则当月赛中的排名顺序为 ABECD 时贡献值最大,为 0 + 0 − ( − 50 ) − ( − 22 ) + 208 = 280 0+0-(-50)-(-22)+208=280 0+0−(−50)−(−22)+208=280。可以证明,这是唯一能使贡献值达到最大的排名顺序。
对于第二组测试数据,设八个人按输入顺序分别为 A,B,C,D,E,F,G,H,则当月赛中的排名顺序为 ABCDEGFH 时可以使贡献值达到最大值 1 1 1,注意此时有多种可能的使贡献值最大的排名顺序。
【样例 #2】
见选手目录下的 contrib/contrib2.in
与 contrib/contrib2.ans
。
【样例 #3】
见选手目录下的 contrib/contrib3.in
与 contrib/contrib3.ans
。
【数据范围】
对于所有数据保证: 1 ≤ T ≤ 5 1\le T\le 5 1≤T≤5, 1 ≤ n ≤ 2 × 1 0 5 1\le n\le 2\times 10^5 1≤n≤2×105, 0 ≤ r i ≤ 1 0 9 0\le r_i\le 10^9 0≤ri≤109, − 1 0 9 ≤ c i ≤ 1 0 9 -10^9\le c_i\le 10^9 −109≤ci≤109,且对于任意 1 ≤ i < n 1\le i<n 1≤i<n, r i > r i + 1 r_i>r_{i+1} ri>ri+1。
测试点编号 | $n\le $ | 特殊限制 |
---|---|---|
1 ∼ 3 1\sim3 1∼3 | 8 8 8 | 无 |
4 4 4 | 100 100 100 | ABC |
5 5 5 | 100 100 100 | C |
6 ∼ 7 6\sim 7 6∼7 | 100 100 100 | 无 |
8 ∼ 9 8\sim 9 8∼9 | 5 × 1 0 3 5\times 10^3 5×103 | AB |
10 ∼ 11 10\sim 11 10∼11 | 5 × 1 0 3 5\times 10^3 5×103 | C |
12 ∼ 14 12\sim 14 12∼14 | 5 × 1 0 3 5\times 10^3 5×103 | 无 |
15 15 15 | 2 × 1 0 5 2\times10^5 2×105 | AB |
16 ∼ 18 16\sim 18 16∼18 | 2 × 1 0 5 2\times10^5 2×105 | B |
19 ∼ 21 19\sim 21 19∼21 | 2 × 1 0 5 2\times10^5 2×105 | C |
22 ∼ 25 22\sim 25 22∼25 | 2 × 1 0 5 2\times 10^5 2×105 | 无 |
- 特殊性质 A:对于任意 1 ≤ i < n 1\le i<n 1≤i<n,保证 c i = c i + 1 c_i=c_{i+1} ci=ci+1;
- 特殊性质 B:对于任意 1 ≤ i < n 1\le i<n 1≤i<n,保证 c i ≤ c i + 1 c_i\le c_{i+1} ci≤ci+1;
- 特殊性质 C:对于任意 1 ≤ i ≤ n 1\le i\le n 1≤i≤n,保证 c i ≥ 0 c_i\ge 0 ci≥0。
思路
-
我们首先先考虑我们如果暴力的话,我们该怎么做这道题:
-
我们发现, a i a_i ai 是给定的排名顺序,之后的顺序是由我们来定的。因此我们发现:对于 a 1 a_1 a1 的元素,它的排名是不会升的,对于 a n a_n an (最后一个元素排名),它的排名是不会降的,因此我们可以不完全归纳出:所有排名发生变动的人中,原来排名最前的人排名必降,原来排名最后的人排名必升。
但这肯定很牵强,因此如何判断可行性?显然,第一个人排名不可能增,最后一个人排名不可能降。
举个例子, n = 7 n=7 n=7, 7 7 7 人排名情况为「降升平升降降升」,如何安排?
第 2 , 4 2,4 2,4 个人分别变为 1 , 2 1,2 1,2 名,第 1 1 1 个人变为第 4 4 4 名;第 5 , 6 5,6 5,6 个人变为第 6 , 7 6,7 6,7 名,第 7 7 7 个人变为第 5 5 5 名。 -
那么我们只需要:枚举第一个排名降的人 i i i 和第一个排名升的人 j j j。只有 i i i 到 j j j 名可能产生贡献。第 i i i 名必贡献 − c i -c_i −ci,第 j j j 名必贡献 c j c_j cj。中间的人随意,第 x x x 人贡献 ∣ c x ∣ |c_x| ∣cx∣ 即可使总贡献最大。所以这里我们可以用前缀和来维护,因此我们可以写出:
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
ans=max(ans,c[j]-c[i]+s[j-1]-s[i]);//s[j]-s[i]=c[i+1]+c[i+2]+...+c[j-1]+c[j]
}
}
其中 c 为贡献值, s 为贡献值的前缀和。
但这会超时。我们观察: c j − c i + s j − 1 − s i c_j-c_i+s_{j-1}-s_i cj−ci+sj−1−si,我们整理一下: ( c j + s j − 1 ) − ( c i + s i ) (c_j+s_{j-1})-(c_i+s_i) (cj+sj−1)−(ci+si),我们可以维护 k n = ∑ i = 1 n ( c i + s i ) k_n=\sum_{i=1}^{n}(c_i+s_i) kn=∑i=1n(ci+si) 的前缀最小值算,这样就不用枚举 i i i 了。
AC 代码
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 2e5+10;
int a[N],s[N],k[N],c[N];//a为赛前排名,b为赛后排名,c为贡献值
int f[N];//所有以i为结尾的贡献最大值的集合
int T,n;
signed main(){
cin>>T;
while(T--){
cin>>n;
k[0ll]=1e18;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
cin>>c[i];
if(c[i]<0ll){
s[i]=s[i-1]-c[i];
}else{
s[i]=s[i-1]+c[i];
}
// cout<<"1="<<s[i]<<endl;
k[i]=min(k[i-1],c[i]+s[i]);
}
int ans=0;
// for(int i=1;i<n;i++){
// for(int j=i+1;j<=n;j++){
// ans=max(ans,c[j]-c[i]+s[j-1]-s[i]);//s[j]-s[i]=c[i+1]+c[i+2]+...+c[j-1]+c[j]
// }
// }
for(int j=2;j<=n;j++){
ans=max(ans,c[j]+s[j-1]-k[j-1]);
}
//优化:c[j]+s[j-1]-(c[i]+s[i])
cout<<ans<<endl;
}
return 0;
}