poj2823 线段树模板题题解

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

给你一组数字,滚动窗口,每次只能看见几个数字,(每次左边出去一个,右边进来一个)求每次的最大数字和最小数字,做了三遍啊啊啊啊。。。。


第一想法比较暴力,判断上一次是不是第一个值是最大的或者最小的,如果是,就重新比较,不是就拿上次的最大值或者最小值和最新加入窗口的值比较。

代码如下(超时) 如果是noj应该可以过23333~~~~

#include <iostream>
#include <cstdio>
#include <queue>
#define maxnum 1000
int curmax,curmin,Max[maxnum],Min[maxnum];
using namespace std;

int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    int a[maxnum];
    for (int i = 0 ; i < n ; i++)
        scanf("%d",&a[i]);

    int i = 0, j = 0,maxn = -maxnum , minn = maxnum;

    for (int i = 0 ; i < k; i++)
    {
        if(a[i] > maxn)
        {
            Max[0] = maxn = a[i];
            curmax = i;
        }

        if(a[i] < minn)
        {
            Min[0] = minn = a[i];
            curmin = i;
        }
    }

    for ( i = 1 ; i < n-k+1 ; i++)
    {
           Max[i] = (curmax == i -1 ) ? -maxnum : Max[i-1];
           Min[i] = (curmin == i -1 ) ? maxnum : Min[i-1];

            for ( j = (curmax == i -1 ) ? i : i+k-1 ; j < i+k ; j++ )
            {
                if(a[j] > Max[i])
                {
                    Max[i] = a[j];
                    curmax = j;
                }
            }
            for ( j = (curmin == i -1 ) ? i : i+k-1 ; j < i+k ; j++ )
            {
                if(a[j] < Min[i])
                {
                    Min[i] = a[j];
                    curmin = j;
                }

            }
    }

    for (int i = 0 ; i < n-k+1 ;i++)
        printf("%d%c",Min[i],(i == n-k )?'\n':' ');
    for (int i = 0 ; i < n-k+1 ;i++)
        printf("%d%c",Max[i],(i == n-k )?'\n':' ');

    return 0;
}
 

比赛的时候就放过去了,没再做。。。后来想到优先队列,不过我写的会超时,有大神的不会。。。。比赛查重到了两个人贴了同一个大神的呵呵呵

我想的是存pair,第一个值是本身数值,第二个是他所在的第一位,这样可以判断他是不是被t出去的那个最左边的值,在复杂度上降了一级,代码如下,但是还是超时了,虽然说优先队列可以做,但是我还没想出来最好的方法,没看别人题解呢。。。

#include <iostream>
#include <cstdio>
#include <queue>
#include <utility>
#include <functional>
#include <deque>
#define maxn 1000005

using namespace std;

typedef pair<int, int> Pii;
priority_queue<Pii> Max;
priority_queue<Pii, deque<Pii>, greater<Pii> > Min;
int minVal[maxn], maxVal[maxn], num[maxn];

void print(int *s, int n)
{
    for(int i = 0; i < n; i++)
    {
        if(i) printf(" ");
        printf("%d", s[i]);
    }
    printf("\n");
}

int main()
{

    #define _PUSH_ Max.push(make_pair(num[i], i));\
        Min.push(make_pair(num[i], i));

    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 0; i < k - 1; i++)
    {
        scanf("%d", num + i);
        _PUSH_
    }
    for(int i = k - 1; i < n; i++)
    {
        scanf("%d", num + i);
        _PUSH_
        while(Max.top().second < i - k + 1) Max.pop();
        while(Min.top().second < i - k + 1) Min.pop();
        minVal[i - k + 1] = Min.top().first;
        maxVal[i - k + 1] = Max.top().first;
    }
    print(minVal , n - k + 1);
    print(maxVal, n - k + 1);
    return 0;
}

