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。


相关文章推荐

HDU 1394 (树状数组 & 线段树 两种做法)

Minimum Inversion Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Jav...

hdu 1166 敌兵布阵 单点更新 树状数组 线段树 两种做法

题意中文题不用解释; 线段树: #include #include #define lson l , m , rt

※ Leetcode - Segment Tree - 307. Range Sum Query - Mutable (线段树+树状数组两种解法以及模板的常见问题解析)

1. Problem Description Given an integer array nums, find the sum of the elements between indices i a...

单调栈、单调队列、线段树、LCA、二维树状数组、Bitset讲解

一、单调栈 1.问题引入 考虑这样一个问题,给出一个数字序列,一段连续的子序列的权值定义为这个子序列中最小的权值乘以子序列的长度,求最大的子序列权值,数据范围O(n)可过。 2.问题转化 稍加...
  • LZJ209
  • LZJ209
  • 2017年07月29日 10:39
  • 416

HDU 1754 I Hate It (线段树 & 树状数组)

I Hate It Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub...

BZOJ 3744 Gty的妹子序列 分块+树状数组+可持久化线段树

题目大意:给定一个序列,多次求区间内逆序对个数 强制在线 让我们呐喊一声:出题人卡常数丧心病狂! 再来一次:出题人卡常数丧心病狂!!!! 不强制在线的直接莫队就能搞 强制在线我是跪了QTZ 首...
  • PoPoQQQ
  • PoPoQQQ
  • 2014年11月13日 23:04
  • 2515

【区间DP】【二维线段树】【二维树状数组】2017.5.20 T3 deliver 题解

Problem 3 (deliver.pas/cpp/c) 【题目描述】 有一个军队正在编队。他们有n个人,编号1-n,编队结构如下: 1.一个军队有一个指挥官 2.编号小于他们指挥官编号的组...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:upc1976 树状数组和线段树两种做法
举报原因:
原因补充:

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