BZOJ4713

题目大意:有 \(n\) 个点 \(n-1\) 条边,每条边有一个字符。给你 \(m\) 个字符串 \(s_i\),问每个字符串是否可以通过树上的一条简单路径表示。

\(n,m\le 30000,\sum |s_i|\le 30000\)

题解

一眼可以看出是点分治,因为路径可以看作点对,判断一个字符串是否出现使用hash即可(为了防止被卡,可能要模数奇怪点,或者多模)。

因为询问 \(m\) 很大,我们不能每次对于每个子树都枚举询问。具体来说对于每个分治重心,将子树里的所有点到重心的路径字符串hash值加入hash表,然后如果一个值分别被从两个不同的子树加入,那么可以知道这个值不管如何都可以和别的任何可行的值进行配对,因为它总能至少有一个与别的值位于不同子树的位置。做完这些就枚举询问,将每个询问的字符串拆分成两段求hash值,看是否都出现且不位于相同子树。然后得到答案的询问可以用链表删除来卡常。

然后发现这样还是有点问题,算一下复杂度是 \(O(n\log n+nm)\) 显然寄了。

发现如果我们对询问建立trie树,然后dfs每个点,并以这个点扩展的同时按照树上的边权跳trie树,那么这样可得到一个与 \(m\) 无关的 \(O(n^2)\) 的暴力。可以优化一下,如果当前trie下的字符串被算过了,就删除,然后减小trie树上每个点维护的 \(sz\) ,发现 \(sz=0\) 就直接退出以此剪枝。总之复杂度小于 \(O(n^2)\) 。

可能你疑惑我为什么讲一个暴力做法?其实因为这个暴力复杂度几乎与 \(m\) 无关,那么可以考虑根号分治。对于点分治下的子树 \(sz\ge \sqrt n\) 的,我们考虑点分治枚举询问做,因为分治子树大小一小于 \(\sqrt n\),就直接跑暴力去了,由 \(\frac{n}{2^k}\le \sqrt n\) ,得 \(\log\sqrt n\le k\),可知复杂度为 \(O(n\log\sqrt n+\sqrt nm)\)。

