越学线段树越觉得线段树是树状数组的升级
最小逆序数的学习是HDU1394
求出一个序列的逆序数后如果把头上元素a[1]放到末尾那么逆序数sum的变化为sum += (n-1-a[i])-a[i];
n为序列长度,a[i]就是第一个元素的值,来回遍历一遍就可以得到最小逆序数了
#define lson i<<1,left,mid
#define rson i<<1|1,mid + 1,right
using namespace std;
const int maxn = 5000+50;
int a[maxn];
int pre[maxn];
int tree[maxn<<2];
准备工作——a数组用来存序列
pre数组存放每一个值所对应的节点序号,方便向上二分更新
tree【】就是线段树,节点存放的的这个区间内出现的数的个数
所以我感觉就和树状数组一样了~~
int main()
{
int t,n,m,cas = 1;
while(~scanf("%d",&n))
{
build(1,0,n);
int sum = 0;
for(int i = 0;i < n;i++)
{
ans = 0;
scanf("%d",&a[i]);
query(1,1,n,a[i] + 1,n);
sum += ans;
tree[pre[a[i]]]++;
update(pre[a[i]]);
}
int ret = sum;
for(int i = 0;i < n;i++)
{
sum += (n-1-a[i])-a[i];
ret = min(ret,sum);
}
printf("%d\n",ret);
}
return 0;
}
根据输入看一下build_tree
void build(int i,int left,int right)
{
tree[i] = 0;
if(left == right)
{
pre[left] = i;
return;
}
int mid = (left + right) >> 1;
build(lson);
build(rson);
}
常规的建树过程,找到叶子节点的时候记录一下节点的值void query(int i,int left,int right,int le,int re)
{
if(le == left && re == right)
{
ans += tree[i];
return;
}
int mid = (left + right) >> 1;
if(re <= mid)query(lson,le,re);
else if(le > mid)query(rson,le,re);
else
{
query(lson,le,mid);
query(rson,mid + 1 ,re);
}
}
然后是询问过程,划分区间来询问,因为是从下到上的更新,所以一旦找到最小包容区间的时候就return
更新很简单,由下到上更新到叶子节点
void update(int i)
{
while(i != 1)
{
int fa = i / 2;
tree[fa] = tree[fa << 1] + tree[fa << 1 | 1];
i = fa;
}
}
#include <iostream>
#define lson i<<1,left,mid
#define rson i<<1|1,mid + 1,right
using namespace std;
const int maxn = 5000+50;
int a[maxn];
int pre[maxn];
int tree[maxn<<2];
void build(int i,int left,int right)
{
tree[i] = 0;
if(left == right)
{
pre[left] = i;
return;
}
int mid = (left + right) >> 1;
build(lson);
build(rson);
}
int ans;
void query(int i,int left,int right,int le,int re)
{
if(le == left && re == right)
{
ans += tree[i];
return;
}
int mid = (left + right) >> 1;
if(re <= mid)query(lson,le,re);
else if(le > mid)query(rson,le,re);
else
{
query(lson,le,mid);
query(rson,mid + 1 ,re);
}
}
void update(int i)
{
while(i != 1)
{
int fa = i / 2;
tree[fa] = tree[fa << 1] + tree[fa << 1 | 1];
i = fa;
}
}
int main()
{
int t,n,m,cas = 1;
while(~scanf("%d",&n))
{
build(1,0,n);
int sum = 0;
for(int i = 0;i < n;i++)
{
ans = 0;
scanf("%d",&a[i]);
query(1,1,n,a[i] + 1,n);
sum += ans;
tree[pre[a[i]]]++;
update(pre[a[i]]);
}
int ret = sum;
for(int i = 0;i < n;i++)
{
sum += (n-1-a[i])-a[i];
ret = min(ret,sum);
}
printf("%d\n",ret);
}
return 0;
}
其实这个题就这么结束了,可我竟然啃了那么久
有一点小感慨吧,就是做题的思路很重要,先把整个框架小号,我要几步分别干什么,想好之后在开始打代码一定要想好在开始,思路思想很重要,代码仅仅是一个展现你思路的工具,其实并不大重要,你有了思路,却打不出来,那是代码能力,那个好学好练,可是好的思路却是难得的