「HDOJ 1394」Minimum Inversion Number 题解

本文网址:https://www.cnblogs.com/zsc985246/p/16366573.html ,转载请注明出处。

题目大意

给你一个 n 个数的序列,其中只有 0 ~ n1 ,可以把第一个数移到最后一个,次数不限

求在所有能够生成的数列中逆序对的最小数量

思路

暴力的想法肯定是求 n 次逆序对。

实际上,我们将 a[1] 放到 a[n+1]a[2] ~ a[n] 是没有改变的。我们可以尝试找到两个数列逆序对数目的关系。我们可以分成两部分考虑。

  1. a 数列去掉 x 时,逆序对个数会少数列中比 x 小的数的个数。由于数列只有 0 ~ n1 ,所以比 x 小的数的个数就等于 x

  2. a 数列增加 x 时,逆序对个数会多比 x 大的数的个数。由于数列只有 0 ~ n1 ,所以比 x 小的数的个数就等于 nx1

所以:ans=ansx+nx1=ans+n2×x1

代码

归并排序

#include<bits/stdc++.h>
#define ll long long//这回是long long了 
const ll N=100010;
using namespace std;

ll n;//长度 
ll a[N];//原数组 
ll aa[N];//排序数组 
ll t[N];//合并数组 
ll cnt;//逆序对数目 
ll ans;//最小逆序对数目 
void merge(ll l1,ll r1,ll l2,ll r2){//合并 
	ll i=l1;//遍历第一个序列 
	ll j=l2;//遍历第二个序列 
	ll k=l1;//遍历合并序列 
	while(i<=r1&&j<=r2){//两个序列都还有数 
		if(a[i]<=a[j]){
			t[k++]=a[i++];//第一个序列的数加入合并序列 
		}else{
			t[k++]=a[j++];//第二个序列的数加入合并序列 
			cnt+=r1-i+1;//统计逆序对 
		}
	}
	//第一个序列剩下的数整体放入合并序列 
	while(i<=r1){
		t[k++]=a[i++];
	}
	//第二个序列剩下的数整体放入合并序列 
	while(j<=r2){
		t[k++]=a[j++];
	}
	for(ll l=l1;l<=r2;l++)a[l]=t[l]; 
}

void merge_sort(ll l,ll r){//归并排序 
	if(l==r)return;//边界条件 
	ll mid=(l+r)>>1;
	merge_sort(l,mid);//分治 
	merge_sort(mid+1,r);//分治 
	merge(l,mid,mid+1,r);//合并 
}

int main(){
	
	//读入,注意多组数据 
	while(scanf("%lld",&n)!=EOF){
		for(ll i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			aa[i]=a[i]; 
		}
		ans=0x3f3f3f3f,cnt=0;//初始化
		merge_sort(1,n);//归并排序 
		for(ll i=1;i<=n;i++){
			ans=min(ans,cnt);//答案 
			cnt+=n-2*aa[i]-1;//统计 
		}
		printf("%lld\n",ans);//输出
	}
	
	return 0;
}

线段树

#include<bits/stdc++.h>
#define ll long long//这回是long long了 
const ll N=200010;//四倍数组 
using namespace std;

ll n;//长度 
ll a[N];//原数组 
ll sum[N];//线段树 
ll cnt;//逆序对数目 
ll ans;//最小逆序对数目 

void change(ll rt,ll l,ll r,ll pos){//单点修改
	if(l==r){//单点 
		sum[rt]++;//更改 
		return;
	}
	ll mid=l+r>>1;//中间值 
	ll lson=rt<<1,rson=rt<<1|1;//左右儿子 
	if(pos<=mid)change(lson,l,mid,pos);//在左子树 
	else change(rson,mid+1,r,pos);//在右子树 
	sum[rt]=sum[lson]+sum[rson];//维护 
}

