标签:Inversion 题解 ll Number long mid 数组 sum 逆序
HDOJ 1394 Minimum Inversion Number 题解
题目大意
给你一个 $ n $ 个数的序列,其中只有 $ 0 $ ~ $ n - 1 $ ,可以把第一个数移到最后一个,次数不限。
求在所有能够生成的数列中逆序对的最小数量。
注意数据有多组。
输入
对于每组数据,先输入一个数 $ n $ 。
接下来 $ n $ 个数,表示原序列。
输入文件以 $ EOF $ 结束。
输出
所有能够生成的数列中逆序对的最小数量。
思路
想法
$ \huge 逆序对 $ ,你能想到什么?
-
归并排序
-
线段树
-
树状数组
暴力
求 $ n $ 次逆序对。复杂度 $ O(n^2 \ log \ n) \huge TLE $ 。
代码如下,这里用的是归并排序。
#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",&aa[i]);
aa[i+n]=aa[i];//二倍数组
}
ans=0x3f3f3f3f;
for(ll i=1;i<=n;i++){
cnt=0;//初始化
for(ll j=1;j<=2*n;j++)a[j]=aa[j];//备份
merge_sort(i,i+n-1);//归并排序
ans=min(ans,cnt);//答案
}
printf("%lld\n",ans);
}
return 0;
}
正解
实际上,我们将 $ a[1] $ 放到 $ a[n+1] $ , $ a[2] $ ~ $ a[n] $ 是没有改变的。我们可以尝试找到两个数列逆序对数目的关系。我们可以分成两部分考虑。
-
当 $ a $ 数列去掉 $ x $ 时,逆序对个数会少数列中比 $ x $ 小的数的个数。由于数列只有 $ 0 $ ~ $ n - 1 $ ,所以比 $ x $ 小的数的个数就等于 $ x $ 。
-
当 $ 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;//最小逆序对数目
ll lowbit(ll x){//lowbit
return x&-x;
}
void change(ll pos){//单点修改
while(pos<=n){
sum[pos]++;//更改
pos+=lowbit(pos);//下一个节点
}
}
ll query(ll pos){//区间求和
ll tmp=0;//临时存储答案
while(pos){//向下寻找
tmp+=sum[pos];//统计
pos-=lowbit(pos);//下一个节点
}
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 !
关于zkw线段树
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
标签:Inversion,题解,ll,Number,long,mid,数组,sum,逆序 来源: https://www.cnblogs.com/zsc985246/p/16366573.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。