划分树

先排序求出排序后的数列

划分树,就是从下向上的递归,每次都把小于或等于(等于的个数是n/2-小于中间数的个数)中间数的数放前。

 lg( n ) 的算法。

(1)poj 2104 K-th Number

最基本的划分树题目,求给定区间内的最大的值。

View Code
// 划分树
#include<cstdio>
#include<iostream>
#include<algorithm>
#define size 100005
using namespace std;
#define inta  long long
int s[size];   //排序的后的数
int val[25][size];    //记录第k层当前位子的元素值
int num[25][size];    //记录元素所在区间的当前位置之前进入左孩子的个数

void build(int l,int r,int p)
{
    if(l==r)
        return ;
    int i,mid=(l+r)>>1,same;
    same=mid-l+1;
    for(i=l;i<=r;i++)
    {
        if(val[p][i] < s[mid])
            same--;
    }
    int ln=l,rn=mid+1;
    for(i=l;i<=r;i++)
    {
        if(i==l)
            num[p][i]=0;
        else
            num[p][i]=num[p][i-1];
        if(val[p][i]<s[mid])
        {
            num[p][i]++;
            val[p+1][ln++]=val[p][i];
        }
        else if(val[p][i]>s[mid])
            val[p+1][rn++]=val[p][i];
        else
        {
            if(same>0)
            {
                same--;
                num[p][i]++;
                val[p+1][ln++]=val[p][i];
            }
            else
                val[p+1][rn++]=val[p][i];
        }
    }
    build(l,mid,p+1);
    build(mid+1,r,p+1);
}

int query(int li,int ri,int k,int l,int r,int p)
{
//    if(li>ri)  return 0;
    if(l==r)
        return val[p][li];
    int mid=(l+r)>>1;
    int w1=num[p][ri],w2=0,b1,b2;
    if(li>l)
    {
        w2=num[p][li-1];  //记录区间[l, li-1)中计入左孩子的元素的个数
        w1-=w2;           // 记录区间[li, ri]中进入左孩子的元素的个数
    }
    if(w1>=k)
        return query(l+w2,l+w2+w1-1,k,l,mid,p+1);
    else
    {
        b1=li-l-w2;     //表示[l, li-1]中分到右孩子的个数
        b2=ri-li+1-w1;  //表示[li, ri] 中分到右孩子的个数
        return query(mid+b1+1,mid+b1+b2,k-w1,mid+1,r,p+1);
    }
}
int main()
{
    int i,n,m;
    int l,r,k;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&s[i]);
        val[1][i]=s[i];
    }
    sort(s+1,s+1+n);
    build(1,n,1);
    for(i=0;i<m;i++)
    {
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",query(l,r,k,1,n,1));
    }
    return 0;
}

(2)hdu 3727 Jewel

对数据进行四个操作,1)直接把数加到末尾,2,4直接可用划分树求解,而3是划分树的方向应用。

意::一直wa ,是应为定义了个size,把size 变成max就过了,,气人呀~~~

View Code
#include<cstdio>
#include<algorithm>
#define max 110000
using namespace std;
int s[max];   //排序的后的数
int val[25][max];    //记录第k层当前位子的元素值
int num[25][max];    //记录元素所在区间的当前位置之前进入左孩子的个数
struct point
{
    int flag,be,end,k;
};
point po[max];
void build(int l,int r,int p)
{
    if(l==r)
        return ;
    int i,mid=(l+r)>>1,same;
    same=mid-l+1;
    for(i=l;i<=r;i++)
    {
        if(val[p][i] < s[mid])
            same--;
    }
    int ln=l,rn=mid+1;
    for(i=l;i<=r;i++)
    {
        if(i==l)
            num[p][i]=0;
        else
            num[p][i]=num[p][i-1];
        if(val[p][i]<s[mid])
        {
            num[p][i]++;
            val[p+1][ln++]=val[p][i];
        }
        else if(val[p][i]>s[mid])
            val[p+1][rn++]=val[p][i];
        else
        {
            if(same>0)
            {
                same--;
                num[p][i]++;
                val[p+1][ln++]=val[p][i];
            }
            else
                val[p+1][rn++]=val[p][i];
        }
    }
    build(l,mid,p+1);
    build(mid+1,r,p+1);
}

