D. Constant Palindrome Sum(线段树)

原题链接

题意:给出n/2个点对,问使得所有点对和相等并且每一个点对的每一个数都小于等于k的最小代价是多少,每花费1的代价就可以把某一个数变成1~k的数字。

思路:
首先很容易想到的就是若一个点对两个数字都大于k,那么这两个数肯定都要花费1的代价,此时总代价加上2;这个地方初始贡献即为ans。

其次若两个数都小于等于k,那我们就先花费1的代价判断这个点对所能达到的和的区间是什么,假设两者较小的是mi,较大的是mx,那么这个点对能覆盖的范围就是(mi+1,mx+k)。

最后是两个数有一个大于k的,那我们就先假定只修改这个大于k的数,那么这个点对覆盖的就只是一个点了,为了使得这个点在所有点对的覆盖区间都能覆盖到,那么我们就先假设修改的这个数数值定为1,那么覆盖的范围就是(mi+1,mi+1);

我们把所有覆盖的地方权值都加上1,那么最优的时候我们肯定选择权值最大的点(相当于都很多点对花费1都可以到达这个点),
然后假设去掉都大于k的点对数量是tot,那么假设点对和不是原数组中已有的和,那么初始答案就是ans+tot+tot-q[1].mx-mxx。
q[1].mx是线段树全局最大值,mxx是原点对和能取到q[1].mx的位置的点对数量的最大值。因为这个地方最开始假定花费1的花费可以省了。

还有一种可能是点对和在原数组中取到,那我们就可以枚举每一个原数组点对和,设这个点位置的所有点对覆盖的最大值为g,那么这个位置带来的总贡献就是ans+tot+tot-g-和为这个位置的点对数量(同上,这个位置最开始花费1可以省了),然后就在这两种情况取min就行了。

代码如下:

#include<bits/stdc++.h>
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const LL inf=1e9;
const int N=4e5+5;
const int M=1e6+5;
const int mod=998244353;
int n,m,k,t,T,op;
int a[N];
struct node{
    int l,r;
    int lan,mx;
}q[N*4];
void change(int now){
    q[now].mx=max(q[now<<1].mx,q[now<<1|1].mx);
}
void build(int now,int L,int R){
    q[now].l=L;q[now].r=R;q[now].lan=0;
    q[now].mx=0;
    if(L==R){
        return;
    }
    int mid=L+R>>1;
    build(now<<1,L,mid);
    build(now<<1|1,mid+1,R);
}
void push(int now){
    int lc(now<<1),rc=(now<<1|1);
    q[lc].mx+=q[now].lan;
    q[lc].lan+=q[now].lan;
    q[rc].mx+=q[now].lan;
    q[rc].lan+=q[now].lan;
    q[now].lan=0;
}
void update(int now,int L,int R,int v){
    if(L>R)return;
    if(q[now].l>=L&&q[now].r<=R){
        q[now].lan++;
        q[now].mx++;
        return;
    }
    if(q[now].lan)push(now);
    int mid=q[now].l+q[now].r>>1;
    if(mid>=L)update(now<<1,L,R,v);
    if(R>mid)update(now<<1|1,L,R,v);
    change(now);
}
int query(int now,int pos){
    if(q[now].l>=pos&&q[now].r<=pos){
        return q[now].mx;

    }
    if(q[now].lan)push(now);
    int mid=q[now].l+q[now].r>>1;
    if(mid>=pos)return query(now<<1,pos);
    else return query(now<<1|1,pos);
}
void solve(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    build(1,1,2*k);
    int ans=0,tot=0;
    map<int,int>p;
    for(int i=1;i<=n/2;i++){
        if(a[i]>k&&a[n-i+1]>k){//都大于k直接加入到答案里
            ans+=2;
            continue;
        }
        tot++;//记录剩下的点对,先假设每一个点对都花费1
        if(a[i]<=k&&a[n-i+1]<=k){
            int mi=min(a[i],a[n-i+1]);
            int mx=max(a[i],a[n-i+1]);
            p[mi+mx]++;//用map记录原始点对和的数量
            update(1,mi+1,mx+k,1);//更新区间
        }else {
            int mi=min(a[i],a[n-i+1]);
            update(1,mi+1,mi+1,1);
            p[mi+1]++;//必须修改的那个数设置为1
        }
    }
    int mxx=0,res=n;
    for(auto e:p){
        auto g=query(1,e.fi);
        res=min(res,ans+tot+tot-g-e.se);//枚举原始点对每一个位置带来的贡献
        if(g==q[1].mx){//找到能取到最大值位置原始的点对数量最大值
            mxx=max(mxx,e.se);
        }
    }
    printf("%d\n",min(res,ans+tot+tot-q[1].mx-mxx));//两者取min
}
int main(){
    int o;scanf("%d",&o);
    while(o--){
        solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值