upc1976 树状数组和线段树两种做法

原创 2013年12月03日 22:58:30

1976: problem F

Time Limit: 3 Sec  Memory Limit: 64 MB
Submit: 78  Solved: 17
[Submit][Status][Web Board]

Description

在一个小镇上住着n位武林高手,他们互相之间经常PK,不过PK的时候必须要有一位裁判在场。这个小镇上的房子从1—n依次排列(一条直线上),两个人要单挑时必须到另一个人的家里,让这个人作为裁判,裁判的武功不能同时比两个人低也不能同时比两个人高。由于每个高手都很懒,所以他们走的路程不能比他俩之间的距离远(即只能在他们序号之间高手中选择裁判)。求小镇上最多能进行几场PK。

Input

先输入一个整数T(T<=100),包含T组测试数据,每组数据先输入n(n<=100000),然后后面是n个数,表示n个武林高手的功夫ai,(ai<=100000)。

Output

能够进行的最大场数,每组数据占一行。

Sample Input

231 2 352 2 2 2 2

Sample Output

110

  很久以前就看到过这个题,当时肯定是不会。现在听说是树状数组做,因为不怎么会,就又把树状数组看了一遍。。

  树状数组貌似都要离散化,先排个序什么的。这道题先用结构体记下每个人的武功值和序号,再按武功值排序(武功相等的序号小的在前)。然后按这个顺序先顺着循环一遍,循环过程中对每个序号(a[i].order)调用sum函数,也就是插入这个序号前有多少个序号比它小(正是武功值也比它小,序号也比它小),用lmin[i]记录在i左边武功比i小的,(是排了序后的第i个人),那么在i左边武功比i大的人数就是i-lmin[i](i从0开始),用lmax[i]记下,再插入a[i].order。接着重新倒着循环一遍,同理可求出rmin[]和rmax[],最后答案就是lmin[i]*rmax[i]+lmax[i]*rmin[i]的和。注意树状数组的起点是1。

 

#include<cstring>
#include<cstdio>
#include<iostream>
#include<climits>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;
int T,N;
long long lmin[400010],lmax[400010],rmin[400010],rmax[400010],c[400010];
struct node{
    int value,order;
}a[100010];
bool cmp(node a,node b){
    if(a.value==b.value) return a.order<b.order;
    return a.value<b.value;
}
int lowbit(int x){
    return x&(-x);
}
void add(int i,int v){
    while(i<=N){
        c[i]+=v;
        i+=lowbit(i);
    }
}
int sum(int i){
    int s=0;
    while(i>0){
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}
int main(){
   // freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        int i;
        long long ans=0;
        for(i=0;i<N;i++){
            scanf("%d",&a[i].value);
            a[i].order=i+1;
        }
        sort(a,a+N,cmp);
        memset(c,0,sizeof(c));
        for(i=0;i<N;i++){
            int s=sum(a[i].order);
            lmin[i]=s;
            lmax[i]=i-s;
            add(a[i].order,1);
        }
        memset(c,0,sizeof(c));
        for(i=N-1;i>=0;i--){
            int s=sum(a[i].order);
            rmin[i]=s;
            rmax[i]=N-i-1-s;
            add(a[i].order,1);
        }
        for(i=0;i<N;i++) ans+=(lmin[i]*rmax[i]+lmax[i]*rmin[i]);
        printf("%lld\n",ans);
    }
    return 0;
}

  当时学长说能用树状数组做的都能用线段树做,于是我就想了下线段树怎么做。排序那些还是一样,只要把add函数和sum函数修改一下。add函数就是往下找路径,路径上每个点都加一,sum函数我感觉有点像2进制有多少个0那个数位DP的,如果路径是往右,就把左边那一部分的加上(也就是比它小的),最后如果自己那条路径上也存在,那也要加上。

 