其实是道线段树的模板题,但是入坑时间短,没实现过,只知道大概思路,线段树分区间上很厉害,不管你要几个数的最大值最小值或者平均值(自己在结构体里加)都可以轻松拿出来。。。建立的核心代码:


struct tree
{
    int lp,rp,minvalue,maxvalue;
    int getmid()
    {
        return (lp + rp) / 2;
    }
} tree[maxnum * 4];


int num[maxnum];   // 往num里面填数字,构建的树的模型,真正的数字要填进去


void build_tree(int left,int right ,int pos)
{
    tree[pos].lp = left;
    tree[pos].rp = right;
    int mid = tree[pos].getmid();
    if(right == left)
    {
        //left和right现在是一样的,最下层的树叶每个里面区间长度为1
        tree[pos].maxvalue = tree[pos].minvalue = num[left]; //只在最下面那层填数字
        return;
    }
    build_tree(left,mid,2*pos); //左子树
    build_tree(mid+1,right,pos*2+1) //右子树
    
    //要填进去这个区间的最大值最小值需要先递归到他的下一层,即将区间一分为二
    tree[pos].minvalue = min(tree[pos*2].minvalue,tree[pos*2+1].minvalue); 
    tree[pos].maxvalue = max(tree[pos*2].maxvalue,tree[pos*2+1].maxvalue);
}


注释部分应该蛮清晰的吧...ac代码:


#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxnum = 1000010;

struct ans
{
    int minans,maxans;

} aa[maxnum];

struct tree
{
    int lp,rp,minvalue,maxvalue;
    int getmid()
    {
        return (lp + rp) / 2;
    }
} tree[maxnum * 4];

int num[maxnum];   // 往num里面填数字,构建的树的模型,真正的数字要填进去

void build_tree(int left,int right ,int pos)
{
    tree[pos].lp = left;
    tree[pos].rp = right;
    int mid = tree[pos].getmid();
    if(right == left)
    {
        //left和right现在是一样的,最下层的树叶每个里面区间长度为1
        tree[pos].maxvalue = tree[pos].minvalue = num[left]; //只在最下面那层填数字
        return;
    }
    build_tree(left,mid,2*pos); //左子树
    build_tree(mid+1,right,pos*2+1) ;//右子树

    //要填进去这个区间的最大值最小值需要先递归到他的下一层,即将区间一分为二
    tree[pos].minvalue = min(tree[pos*2].minvalue,tree[pos*2+1].minvalue);
    tree[pos].maxvalue = max(tree[pos*2].maxvalue,tree[pos*2+1].maxvalue);
}

int query(int lp,int rp,int pos,int id)
{
    if(tree[pos].lp == lp && tree[pos].rp == rp)
    {
        if(id == 0)
            return tree[pos].minvalue;
        else
            return tree[pos].maxvalue;
    }

    int mid = tree[pos].getmid();
    if(mid >= rp)
        return query(lp,rp,pos*2,id);
    else if(mid < lp)
        return query(lp,rp,pos*2+1,id);
    else
    {
        if(id == 0)
            return min(query(lp,mid,pos*2,id),query(mid+1,rp,pos*2+1,id));
        else
            return max(query(lp,mid,pos*2,id),query(mid+1,rp,pos*2+1,id));
    }
}

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m) != EOF)
    {
        for(int i = 1; i <= n; ++i)
            scanf("%d",&num[i]);
        build_tree(1,n,1);
        for(int i = 1; i <= n - m + 1; ++i)
        {
            int j = i + m - 1;
            aa[i].minans = query(i,j,1,0);
            aa[i].maxans = query(i,j,1,1);
        }

        for(int i = 1; i <= n - m + 1; i++)
            printf("%d%c",aa[i].minans,(i == n - m + 1) ? '\n':' ');

        for(int i = 1; i <= n - m + 1; i++)
            printf("%d%c",aa[i].maxans,(i == n - m + 1) ? '\n':' ');
    }
    return 0;
}


你可能感兴趣的:(poj2823 线段树模板题题解)