曲神的hu测 T1.FaQ(可持久化Trie)

33 篇文章 0 订阅
15 篇文章 0 订阅

版权属于yhzq,想要引用此题(包括题面)的朋友请联系博主

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

分析:
原创题,题面真的。。。感觉对不起博弈。。。

yy了一种很科学的做法:

首先,我们把这些字符串扔到一棵 Trie T r i e
那么两个字符串的 lcp l c p 就是这两个字符串 ed e d 结点在 Trie T r i e 上的 lca l c a 的深度
不明白?参考一下这道题(顺便帮我刷刷访问量)

我们需要询问一个区间和一个字符串的 lcp l c p
于是就想出一种主席树套Trie的方法

每塞入一个串,我们就新建一棵 Trie T r i e
在Trie的每个结点中,不仅要记录儿子,而且要记录有多少字符串走到了ta的 x x 儿子
在处理询问的时候,我们就像主席树查询一样,锁定一个区间
假设当前字符为x,当前结点是 now n o w ,到达 now n o w 的字符串个数有 tot t o t ,有 cnt c n t 个字符串可以走到儿子 x x
那么显然有totcnt个字符串与原字符串的lcp为 deepnow d e e p n o w
这样我们就可以做到 O(n) O ( n ) 匹配,完成询问了

不过这样有一个缺陷:需要的空间大
所以在题目要求下,只能得到80%

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define ll long long

using namespace std;

const ll p=998244353;
const int N=1000003;
int n,m,sz=0,root[N],delta;
struct node{
    int ch[26],cnt[26];
}t[N];
char ss[N];
vector<int> s[N];
ll ans;

void insert(int &now,int bh,int l,int r) {
    sz++;
    t[sz]=t[now];
    now=sz;
    int x=s[bh][l];
    t[now].cnt[x]++;
    if (l==r) return;
    insert(t[now].ch[x],bh,l+1,r);
}

void solve(int l,int r,int bh,int st,int ed,int dep) {
    int x=s[bh][st];
    int tmp=t[r].cnt[x]-t[l].cnt[x;
    if (!tmp) {
        if (delta>0) 
            ans=(ans+(ll)(delta*(dep-1)))%p;
        return;
    }
    if (delta==-1) delta=tmp;
    else if (delta>tmp) {
        ans=(ans+(ll)(delta-tmp)*(dep-1))%p;
        delta=tmp;
    }
    if (st==ed) {
        ans=(ans+(ll)tmp*dep)%p;
        return;
    }
    solve(t[l].ch[x],t[r].ch[x],bh,st+1,ed,dep+1);
}

int main()
{
    //freopen("faq.in","r",stdin);
    //freopen("faq.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        scanf("%s",ss);
        int len=strlen(ss);
        for (int j=0;j<len;j++) s[i].push_back(ss[j]-'a');
        root[i]=root[i-1];
        insert(root[i],i,0,len-1);
    }
    for (int i=1;i<=m;i++) {
        int l,r,x;
        scanf("%d%d%d",&l,&r,&x);
        l--;
        delta=-1; ans=0;
        int len=s[x].size();
        solve(root[l],root[r],x,0,len-1,1);
        printf("%lld\n",ans%p);
    }
    return 0;
}

于是我就开始考虑优化
每个结点只记录有多少字符串使用了ta
这样 insert i n s e r t solve s o l v e 都有一点小变化(还是比较好理解的)
不炸内存,成功卡进 0.5s 0.5 s
这里写图片描述

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define ll long long

using namespace std;

const ll p=998244353;
const int N=1000003;
int n,m,sz=0,root[N],delta;
struct node{
    int ch[26],cnt;
}t[N*2];
char ss[N];
vector<int> s[N];
ll ans;

void insert(int &now,int bh,int l,int r) {
    sz++;
    t[sz]=t[now];
    now=sz;
    t[now].cnt++;
    if (l>r) return;
    int x=s[bh][l];
    insert(t[now].ch[x],bh,l+1,r);
}

void solve(int l,int r,int bh,int st,int ed,int dep) {
    int tmp=t[r].cnt-t[l].cnt; 
    if (!tmp) {
        if (delta>0) 
            ans=(ans+(ll)(delta*(dep-1)))%p;
        return;
    }
    if (delta>tmp) {
        ans=(ans+(ll)(delta-tmp)*(dep-1))%p;
        delta=tmp;
    }
    if (st==ed) {
        ans=(ans+(ll)tmp*dep)%p;
        return;
    }
    int x=s[bh][st+1];
    solve(t[l].ch[x],t[r].ch[x],bh,st+1,ed,dep+1);
}

int main()
{
    //freopen("faq.in","r",stdin);
    //freopen("faq.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        scanf("%s",ss);
        int len=strlen(ss);
        for (int j=0;j<len;j++) s[i].push_back(ss[j]-'a');
        root[i]=root[i-1];
        insert(root[i],i,0,len-1);
    }
    for (int i=1;i<=m;i++) {
        int l,r,x;
        scanf("%d%d%d",&l,&r,&x);
        l--;
        delta=r-l; ans=0;
        int len=s[x].size();
        int c=s[x][0];
        solve(t[root[l]].ch[c],t[root[r]].ch[c],x,0,len-1,1);
        printf("%lld\n",ans%p);
    }
    return 0;
}
正解?

还是利用 Trie T r i e 树的性质:两个串的 lcp l c p ed e d 结点 lca l c a 的深度
这样题目就转化为给出一个区间和一个点,求这个点和这个区间的点的lca的深度和
考虑如何解决这个问题:
可以把这个区间所有的点到根上的路径+1,找出查询点到根的和,就是答案
这里写图片描述

可以把所有的查询拆成 [1,l1] [ 1 , l − 1 ] [1,r] [ 1 , r ] ,减一下就好了
于是把所有的查询离线下来,拆开再排序,从 1n 1 − n 依次将点到根的路径加一,再查询即可
链剖 O(mlog2n) O ( m l o g 2 n ) ,LCT O(mlogn) O ( m l o g n )
std写了一个LCT,最慢的点 0.25s 0.25 s −

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值