而对于分治子树 \(sz<\sqrt n\) 的,我们最坏复杂度 \(O(n)\)。综上总复杂度 \(O(n\log\sqrt n+\sqrt nm+n)\) ,足以通过此题。

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
inline bool blank(char c){
return c==' ' or c=='\t' or c=='\n' or c=='\r' or c==EOF;
}
inline void gs(std::string &s){
s+='#';char ch=gc();
while(blank(ch) ) ch=gc();
while(!blank(ch) ){
s+=ch;ch=gc();
}
}
};
using IO::read;
using IO::gs;
const int N=3e4+3;
int n;
std::vector<int>head,nxt;
struct Edge{
int u,v;
char w;
};std::vector<Edge>to;
inline void join(int u,int v,char w){
nxt.push_back(head[u]);
head[u]=to.size();
to.push_back({u,v,w});
}
const int Sz=1e7+9;
struct pr{
ll x,x2;
};
struct Hash_map{
std::vector<int>head,nxt;
struct node{
pr x;
int val;
};
Hash_map(){
head.resize(Sz+3,-1);
};
std::vector<node>to;
inline int& operator [] (pr x){
int u=(x.x+x.x2)%Sz;
for(int i=head[u];~i;i=nxt[i]){
if(to[i].x.x==x.x and to[i].x.x2==x.x2){
return to[i].val;
}
}
nxt.push_back(head[u]);
head[u]=to.size();
to.push_back({x,0});
return to[to.size()-1].val;
}
}ex;
std::string s[N],s2[N];
int sum,rt;
int sz[N],wei[N];
bool vis[N],ans[N];
inline void getroot(int u,int f){
sz[u]=1;wei[u]=0;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(v==f or vis[v]) continue;
getroot(v,u);
sz[u]+=sz[v];
wei[u]=std::max(wei[u],sz[v]);
}
wei[u]=std::max(wei[u],sum-sz[u]);
if(!rt or wei[rt]>wei[u]){
rt=u;
}
}
const ll mod1=1e15+23,mod2=1e14+31;
const int p1=1361,p2=1009;
ll P1[N],P2[N];
std::vector<pr>cls;
inline void getdis(int u,int f,ll hs,ll hs2,int id){
int k=ex[{hs,hs2}];
if(k==id or k==0) ex[{hs,hs2}]=id;
else ex[{hs,hs2}]=-1;
cls.push_back({hs,hs2});
//fprintf(stderr,"%d %lld %lld\n",u,hs,hs2);
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(v==f or vis[v]) continue;
getdis(v,u,(1ll*hs*p1%mod1+(to[i].w-'a'+1) )%mod1,
(1ll*hs2*p2%mod2+(to[i].w-'a'+1) )%mod2,id);
}
}
int size;
struct List{
int x,r;
}p[N];
inline void Erase(int x){
p[x].x=p[p[x].r].x;
p[x].r=p[p[x].r].r;
--size;
}
int m;
inline void calc(int u){
//fprintf(stderr,"%d\n",u);
cls.clear();
int id=1;
ex[{0,0}]=id;
cls.push_back({0,0});
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(vis[v]) continue;
++id;
getdis(v,u,to[i].w-'a'+1,to[i].w-'a'+1,id);
}
for(int i=1;(i and p[i].x) and size;){
//fprintf(stderr," %d\n",p[i].x);
bool flag=0;
int len=s[p[i].x].size()-1;
ll hl=0,hr=0;
ll hl2=0,hr2=0;
for(int j=1;j<=len;++j){
hr=(1ll*hr*p1%mod1+(s[p[i].x][j]-'a'+1) )%mod1;
hr2=(1ll*hr2*p2%mod2+(s[p[i].x][j]-'a'+1) )%mod2;
}
//fprintf(stderr,"%d %d %d\n",0,hl,hr);
if(ex[{hr,hr2}]){
ans[p[i].x]=1;
//fprintf(stderr,"Kill %d\n",p[i].x);
Erase(i);flag=1;
continue;
}
for(int j=1;j<=len;++j){
hr=(hr-1ll*(s[p[i].x][j]-'a'+1)*P1[len-j]%mod1+mod1)%mod1;
hr2=(hr2-1ll*(s[p[i].x][j]-'a'+1)*P2[len-j]%mod2+mod2)%mod2;
hl=(hl+1ll*P1[j-1]*(s[p[i].x][j]-'a'+1)%mod1)%mod1;
hl2=(hl2+1ll*P2[j-1]*(s[p[i].x][j]-'a'+1)%mod2)%mod2;
//fprintf(stderr,"%d %d %d\n",j,hl,hr);
int k1=ex[{hl,hl2}],k2=ex[{hr,hr2}];
//fprintf(stderr,"%d %d\n",k1,k2);
if(k1 and k2 and (k1==-1 or k2==-1 or k1!=k2) ){
ans[p[i].x]=1;
//fprintf(stderr,"Kill %d\n",p[i].x);
Erase(i);flag=1;
break;
}
}
if(!flag) i=p[i].r;
}
for(auto it:cls){
ex[it]=0;
}
}
int tot=0;
int ch[N][26];
int csz[N];
std::vector<int>val[N];
inline void ins(const std::string &s,int id){
int p=0,len=s.size()-1;
for(int i=1;i<=len;++i){
int c=s[i]-'a';
if(!ch[p][c]) ch[p][c]=++tot;
++csz[p];
p=ch[p][c];
}
++csz[p];
val[p].push_back(id);
}
int dfs2(int u,int f,int p){
int d=0;
if(!val[p].empty() ){
d+=val[p].size();
for(auto it:val[p]) ans[it]=1;
val[p].clear();
}
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v,c=to[i].w-'a';
if(v==f or vis[v]) continue;
if(ch[p][c] and csz[ch[p][c] ])
d+=dfs2(v,u,ch[p][c]);
}
csz[p]-=d;
return d;
}
void dfs1(int u,int f){
dfs2(u,u,0);
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(v==f or vis[v]) continue;
dfs1(v,u);
}
}
const int sqn=sqrt(N);
void dfs(int u){
if(sum>sqn)
calc(u);
else{
dfs1(u,u);
return;
}
vis[u]=1;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(vis[v]) continue;
rt=0;sum=sz[v];
getroot(v,u);
dfs(rt);
}
}
int main(){
filein(a);fileot(a);
read(n);
head.resize(n+3,-1);
for(int i=1;i<n;++i){
int u,v;char c;
read(u,v);
scanf("%c",&c);
join(u,v,c);join(v,u,c);
}
read(m);
for(int i=1;i<=m;++i){
gs(s[i]);
p[i].r=i+1;
p[i].x=i;
ins(s[i],i);
}
P1[0]=P2[0]=1;
for(int i=1;i<=(int)3e4;++i){
P1[i]=1ll*P1[i-1]*p1%mod1;
P2[i]=1ll*P2[i-1]*p2%mod2;
}
size=m;
sum=n;rt=0;
getroot(1,1);
dfs(rt);
for(int i=1;i<=m;++i){
if(ans[i]) printf("YES\n");
else printf("NO\n");
}
fprintf(stderr,"%dms\n",clock() );
return 0;
}

