题目链接:
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;
}