线段树+KMP-hdu-4125-Moles

本文详细介绍了如何使用线段树和KMP算法解决HDU 4125 Moles的问题。首先,通过平衡二叉树的性质,将问题转化为寻找特定节点之前的最大值和最小值。线段树在此过程中用于高效地维护这些值。然后,根据先序遍历生成序列res,并利用KMP算法计算给定01序列s在res中的出现次数,考虑到可能的重叠情况。
摘要由CSDN通过智能技术生成

题目链接:

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

题目意思:

给1~n组成的n个数,按输入顺序插入到平衡二叉树中,现在按1~n从小到大的顺序从根开始访问节点,每遇到一个节点i,如果i为奇数则序列+‘1’,如果i为偶数则序列+‘0’,这样得到一个序列res,给一个01序列s,求s在res中出现的次数,可以重叠。

解题思路:

首先要明白平衡二叉树的三个性质:

1、对于新插入的节点,其父亲要么是比自己大的最小的那个,或比自己小的最大的那个。

2、先序遍历为从小到大的顺序。

3、先序遍历回到根节点一共要访问2*n-1次节点。因为有n-1条边,一共有2*n-2个度,对于每个节点来说它的度数即为它的访问次数,而根节点进来的时候要加一个度,所以就是2*n-1次。

问题就转化成了,给一个数,求出在它之前的数序列中小于它的最大值或大于它的最小值。而这个问题可以用线段树维护,每个区间维护该区间已经放了数的最大值和最小值,如果查小于va的最大值,如果va>mid,则直接返回max(左区间的最大值,查右边)。同理查询大于va的最小值。

这个问题解决后,就是按先序顺序遍历,求出走的序列res.

最后KMP求出s在res中出现的次数,因为可以重复,所有每找到一个完全匹配串后,模式串下标跳到其的next值处。

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#include<ctime>
#define eps 1e-6
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define Maxn 610000

int n;
struct Inte
{
    int mi,ma; //线段树维护区间的最大值和最小值
}inte[Maxn*4];

struct Node //静态存储,左右孩子节点
{
    int l,r;
}node[Maxn];

void pushup(int v) //向上更新
{
    inte[v].mi=min(inte[v<<1].mi,inte[v<<1|1].mi);
    inte[v].ma=max(inte[v<<1].ma,inte[v<<1|1].ma);
}

void build(int l,int r,int rt)
{
    if(l==r) //建树
    {
        inte[rt].mi=INF; //把最小值置为无穷大,表示不存在
        inte[rt].ma=0;  //把最大值置为0
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}
void update(int l,int r,int rt,int va) //单点更新
{
    if(l==r)
    {
        inte[rt].mi=inte[rt].ma=va;
        return ;
    }
    int m=(l+r)>>1;
    if(va<=m)
        update(lson,va);
    else
        update(rson,va);
    pushup(rt);
}
int query1(int l,int r,int rt,int va) //查询小于va的最大值
{
   if(l==r)
        return inte[rt].ma;
    int m=(l+r)>>1;
    if(va<=m) //不需要查询右区间了
      return query1(lson,va);
    else    //左区间的直接返回后比较
        return max(inte[rt<<1].ma,query1(rson,va));
}
int query2(int l,int r,int rt,int va)//查询大于va的最小值
{
    if(l==r)
        return inte[rt].mi;
    int m=(l+r)>>1;
    if(va<=m)
        return min(inte[rt<<1|1].mi,query2(lson,va));
    else
        return query2(rson,va);
}
char res[Maxn<<1];
int s,cnt;
char bb[7500];

void dfs(int cur) //按先序遍历搜
{
    res[++cnt]=(cur&1)?'1':'0'; //走到改点
    if(node[cur].l) //如果有左儿子
    {
        dfs(node[cur].l);
        if(node[cur].r) //如果有右儿子
        {
            res[++cnt]=(cur&1)?'1':'0';//访问完左子树,回到它自己
            dfs(node[cur].r); //再访问右子树
        }
        res[++cnt]=(cur&1)?'1':'0';//回到自己
        return ;
    }
    if(node[cur].r) //只有右子树
    {
        dfs(node[cur].r);
        res[++cnt]=(cur&1)?'1':'0'; //访问完右子树后回到自己
    }
}
int next[7500],nn;

void getnext() //模式串求next数组
{
    int j=0;

    next[1]=0;
    for(int i=2;i<=nn;i++)
    {
        while(j>0&&bb[j+1]-bb[i])
            j=next[j];
        if(bb[j+1]==bb[i])
            j++;
        next[i]=j;
    }
    return ;
}
int KMP() //
{
    int j=0;
    int ans=0;

    for(int i=1;i<=cnt;i++)
    {
        while(j>0&&bb[j+1]-res[i])
            j=next[j];
        if(bb[j+1]==res[i])
            j++;
        if(j==nn)
        {
            ans++;
            j=next[j];
        }
    }
    return ans;
}
int main()
{
    int t;

    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%d",&n);
        build(1,n,1); //建树
        scanf("%d",&s);
        update(1,n,1,s);//插到叶子
        cnt=0;
        memset(node,0,sizeof(node));
        for(int i=2;i<=n;i++)
        {
            int a,al,ar;
            scanf("%d",&a);
            al=query1(1,n,1,a); //小于a的最大值
            ar=query2(1,n,1,a); //大于a的最小值
            if(!al) //不存在小于a的最大值
                node[ar].l=a; //那么只能是以大于a的最小值作为父亲
            else if(ar==INF)  //不存在自己作为左儿子的父亲
                node[al].r=a; //那么只可能是自己是右儿子的父亲
            else
            {
                if(node[al].r) //已经有了
                    node[ar].l=a;
                else
                    node[al].r=a;
            }
            update(1,n,1,a);//加到线段树中
        }
     /*   for(int i=1;i<=n;i++)
            printf("i:%d l:%d r:%d\n",i,node[i].l,node[i].r);*/
        cnt=0;
        dfs(s);
        scanf("%s",bb+1);
        nn=strlen(bb+1);
        getnext();
        int ans=KMP();
        printf("Case #%d: %d\n",ca,ans);

    }
   return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值