线段树专项训练
贴一篇个人认为很好的线段树讲解:
https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html
-
A - 单点修改区间求和
hdoj 1166:http://acm.hdu.edu.cn/showproblem.php?pid=1166
直接套模版,父节点是左右节点的和。 -
B - 单点修改区间最值
hdoj 1754:http://acm.hdu.edu.cn/showproblem.php?pid=1754
直接套模版,父节点是左右节点的最大值。 -
C - 求逆序对
hdoj 1394:http://acm.hdu.edu.cn/showproblem.php?pid=1394
大意:给出n个数字,范围0~n-1,有n-1种变换,每次把第一个数移动至最后一位,求出在所有移动方法中,存在的逆序对最少是多少组。
逆序对怎么求?
考察每一位,从这一位往前看,有多少个比它大的数,记录下来,作为线段树叶子节点值。例如:1 3 6 9 0 8 5 7 4 2,叶子节点分别为 0 0 0 0 4 1 3 2 5 7,构建二叉树,父节点是左右节点的和,在上面这个例子中,tree[1]为22,则该序列逆序对的个数为22。
得到初始数列逆序对个数后,求后面的逆序对个数有一个公式:sum=sum+(n-1-arr[i])-arr[i]。然后就解决了。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=5005;
struct node
{
int l,r,w;
}tree[4*maxn+1];
int a[5005]={0};
void build(int l,int r,int k)
{
tree[k].l=l;tree[k].r=r;
if(l==r)
{
tree[k].w=0;
return ;
}
int m=(l+r)/2;
build(l,m,k*2);
build(m+1,r,k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
void add(int x,int y,int k)
{
if(tree[k].l==tree[k].r)
{
tree[k].w+=y;
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) add(x,y,k*2);
else add(x,y,k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
int main()
{
int n;
while (cin>>n)
{
memset(a,0,sizeof(a));
int arr[5005]={0};
for (int i=1;i<=n;i++)
{
cin>>arr[i];
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<i;j++)
{
if (arr[i]<arr[j])
{
a[i]++;
}
}
}
build(1,n,1);
for (int i=1;i<=n;i++)
{
add(i,a[i],1);
}
int sum=tree[1].w;
int ans=sum;
for (int i=1;i<n;i++)
{
sum=sum+(n-1-arr[i])-arr[i];
ans=min(sum,ans);
}
cout<<ans<<endl;
}
}
这是今年四月份写的了,一直在草稿箱里,这个专题后面还有几道题,但是当时没写题解。
现在退役了,发出来吧。