ll query(ll rt,ll l,ll r,ll x,ll y){//区间求和 
	if(x<=l&&r<=y)return sum[rt];//包含此区间 
	ll mid=l+r>>1;//中间值 
	ll tmp=0;//临时记录答案 
	ll lson=rt<<1,rson=rt<<1|1;//左右儿子 
	if(x<=mid)tmp+=query(lson,l,mid,x,y);//左儿子在区间内 
	if(mid<r)tmp+=query(rson,mid+1,r,x,y);//右儿子在区间内 
	return tmp;//返回答案 
}

int main(){
	
	//读入,注意多组数据 
	while(scanf("%lld",&n)!=EOF){
		memset(sum,0,sizeof(sum));//初始化 
		ans=0x3f3f3f3f,cnt=0;//初始化 
		for(ll i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			cnt+=query(1,1,n,a[i]+1,n);//先询问 
			change(1,1,n,a[i]);//后加入 
		}
		for(ll i=1;i<=n;i++){
			ans=min(ans,cnt);//答案 
			cnt+=n-2*a[i]-1;//统计 
		}
		printf("%lld\n",ans);//输出 
	}
	
	return 0;
}

树状数组

#include<bits/stdc++.h>
#define ll long long//这回是long long了 
const ll N=200010;//四倍数组 
using namespace std;

ll n;//长度 
ll a[N];//原数组 
ll sum[N];//树状数组 
ll cnt;//逆序对数目 
ll ans;//最小逆序对数目 

void change(ll x){//单点修改 
	while(x<=n){
		sum[x]++;//更改 
		x+=x&-x;//下一个节点 
	}
}

ll query(ll x){//区间求和 
	ll tmp=0;//临时存储答案 
	while(x){//向下寻找 
		tmp+=sum[x];//统计 
		x-=x&-x;//下一个节点 
	}
	return tmp;//返回答案 
}

int main(){
	
	//读入,注意多组数据 
	while(scanf("%lld",&n)!=EOF){
		memset(sum,0,sizeof(sum));//初始化 
		ans=0x3f3f3f3f,cnt=0;//初始化 
		for(ll i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			//这里a[i]+1是为了防止0出现 
			//原因:0+lowbit(0)=0,在change的时候会死循环 
			cnt+=query(n)-query(a[i]+1);//先询问 
			change(a[i]+1);//后加入 
		}
		for(ll i=1;i<=n;i++){
			ans=min(ans,cnt);//答案 
			cnt+=n-2*a[i]-1;//统计 
		}
		printf("%lld\n",ans);//输出 
	}
	
	return 0;
}

zkw线段树

#include<bits/stdc++.h>
#define ll long long
const ll N=200010;//二倍数组 
using namespace std;

ll n;//长度 
ll m;//非叶子节点 
ll a[N];//原数组 
ll sum[N];//线段树 
ll cnt;//逆序对数目 
ll ans;//最小逆序对数目 

void change(ll pos){//单点修改
	pos+=m;//直接跳到对应节点 
	for(;pos;pos>>=1){//找父亲 
		sum[pos]++;//维护 
	}
}

ll query(ll s,ll t){//区间求和 
	ll tmp=0;//临时答案 
	s+=m-1,t+=m+1;//直接跳到对应节点 
	for(;s^t^1;s>>=1,t>>=1){
		if(s&1^1)tmp+=sum[s^1];//统计 
		if(t&1)tmp+=sum[t^1];//统计 
	}
	return tmp;//返回 
}

int main(){
	
	//读入,注意多组数据 
	while(scanf("%lld",&n)!=EOF){
		memset(sum,0,sizeof(sum));//初始化 
		for(m=1;m<=n;m<<=1);
		ans=0x3f3f3f3f,cnt=0;//初始化 
		for(ll i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			cnt+=query(a[i]+1,n);//先询问 
			change(a[i]);//后加入 
		}
		for(ll i=1;i<=n;i++){
			ans=min(ans,cnt);//答案 
			cnt+=n-2*a[i]-1;//统计 
		}
		printf("%lld\n",ans);//输出 
	}
	
	return 0;
}

关于zkw线段树

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @   zsc985246  阅读(285)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示