BZOJ4713 迷失的字符串 解题报告的更多相关文章

  1. 【九度OJ】题目1192:回文字符串 解题报告

    [九度OJ]题目1192:回文字符串 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1192 题目描述: 给出一个长度不超过1000的 ...

  2. 【剑指Offer】左旋转字符串 解题报告(Python)

    [剑指Offer]左旋转字符串 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interviews 题目 ...

  3. 洛谷 P3989 [SHOI2013]阶乘字符串 解题报告

    P3989 [SHOI2013]阶乘字符串 题目描述 给定一个由前\(n(\le 26)\)个小写字母组成的串\(S(|S|\le 450)\).串\(S\)是阶乘字符串当且仅当前 \(n\) 个小写 ...

  4. BZOJ4713 迷失的字符串

    分析 首先考虑只有一个串时的做法,可以进行背包dp,记\(f(i,j)\)表示从\(i\)的子树中某点出发到\(i\)能否匹配字符串的\(1 \dots j\)位且\(i\)与\(j\)匹配.同时记\ ...

  5. 【九度OJ】题目1054:字符串内排序 解题报告

    [九度OJ]题目1054:字符串内排序 解题报告 标签(空格分隔): 九度OJ [LeetCode] http://ac.jobdu.com/problem.php?pid=1054 题目描述: 输入 ...

  6. 【九度OJ】题目1206:字符串连接 解题报告

    [九度OJ]题目1206:字符串连接 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1206 题目描述: 不借用任何字符串库函数实现无 ...

  7. 【剑指Offer】字符串的排列 解题报告(Python)

    [剑指Offer]字符串的排列 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https://www.nowcoder.com/ta/coding-interviews 题 ...

  8. 【NOIP2015】提高day2解题报告

    题目: P1981跳石头 描述 一年一度的“跳石头”比赛又要开始了!这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 N ...

  9. 【第40套模拟题】【noip2011_mayan】解题报告【map】【数论】【dfs】

    目录:1.潜伏者 [map] 2.Hankson的趣味题[数论]3.mayan游戏[dfs] 题目: 1. 潜伏者(spy.pas/c/cpp)[问题描述]R 国和S 国正陷入战火之中,双方都互派间谍 ...

随机推荐

  1. 【Android开发】Android工程打压缩包技巧

    android studio的: ".gradle" "*/.gradle" ".idea" "*/.idea" &qu ...

  2. 字符串反转&说反话

    题目描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串.(字符串长度不超过1000) 输入描述: 输入N个字符 输出描述: 输出该字符串反转后的字符串 示例1 输入 abcd 输出 d ...

  3. 在 MarkDown 中添加表格(例如:在 CSDN 中添加表格)

    内容 一.使用 Markdown 创建表格(例如:在 CSDN 中创建表格) 1. 表格格式 对齐方式 -: 设置内容和标题栏居右对齐: :- 设置内容和标题栏居左对齐: :-: 设置内容和标题栏居中 ...

  4. 前端加密办法之混淆js加密

    每个页面都是有数据和页面结构以及样式组成,对于单页面的作者来说这中分离的感觉会明显一点,那当我们通过ajax请求拿到数据之后我们一般就是把数据渲染到页面,但是数据接口又不想那么明显是一个json字符串 ...

  5. GIL全局解释器锁、协程运用、IO模型

    GIL全局解释器锁 一.什么是GIL 首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C是一套语言(语法)标准,但是可以用不 ...

  6. 函数.python

    今日内容概要 名称空间 名字的查找顺序 作用域 global与nonlocal关键字 函数名对象 函数的嵌套 今日内容详细 1.名称空间 #名称空间其实就是存放变量名与变量名绑定关系的地方#分类1.内 ...

  7. Codeforces Round #710 (Div. 3) Editorial 1506A - Strange Table

    题目链接 https://codeforces.com/contest/1506/problem/A 原题 1506A - Strange Table Example input 5 1 1 1 2 ...

  8. VisualStudio安装步骤

    1.下载vs2017,点击安装 2.选择asp.net选项进行安装,如果需要其他的功能,可以选上 3.更改安装路径,尽量把文件安装在c盘以外的盘上,因为c盘是系统盘,安装的东西越多电脑会越卡.注意:不 ...

  9. 2021.12.16 eleveni的刷题记录

    2021.12.16 eleveni的刷题记录 1. 数论 https://www.luogu.com.cn/problem/P2532 1.1卡特兰数 https://www.luogu.com.c ...

  10. VMware虚拟机无法安装Win11解决方法 (暂时全网最全方案)

    目录 1.现象 1.蓝屏重启 2.如下图示,无法启动 2.解决方案 2.1 Hyper-V方案 2.2 禁用 Device Guard(系统:win11) 2.3 升级虚拟机VMware pro的版本 ...