int query(int li,int ri,int k,int l,int r,int p)
{
    if(l==r)
        return val[p][li];
    int mid=(l+r)>>1;
    int w1=num[p][ri],w2=0,b1,b2;
    if(li>l)
    {
        w2=num[p][li-1];  //记录区间[l, li-1)中计入左孩子的元素的个数
        w1-=w2;           // 记录区间[li, ri]中进入左孩子的元素的个数
    }
    if(w1>=k)
        return query(l+w2,l+w2+w1-1,k,l,mid,p+1);
    else
    {
        b1=li-l-w2;     //表示[l, li-1]中分到右孩子的个数
        b2=ri-li+1-w1;  //表示[li, ri] 中分到右孩子的个数
        return query(mid+b1+1,mid+b1+b2,k-w1,mid+1,r,p+1);
    }
}
int getans(int end,int x,int n)
{
    int low=1,high=end,a;
    while(low<=high)
    {
        int m=(low+high)>>1;
        a=query(1,end,m,1,n,1);
        if(a<=x)
            low=m+1;
        else
            high=m-1;
    }
    return low-1;
}

int main()
{
    int n,i,m,t;
    char str[10];
    long long ans1,ans2,ans3;
    int as=1;
    while(scanf("%d",&m)!=EOF)
    {
        n=0;ans1=0;ans2=0;ans3=0;t=0;
        for(i=0;i<m;i++)
        {
            scanf("%s",str);
            if(str[0]=='I')
            {
                ++n;
                scanf("%d",&s[n]);
                val[1][n]=s[n];
            }
            else
            {
                t++;
                if(str[6]=='1')
                {
                    po[t].flag=1;
                    scanf("%d%d%d",&po[t].be,&po[t].end,&po[t].k);
                }
                else if(str[6]=='2')
                {
                    po[t].flag=2;
                    scanf("%d",&po[t].k);
                    po[t].be=1;
                    po[t].end=n;
                }
                else
                {
                    po[t].flag=3;
                    scanf("%d",&po[t].k);
                    po[t].be=1;
                    po[t].end=n;
                }
            }
        }
        sort(s+1,s+n+1);
        build(1,n,1);
        printf("Case %d:\n",as++);
        for(i=1;i<=t;i++)
        {
            if(po[i].flag==1)
                ans1+=query(po[i].be,po[i].end,po[i].k,1,n,1);
            else if(po[i].flag==2)
                ans2+=getans(po[i].end,po[i].k,n);
            else
                ans3+=query(po[i].be,po[i].end,po[i].k,1,n,1);
        }
        printf("%I64d\n%I64d\n%I64d\n",ans1,ans2,ans3);
    }
    return 0;
}

(3)hdu 3473 Minimum Sum

这题除了划分树,考的就是划分树中sum的活用 ,sum为[ l , r ]中的小于第k个数的和。

这题有两个注意:first:她所求的x就是中位数,奇数是中间那个,偶数就是中间两个的任意一个,画个数轴就可以证明。

                          second:注意和的范围是超出乐 int 整型,在一些转换中,要注意强制转换。

View Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#define size 100010
#define LL long long
using namespace std;
int s[size];   //排序的后的数
int val[20][size];    //记录第k层当前位子的元素值
int num[20][size];    //记录元素所在区间的当前位置之前进入左孩子的个数
LL sum[20][size];    //记录比当前元素小的元素的和
LL Sum;
void build(int l,int r,int p)
{
    if(l==r)
        return ;
    int i,mid=(l+r)>>1,same;
    same=mid-l+1;
    for(i=l;i<=r;i++)
    {
        if(val[p][i] < s[mid])
            same--;
    }
    int ln=l,rn=mid+1;
    for(i=l;i<=r;i++)
    {
        if(i==l)
        {
            num[p][i]=0;
            sum[p][i]=0;
        }
        else
        {
            num[p][i]=num[p][i-1];
            sum[p][i]=sum[p][i-1];
        }
        if(val[p][i]<s[mid])
        {
            num[p][i]++;
            sum[p][i]+=val[p][i];
            val[p+1][ln++]=val[p][i];
        }
        else if(val[p][i]>s[mid])
            val[p+1][rn++]=val[p][i];
        else
        {
            if(same>0)
            {
                same--;
                num[p][i]++;
                sum[p][i]+=val[p][i];
                val[p+1][ln++]=val[p][i];
            }
            else
                val[p+1][rn++]=val[p][i];
        }
    }
    build(l,mid,p+1);
    build(mid+1,r,p+1);
}

