题意:给出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;
}