线段树一

下面的题目都是更新点查找区间或者是更新区间查找点的题目,这一类属于比较简单的线段树的题目,可以用来入门或者加强对线段树的理解。

后面几道稍微有点难想到,但是想到怎么使用线段树之后就是简单的是用线段树了。


HDU 1754

http://acm.hdu.edu.cn/showproblem.php?pid=1754

简单的更新点的值,查找区间的最大值,很好写。


HDU 1394

http://acm.hdu.edu.cn/showproblem.php?pid=1394

简单的更新点的值,查找区间的和值,也很好写。


HDU 2795

http://acm.hdu.edu.cn/showproblem.php?pid=2795

查找区间内大于给定值的最小的下标,灵活的使用线段树求解区间最大值。这道题倒是可以看成是线段树的变形题了,感觉很好的题目。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#define MAX 200020
#define lson l,m,k<<1
#define rson m+1,r,k<<1|1
using namespace std;
int seg[MAX<<2];
int h,w,n;
void Init(int l,int r,int k)
{
    seg[k]=w;
    if(l==r)
        return ;
    int m = (l+r)>>1;
    Init(lson);
    Init(rson);
}

void PushUp(int rt)
{
    seg[rt]=max(seg[rt<<1],seg[rt<<1|1]);
}

int query(int x,int l,int r,int k) {
	if (l == r) {
		seg[k] -= x;
		return l;
	}
	int m = (l + r) >> 1;
	int ret;
	if(seg[k<<1]>=x)
        ret = query(x,lson);
    else
        ret = query(x,rson);
	PushUp(k);
	return ret;
}
int main()
{
    while(scanf("%d%d%d",&h,&w,&n)!=EOF)
    {
        if(h>n)
            h=n;
        Init(1,h,1);
        while(n--)
        {
            int t;
            scanf("%d",&t);
            if(seg[1]<t)
                printf("-1\n");
            else
                printf("%d\n",query(t,1,h,1));
        }
    }
    return 0;
}

POJ 2828 

http://poj.org/problem?id=2828

这道题也是线段树的灵活应用,但是前提是要先想到如何使用线段树解决这个问题。

我们来分析插队的情况,如果一个人插的位置是j,那么表示该人插的时候前面已经有了j个人;又由于插队,在前面有相同个数人的时候,是后插的人排在前面;因此我们从输入的序列的后面向前确定每一个人的位置。

比如a[i]=j,那么我们要找到最小的下标使得[1,x]这个区间的空位正好是j+1个,那么x就是这个人的正确的位置。

这样我们用线段树表示一个区间的空闲的位置数,也就是还没有被占领的位置,那么每次查找相应的需要的空位个数的最小的下标就是这个人所应该站的位置。

我想应该说的比较清楚了吧,还不清楚就看代码,是吧更新放在了Query中。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#define lson l,m,k<<1
#define rson m+1,r,k<<1|1
#define MAX 200020
using namespace std;
typedef struct NODE
{
    int p,v;
}Node;
Node node[MAX];
int seg[MAX<<2];

void PushUp(int k)
{
    seg[k]=seg[k<<1]+seg[k<<1|1];
}

void Init(int l,int r,int k)
{
    if(l==r)
    {
        seg[k]=1;
        return;
    }
    int m = (l+r)>>1;
    Init(lson);
    Init(rson);
    PushUp(k);
}

int Query(int v,int l,int r,int k)
{
    if(l==r)
    {
        seg[k]--;
        return l;
    }
    int m = (l+r)>>1;
    int res;
    if(seg[k<<1]>=v)
        res = Query(v,lson);
    else
        res = Query(v-seg[k<<1],rson);
    PushUp(k);
    return res;
}

bool cmp(Node a,Node b)
{
    if(a.p<b.p)
        return true;
    else
        return false;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&node[i].p,&node[i].v);
            ++node[i].p;
        }
        Init(1,n,1);
        for(int i=n;i>=1;i--)
        {
            int t = Query(node[i].p,1,n,1);
            node[i].p=t;
        }
        sort(node+1,node+n+1,cmp);
        for(int i=1;i<=n;i++)
            printf("%d%c",node[i].v,i==n?'\n':' ');
    }
    return 0;
}

POJ 2886 更新点查找区间的和值

http://poj.org/problem?id=2886

这道题的关键是在每次删除一个人之后快速找到该人的号码指定的人。

我们知道剩下的人的个数,然后从该人往前或者往后的处理,都有一个取模的过程,注意取模的时候为零的时候特殊处理,这样就行了。

还有就是快速求一个不大于n的因数个数最多的数,也就是反素数,使用的是别人的反素数表。为什么是对的也很容易理解,从当前的反素数到下一个反素数之间的所有数的约数都是小于等于这个反素数的约数个数的,所以反素数一定是一个范围内最早出现的约数个数最多的数。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#define MAX 500020
#define lson l,m,k<<1
#define rson m+1,r,k<<1|1
using namespace std;
typedef struct NODE
{
    char name[12];
    int t;
}Node;
Node node[MAX];
int seg[MAX<<2];

void PushUp(int k)
{
    seg[k]=seg[k<<1]+seg[k<<1|1];
}

int id;

void Init(int l,int r,int k)
{
    if(l==r)
    {
        if(l==id)
            seg[k]=0;
        else
            seg[k]=1;
        return ;
    }
    int m = (l+r)>>1;
    Init(lson);
    Init(rson);
    PushUp(k);
}

int Query1(int ll,int rr,int l,int r,int k)
{
    if(ll>rr)
        return 0;
    if(ll==l&&rr==r)
        return seg[k];
    int m = (l+r)>>1;
    if(rr<=m)
        return Query1(ll,rr,lson);
    else if(ll>m)
        return Query1(ll,rr,rson);
    else
        return Query1(ll,m,lson)+Query1(m+1,rr,rson);
}

int Query2(int v,int l,int r,int k)
{
    if(l==r)
    {
        seg[k]--;
        return l;
    }
    int m = (l+r)>>1;
    int ans ;
    if(seg[k<<1]>=v)
        ans = Query2(v,lson);
    else
        ans = Query2(v-seg[k<<1],rson);
    PushUp(k);
    return ans;
}

int mod(int a,int b)
{
    a %= b;
    a = (a+b)%b;
    if(a==0)
        return b;
    else
        return a;
}

const int antiprime[]={1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,
1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,
45360,50400,55440,83160,110880,166320,221760,277200,
332640,498960,554400,665280};// 反质数表
const int factorNum[]={1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,64,72,80,84,
90,96,100,108,120,128,144,160,168,180,192,200,216,224};// 对应的约数个数

int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        id = k;
        Init(1,n,1);
        for(int i=1;i<=n;i++)
            scanf("%s%d",node[i].name,&node[i].t);
        int left = n-1;
        int index = k;
        int ans=1,ansIndex=1;

        int cnt=0;
        while(cnt<35 && antiprime[cnt]<=n)    cnt++;
        cnt--;

        for(int i=1;i<antiprime[cnt];i++)
        {
            if(node[index].t>0)
            {
                k = node[index].t+Query1(1,index-1,1,n,1);
                k = mod(k,left);
                index = Query2(k,1,n,1);
            }
            else
            {
                k = 1+node[index].t+Query1(1,index-1,1,n,1);
                k = mod(k,left);
                index = Query2(k,1,n,1);
            }

            left--;
        }
        printf("%s %d\n",node[index].name,factorNum[cnt]);
    }
    return 0;
}


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值