您的位置:首页 > 其它

hdu 1394 Minimum Inversion Number(成段更新)

2015-10-15 10:25 274 查看
题意:给你区间在[0,n-1]的整数,也就是n个数排成一行,下标从0开始到n-1,让你求它的逆序数之和,然后每次将最左边的的数移到末尾,重新求逆序数,这样有n组,让你求最小的逆序数之和,所谓逆序数,就是下标i<j,但数值确是x[i]>x[j],让你求这样的数有几个。

比如 : 3 1 2 4 0

下标比三小,数值比三大的数没有,则对三来说满足条件的是零个

然后考虑1,下标比一小,数值比一大,対一来说满足的为1个

同理 对2来说有一个,对四来说没有,对零来说有4个,所以逆序数个数为6个

这样一组逆序数就求出来了。

题目要求的是n组中逆序数的最小和,下面有一个规律:

每一次把第一个数移到最右边,原来比他小的数都构不成逆序数,比它大的数重新成为逆序数,所以要加上比它大的数,减去比他小的数,还有一个技巧,就是给定的是连续的数,假设第一个数值为m,要把它移到末尾,因为总共有n个数,下标从0 开始,所以,比m大的数就是(n-m-1)个,比m小的数就是m个,所以得到递推公式 sum =sum +(n-1+m)-(m),在用一重循环,求出最小的值。

两种方法:

第一种:暴力,两重循环,求出每一组逆序数之和

<span style="font-size:18px;">#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define maxn 5555
using namespace std;
int x[maxn];
int main()
{
int n,sum,a;
while(~scanf("%d",&n))
{
sum=0;
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
for(int i=n-1;i>0;i--)
{
for(int j=0;j<i;j++)
if(x[i]<x[j]) sum++; //每次循环找出比当前数下标比它小,数值比它大的数,次数加一
}
// printf("%d\n",sum);
int ans=sum;
for(int i=0;i<n;i++)
sum+=(-x[i])+(n-x[i]-1),ans=min(ans,sum);
printf("%d\n",ans);
}
}</span><span style="font-size:24px;">
</span>


第二种:线段树

输入一个点,就把他与之对应的叶子节点标记为1,说明这个点已经插入,这样插入可以表明插入的先后,所以每次查询的只要查询当前点到n-1这段区间,若有标记为一的,就说明这是满足条件的数,逆序数和加一。

#include<iostream>
#include<cstdio>
#define maxn 5555
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
int sum[maxn<<2];
int x[maxn];
void pushup(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //向上更新逆序数的和
}
void build(int l,int r,int rt)
{
sum[rt]=0;   //初始化,建立一个空的线段树
if(l==r) return;
int m=(l+r)>>1;
build(lson);
build(rson);
}
int query(int L,int R,int l,int  r,int rt)
{
if(L<=l&&r<=R)  return sum[rt];    //每次询问,插入的点作为区间左端点,n-1作为区间右端点
int m=(l+r)>>1;
int ret=0;
if(L<=m) ret+=query(L,R,lson);
if(R>m)  ret+=query(L,R,rson);
return ret;
}
void update(int p,int c,int l,int r,int rt)
{
if(l==r)
{
sum[rt]=c;  //每次插入一个数,就标记为c
return ;
}
int m=(l+r)>>1;
if(p<=m) update(p,c,lson);
else update(p,c,rson);
pushup(rt);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int sum=0;
build(0,n-1,1); //题意区间为[0,n-1]
for(int i=0;i<n;i++)  //每次输入一个点,先询问序号比他小,数值比他大的数的数数值和,就把他插入到线段树内
{
scanf("%d",&x[i]);
sum+=query(x[i],n-1,0,n-1,1); //询问
update(x[i],1,0,n-1,1);//更新
}
int ans = sum;
//printf("%d",ans);
for(int i=0;i<n;i++) //递推
{
sum+=(-x[i])+(n-x[i]-1);
ans=min(ans,sum); //不断求最小的值
}
printf("%d\n",ans);
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: