【补题报告】牛客暑假多校一(2021)GK

【补题报告】牛客暑假多校一(2021)

G

题意

  • 给你长度为 N N N 的序列 A 、 B A、B AB
    你必须操作 K K K 次,每次操作需要选择两个数字 A i , A j A_i,A_j Ai,Aj,然后交换他们
    让你最大 ∑ ∣ A i − B i ∣ \sum|A_i-B_i| AiBi
  • 1 ≤ N ≤ 5 e 5 1\le N\le 5e5 1N5e5

思路

  • 首先, N > 2 N>2 N>2 时必须操作 K K K 次和至多操作 K K K 次在答案上是等价的
    因为有些交换不会影响答案的点对,交换他们即可
  • 交换一次,我们增加了什么?
    在这里插入图片描述
  • 也就是说,我们希望每次最大化 min ⁡ ( A i , B i ) − max ⁡ ( A j , B j ) \min(A_i,B_i)-\max(A_j,B_j) min(Ai,Bi)max(Aj,Bj)
    我们设 S [ i ] = − max ⁡ ( A i , B i ) S[i]=-\max(A_i,B_i) S[i]=max(Ai,Bi) R [ i ] = min ⁡ ( A i , B i ) R[i]=\min(A_i,B_i) R[i]=min(Ai,Bi)
    然后我们 s o r t sort sort 升序他们,每次拿最大的 S [ i ] S[i] S[i] R [ i ] R[i] R[i],然后答案加上 2 ( S [ i ] + R [ i ] ) 2(S[i]+R[i]) 2(S[i]+R[i]),就是目前交换一次的最大收益
  • 当然前提是:目前还有交换的次数,以及交换这一次对答案是正贡献的
    还有 N = 2 N=2 N=2 的情况,交换操作是唯一的,特判一下即可

代码

  • 时间复杂度: O ( N log ⁡ N ) O(N\log N) O(NlogN)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 5e5+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll aa[MAX],bb[MAX];
ll AA[MAX],BB[MAX];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;++i)scanf("%lld",&aa[i]);
    for(int i = 1;i <= n;++i)scanf("%lld",&bb[i]);

    ll ans = 0;

    if(n == 2){
        if(k&1)swap(aa[1],aa[2]);
        for(int i = 1;i <= n;++i){
            ans += abs(aa[i] - bb[i]);
        }
    }else{
        for(int i = 1;i <= n;++i){
            ans += abs(aa[i] - bb[i]);
            AA[i] = -max(aa[i],bb[i]);
            BB[i] =  min(aa[i],bb[i]);
        }
        sort(AA+1,AA+1+n);
        sort(BB+1,BB+1+n);
        for(int i = n;i > n - k && i >= 1 && AA[i] + BB[i] > 0;--i){
            ans += 2*(AA[i] + BB[i]);
        }
    }
    printf("%lld",ans);

    return 0;
}
/**

*/

K

题意

  • 给你一个长度为 N N N 的序列 b [ N ] b[N] b[N],满足 b [ i ] ∈ [ 0 , n − 1 ] b[i]\in[0,n-1] b[i][0,n1]
    让你重排列它,然后最小化这个值:
    ∑ i = 0 N − 1 i − b i \sum_{i=0}^{N-1}\sqrt{i-b_i} i=0N1ibi
    最后答案必须和标准答案均差 ≤ 4 % \le 4\% 4%
  • 10 ≤ N ≤ 1 0 3 10\le N\le 10^3 10N103

思路

  • 因为和标准答案有较小误差,于是我们想到贪心(???怎么想到的)
    我们不能直接单纯 s o r t sort sort ,因为根号函数导数越来越小,不像二次函数导数越来越大
    比如 b [ ] = { 1 , 2 , 3 } b[]=\{1,2,3\} b[]={1,2,3},答案为 3 3 3,但是我们让 b [ ] = { 3 , 1 , 2 } b[]=\{3,1,2\} b[]={3,1,2},答案就是 3 \sqrt3 3 更优了
  • 我们记录一下每个数字的出现次数,记作 c n t [ i ] cnt[i] cnt[i]
    我们记录 a n s [ i ] ans[i] ans[i] 表示下标为 i i i 最终放什么
  • 我们优先考虑怎么放可以让 i − b i = 0 i-b_i=0 ibi=0,然后优先考虑怎么放让 i − b i = 1 i-b_i=1 ibi=1,然后诸如此类
    也就是我们先枚举差 i = 0   t o   n i=0\ to\ n i=0 to n,然后枚举放的位置 j j j
    那么,我们只要去找是否存在 c n t [ j − i ] cnt[j-i] cnt[ji] c n t [ j + i ] cnt[j+i] cnt[j+i],有的话直接拿过来放在这个位置
  • 官方:经过试验,这样操作大多数误差是在 2.8 % 2.8\% 2.8%
    (???)
    好吧,以后这种有误差题都会优先去考虑贪心吧
  • 对了,如果数据比较小,可以使用 K M KM KM 算法, O ( N 3 ) O(N^3) O(N3) 算出正确的答案,样例数据也都是官方这样跑出来的

代码

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e3+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

int ans[MAX],cnt[MAX];

int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i = 0;i <= n;++i)ans[i] = -1,cnt[i] = 0;
        for(int i = 0;i < n;++i){
            int t;scanf("%d",&t);
            cnt[t]++;
        }
        for(int i = 0;i <= n;++i)
            for(int j = 0;j < n;++j){
                if(ans[j] != -1)continue;
                int L = j-i,R = j+i;
                if(L>=0 && cnt[L])ans[j]=L,cnt[L]--;
                else if(R<n && cnt[R])ans[j]=R,cnt[R]--;
            }
        for(int i = 0;i < n;++i)
            printf("%d ",ans[i]);
        puts("");
    }
    return 0;
}
/**

*/

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值