「KDOI-06-J」贡献系统(前缀和+贪心+找规律+思维)

99 篇文章 0 订阅

「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 1i<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.incontrib/contrib2.ans

【样例 #3】

见选手目录下的 contrib/contrib3.incontrib/contrib3.ans


【数据范围】

对于所有数据保证: 1 ≤ T ≤ 5 1\le T\le 5 1T5 1 ≤ n ≤ 2 × 1 0 5 1\le n\le 2\times 10^5 1n2×105 0 ≤ r i ≤ 1 0 9 0\le r_i\le 10^9 0ri109 − 1 0 9 ≤ c i ≤ 1 0 9 -10^9\le c_i\le 10^9 109ci109,且对于任意 1 ≤ i < n 1\le i<n 1i<n r i > r i + 1 r_i>r_{i+1} ri>ri+1

测试点编号$n\le $特殊限制
1 ∼ 3 1\sim3 13 8 8 8
4 4 4 100 100 100ABC
5 5 5 100 100 100C
6 ∼ 7 6\sim 7 67 100 100 100
8 ∼ 9 8\sim 9 89 5 × 1 0 3 5\times 10^3 5×103AB
10 ∼ 11 10\sim 11 1011 5 × 1 0 3 5\times 10^3 5×103C
12 ∼ 14 12\sim 14 1214 5 × 1 0 3 5\times 10^3 5×103
15 15 15 2 × 1 0 5 2\times10^5 2×105AB
16 ∼ 18 16\sim 18 1618 2 × 1 0 5 2\times10^5 2×105B
19 ∼ 21 19\sim 21 1921 2 × 1 0 5 2\times10^5 2×105C
22 ∼ 25 22\sim 25 2225 2 × 1 0 5 2\times 10^5 2×105
  • 特殊性质 A:对于任意 1 ≤ i < n 1\le i<n 1i<n,保证 c i = c i + 1 c_i=c_{i+1} ci=ci+1
  • 特殊性质 B:对于任意 1 ≤ i < n 1\le i<n 1i<n,保证 c i ≤ c i + 1 c_i\le c_{i+1} cici+1
  • 特殊性质 C:对于任意 1 ≤ i ≤ n 1\le i\le n 1in,保证 c i ≥ 0 c_i\ge 0 ci0

思路

  • 我们首先先考虑我们如果暴力的话,我们该怎么做这道题:

  • 我们发现, 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 cjci+sj1si,我们整理一下: ( c j + s j − 1 ) − ( c i + s i ) (c_j+s_{j-1})-(c_i+s_i) (cj+sj1)(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;
}
  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值