#include<cstring>
#include<cstdio>
#include<iostream>
#include<climits>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;
int T,N;
long long lmin[400010],lmax[400010],rmin[400010],rmax[400010],c[400010];
struct node{
    int value,order;
}a[100010];
bool cmp(node a,node b){
    if(a.value==b.value) return a.order<b.order;
    return a.value<b.value;
}
void add(int l,int r,int pos,int p,int v){
    if(l==r){
        c[pos]=v;
        return;
    }
    c[pos]+=v;
    int mid=(l+r)/2;
    if(p<=mid) add(l,mid,pos<<1,p,v);
    else add(mid+1,r,(pos<<1)+1,p,v);
}
int sum(int l,int r,int pos,int p){
    if(l==r) return c[pos];
    int mid=(l+r)/2;
    if(p<=mid) return sum(l,mid,pos<<1,p);
    else return c[pos<<1]+sum(mid+1,r,(pos<<1)+1,p);
}
int main(){
  //  freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        int i;
        long long ans=0;
        for(i=0;i<N;i++){
            scanf("%d",&a[i].value);
            a[i].order=i+1;
        }
        sort(a,a+N,cmp);
        memset(c,0,sizeof(c));
        for(i=0;i<N;i++){
            int s=sum(1,N,1,a[i].order);
            lmin[i]=s;
            lmax[i]=i-s;
            add(1,N,1,a[i].order,1);
        }
        memset(c,0,sizeof(c));
        for(i=N-1;i>=0;i--){
            int s=sum(1,N,1,a[i].order);
            rmin[i]=s;
            rmax[i]=N-i-1-s;
            add(1,N,1,a[i].order,1);
        }
        for(i=0;i<N;i++) ans+=lmin[i]*rmax[i]+lmax[i]*rmin[i];
        printf("%lld\n",ans);
    }
    return 0;
}

  做了这两种方法,对线段树和树状数组又熟悉了,感觉树状数组快一些,因为不用递归。这两种方法的区间表示方式区别挺大的,树状数组增加元素m只要循环从m到N,求sum的时候只要从m到1。而线段树修改和询问函数都要找它的区间。

  要注意的是数组要开4*N。


线段树&树状数组总结篇

1、 入门题:hdu1166敌兵布阵 单点更新区间求和 线段树: using namespace std; struct node { int l,r,sum; }num[maxn*...
  • zhou_yujia
  • zhou_yujia
  • 2016年05月12日 22:03
  • 736

【算法与实现】线段树&树状数组(上)

线段树&树状数组(上) 一、线段树   1.     结构描述: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子...
  • u011526340
  • u011526340
  • 2014年09月30日 00:24
  • 919

图说线段树和树状数组

图说线段树和树状数组
  • sunny606
  • sunny606
  • 2014年08月25日 17:30
  • 2351

线段树或树状数组求逆序数(附例题)

线段树或树状数组求逆序数 假设给你一个序列 6 1 2 7 3 4 8 5, 首先我们先手算逆序数, 设逆序数为 N; 6的前面没有比他大的数 N +=0 1的前面有一个比他大的数 N+=1 ...
  • chaiwenjun000
  • chaiwenjun000
  • 2015年08月21日 13:02
  • 1633

树状数组和线段树

一、树状数组 在解题过程中,我们有时需要维护一个数组的前缀和 S[i]=A[1]+A[2]+...+A[i] 。但是不难发现,如果我们修改了任意一个 A[i],S[i] 、 S[i+1]...S[n]...
  • BOYxiejunBOY
  • BOYxiejunBOY
  • 2015年07月16日 20:22
  • 535

[树套树] 可持久化线段树 树状数组套值域线段树

大家都很强, 可与之共勉。q l, r, k查询[l, r] 第k小。m pos, x; 把pos位置的改为x。#include "cstdio" #include "cctype" #includ...
  • simpsonk
  • simpsonk
  • 2017年04月04日 15:45
  • 278

线段树 && 树状数组

啊哈!第一篇文章~ 在机房奋斗了几个小时终于打出了线段树!屠龙宝刀点击就送#include #include using namespace std; const int MAXN = 200010...
  • Loi_Vampire
  • Loi_Vampire
  • 2016年03月18日 19:51
  • 345

bzoj 1901 有更新区间第k大 树状数组套可持久化线段树

http://www.lydsy.com/JudgeOnline/problem.php?id=1901 我的可持久化线段树消耗的内存太大了,在zoj超内存 如果对 上次的那题无修改的区间第k大的...
  • haha593572013
  • haha593572013
  • 2012年10月09日 19:54
  • 3511

【洛谷】线段树 树状数组区间修改区间查询

在做一道整体二分的题目的时候遇到了这种区间修改区间查询的树状数组,感觉用起来手感不错就拿来了。证明的话,那其实不重要,会用就好了 #include #include #include #define...
  • pbihao
  • pbihao
  • 2016年12月06日 22:10
  • 134

bzoj 1901 ZOJ 2112 Dynamic Rankings [树状数组套主席树] [线段树套平衡树]

Dynamic RankingsTime Limit: 10 Sec Memory Limit: 128 MB Submit: 6900 Solved: 2870Description给定一个含...
  • ourfutr2330
  • ourfutr2330
  • 2016年08月06日 15:39
  • 577
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:upc1976 树状数组和线段树两种做法
举报原因:
原因补充:

(最多只允许输入30个字)