版权属于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
儿子
在处理询问的时候,我们就像主席树查询一样,锁定一个区间
假设当前字符为,当前结点是
now
n
o
w
,到达
now
n
o
w
的字符串个数有
tot
t
o
t
,有
cnt
c
n
t
个字符串可以走到儿子
x
x
那么显然有个字符串与原字符串的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,l−1]
[
1
,
l
−
1
]
和
[1,r]
[
1
,
r
]
,减一下就好了
于是把所有的查询离线下来,拆开再排序,从
1−n
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
−