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

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。


你可能感兴趣的:(upc1976 树状数组和线段树两种做法)