int query(int li,int ri,int k,int l,int r,int p)
{
    if(l==r)
        return val[p][li];
    int mid=(l+r)>>1;
    int w1=num[p][ri],w2=0,b1,b2;
    LL w3=sum[p][ri];
    if(li>l)
    {
        w2=num[p][li-1];
        w1-=w2;
        w3-=sum[p][li-1];
    }
    if(w1>=k)
        return query(l+w2,l+w2+w1-1,k,l,mid,p+1);
    else
    {
        b1=li-l-w2;
        b2=ri-li+1-w1;
        Sum+=w3;
        return query(mid+b1+1,mid+b1+b2,k-w1,mid+1,r,p+1);
    }
}
int main()
{
    int t,n,q,i,k;
    int l,r;
    LL ans;
    scanf("%d",&t);
    for(int as=1;as<=t;as++)
    {
        scanf("%d",&n);
        sum[0][0]=0;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&s[i]);
            val[1][i]=s[i];
            sum[0][i]=s[i]+sum[0][i-1];
        }
        sort(s+1,s+n+1);
        build(1,n,1);
        scanf("%d",&q);
        printf("Case #%d:\n",as);
        for(i=0;i<q;i++)
        {
            Sum=0;
            scanf("%d%d",&l,&r);
            l++;r++;k=(r-l)/2+1;
            LL a=(LL)query(l,r,k,1,n,1);
            ans=a*(k-1)-Sum;
            ans+=(sum[0][r]-sum[0][l-1])-Sum-a-a*(r-l+1-k);
            printf("%I64d\n",ans);
        }
        printf("\n");
    }
    return 0;
}

 

(4)hdu  4417 Super Mario

划分树+二分查找  (此题还可以用线段树求解,见线段树专题)

View Code
#include<stdio.h>
#include<algorithm>
#define size 100005
using namespace std;
int s[size];   //排序的后的数
int val[25][size];    //记录第k层当前位子的元素值
int num[25][size];    //记录元素所在区间的当前位置之前进入左孩子的个数

void build(int l,int r,int p)
{
    if(l==r)
        return ;
    int i,mid=(l+r)>>1,same;
    same=mid-l+1;
    for(i=l;i<=r;i++)
    {
        if(val[p][i] < s[mid])
            same--;
    }
    int ln=l,rn=mid+1;
    for(i=l;i<=r;i++)
    {
        if(i==l)
            num[p][i]=0;
        else
            num[p][i]=num[p][i-1];
        if(val[p][i]<s[mid])
        {
            num[p][i]++;
            val[p+1][ln++]=val[p][i];
        }
        else if(val[p][i]>s[mid])
            val[p+1][rn++]=val[p][i];
        else
        {
            if(same>0)
            {
                same--;
                num[p][i]++;
                val[p+1][ln++]=val[p][i];
            }
            else
                val[p+1][rn++]=val[p][i];
        }
    }
    build(l,mid,p+1);
    build(mid+1,r,p+1);
}

int query(int li,int ri,int k,int l,int r,int p)
{
//    if(li>ri)  return 0;
    if(l==r)
        return val[p][li];
    int mid=(l+r)>>1;
    int w1=num[p][ri],w2=0,b1,b2;
    if(li>l)
    {
        w2=num[p][li-1];
        w1-=w2;
    }
    if(w1>=k)
        return query(l+w2,l+w2+w1-1,k,l,mid,p+1);
    else
    {
        b1=li-l-w2;
        b2=ri-li+1-w1;
        return query(mid+b1+1,mid+b1+b2,k-w1,mid+1,r,p+1);
    }
}
void find(int l,int r,int h,int n)
{
    int low=1,high=r-l+1,a;
    while(low<=high)
    {
        int m=(low+high)>>1;
        a=query(l,r,m,1,n,1);
        if(a<=h)
            low=m+1;
        else
            high=m-1;
    }
    printf("%d\n",low-1);
}
int main()
{
    int t,n,m,i;
    int l,r,h;
    scanf("%d",&t);
    for(int as=1;as<=t;as++)
    {
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&s[i]);
            val[1][i]=s[i];
        }
        sort(s+1,s+n+1);
        build(1,n,1);
        printf("Case %d:\n",as);
        while(m--)
        {
            scanf("%d%d%d",&l,&r,&h);
            l++;r++;
            if(query(l,r,r-l+1,1,n,1)<=h)
                printf("%d\n",r-l+1);
            else if(query(l,r,1,1,n,1)>h)
                printf("0\n");
            else
                find(l,r,h,n);
        }
    }
    return 0;
}

 

还有:hdu 4251

posted @ 2012-09-27 16:10  feng_linxu  Views(182)  Comments(0)    收藏  举报