「HDOJ 1394」Minimum Inversion Number 题解

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

题目大意

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

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

思路

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

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

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

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

所以:$ans = ans - x + n - x - 1 = ans + n - 2 \times x - 1 $ 。

代码

归并排序

#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 @ 2022-06-11 19:10  zsc985246  阅读(300)  评论(0)    收藏  举报