[BZOJ]3439: Kpm的MC密码 trie树+主席树(线段树合并)

Description

背景
想Kpm当年为了防止别人随便进入他的MC,给他的PC设了各种奇怪的密码和验证问题(不要问我他是怎么设的。。。),于是乎,他现在理所当然地忘记了密码,只能来解答那些神奇的身份验证问题了。。。

描述
Kpm当年设下的问题是这样的:
现在定义这么一个概念,如果字符串s是字符串c的一个后缀,那么我们称c是s的一个kpm串。
系统将随机生成n个由a…z组成的字符串,由1…n编号(s1,s2…,sn),然后将它们按序告诉你,接下来会给你n个数字,分别为k1…kn,对于每一个ki,要求你求出列出的n个字符串中所有是si的kpm串的字符串的编号中第ki小的数,如果不存在第ki小的数,则用-1代替。(比如说给出的字符串是cd,abcd,bcd,此时k1=2,那么”cd”的kpm串有”cd”,”abcd”,”bcd”,编号分别为1,2,3其中第2小的编号就是2)(PS:如果你能在相当快的时间里回答完所有n个ki的查询,那么你就可以成功帮kpm进入MC啦~~)

题解:

这种题目都没有1A……我真是菜……容易想到把字符串反过来建字典树,然后每个查询就可以转化为在一颗子树中找第k小的值,那么容易想到dfs序+主席树,直接上就可以了。注意重复字符串的处理,我就是因为这个失去了1A……

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int Maxn=100010;
const int Maxl=300010;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
vector<int>m1[Maxl];
int n,m2[Maxn];
int root[Maxl],lc[6000000],rc[6000000],s[6000000],tot2=0;
int son[Maxl][27],tot1=0;
string str[Maxn];
struct Edge{int y,next;}e[Maxl];
int last[Maxl],len=0;
void ins(int x,int y){int t=++len;e[t].y=y;e[t].next=last[x];last[x]=t;}
void build(int o)
{
    int now=0;
    for(int i=0;i<str[o].size();i++)
    {
        int x=str[o][i]-'a';
        if(!son[now][x])son[now][x]=++tot1,ins(now,tot1);
        now=son[now][x];
    }
    m1[now].push_back(o);
    m2[o]=now;
}
void Ins(int &u,int l,int r,int p)
{
    if(!u)u=++tot2;
    s[u]++;if(l==r)return;
    int mid=l+r>>1;
    if(p<=mid)Ins(lc[u],l,mid,p);else Ins(rc[u],mid+1,r,p);
}
void merge(int &u1,int u2)
{
    if(!u1){u1=u2;return;}
    if(!u2)return;
    s[u1]+=s[u2];
    merge(lc[u1],lc[u2]);merge(rc[u1],rc[u2]);
}
int query(int rt1,int rt2,int l,int r,int k)//第k小
{
    if(s[rt1]-s[rt2]<k)return -1;
    if(l==r)return l;
    int c=s[lc[rt1]]-s[lc[rt2]],mid=l+r>>1;
    if(k<=c)return query(lc[rt1],lc[rt2],l,mid,k);
    else return query(rc[rt1],rc[rt2],mid+1,r,k-c);
}
int in[Maxl],out[Maxl],dfn=0;
void dfs(int x)
{
    in[x]=++dfn;
    if(m1[x].size())
    for(int i=0;i<m1[x].size();i++)Ins(root[in[x]],1,n,m1[x][i]);
    for(int i=last[x];i;i=e[i].next)dfs(e[i].y);
    out[x]=dfn;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        cin>>str[i];
        reverse(str[i].begin(),str[i].end());
        build(i);
    }
    dfs(0);root[0]=0;
    for(int i=1;i<=dfn;i++)merge(root[i],root[i-1]);
    for(int i=1;i<=n;i++)
    {
        int k=read();
        printf("%d\n",query(root[out[m2[i]]],root[in[m2[i]]-1],1,n,k));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值