@bzoj - 1921@ [ctsc2010]珠宝商
@description@
简述版题意:给定字符串 S 与一棵树 T,树上每个点有一个字符。求树上所有简单路径对应的字符串在 S 中的出现次数之和。
@solution@
一个显然的暴力:O(N^2) 枚举所有点对的字符串,建后缀自动机跑算出现次数之和。
另一个看起来比较好的算法:点分治。
每次计算经过重心的字符串,可以拆成两部分:某个点到重心的字符串 + 重心到某个点的字符串。
不过合并的时候需要在原字符串 S 上找分界点(不然无法合并),该算法执行一次复杂度为 O(M)。
接下来?发现并没有什么更好的性质可以利用。
数据范围比较小,我们不妨考虑一些玄学的操作:平衡复杂度。
在点分治时,如果当前连通块大小 < \(\sqrt{M}\) 则执行第一种暴力,否则执行第二种暴力。这样平衡下来复杂度为 \(O(N\sqrt{M})\)。
看起来比较显然:连通块大小 < \(\sqrt{M}\) 时 O(size^2) 优于 O(M);否则 O(M) 优于 O(size^2)。
至于复杂度的正确性,第一种暴力的总和显然 \(O(N\sqrt{M})\)。第二种暴力由于决策树的叶子个数 <= \(O(\sqrt{M})\),而深度为 logN(点分治),所以也是 \(O(N\sqrt{M})\)。
不过需要注意点分治时,容斥减去同一子树的贡献也需要根据子树大小分类讨论。
提一点细节:我们找某个点到重心的字符串是往前加字符,并以该字符串为后缀,更新 S 的前缀。也就是说我们不能跑 DAG,需要直接在 parent 树上跑。
其实也不是很难处理,不过状态需要存成两部分:所在结点与现长度。
转移时分两种情况考虑,一个是长度依然小于等于所在结点表示字符串的最大长度,直接在原字符串 S 上看加入这个字符是否仍然合法;另一种,我们需要处理出每个结点的最长字符串前面加入某个字符会转移到的点(可以根据儿子找父亲)。
@accepted code@
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
#define mp make_pair
#define fi first
#define se second
struct SAM{
char str[MAXN + 5]; int n;
struct node{
int len, pos, cnt, tag;
node *sn[26], *ch[26], *fa;
}pl[MAXN + 5], *ncnt, *lst, *root;
SAM() {ncnt = lst = root = pl;}
node *extend(int x, int ps) {
node *nw = (++ncnt), *p = lst; lst = nw;
nw->len = p->len + 1, nw->pos = ps, nw->cnt = 1;
while( p && p->ch[x] == NULL )
p->ch[x] = nw, p = p->fa;
if( !p ) nw->fa = root;
else {
node *q = p->ch[x];
if( p->len + 1 == q->len )
nw->fa = q;
else {
node *nq = (++ncnt); (*nq) = (*q);
nq->len = p->len + 1, nq->cnt = 0;
nw->fa = q->fa = nq;
while( p && p->ch[x] == q )
p->ch[x] = nq, p = p->fa;
}
}
return nw;
}
node *nd[MAXN + 5];
int a[MAXN + 5], b[MAXN + 5];
void build(int _n) {
n = _n;
for(int i=0;i<n;i++) nd[i] = extend(str[i] - 'a', i);
for(int i=ncnt-pl;i>=1;i--) {
node *p = &pl[i];
p->fa->sn[str[p->pos - p->fa->len] - 'a'] = p;
}
for(int i=1;i<=ncnt-pl;i++) b[pl[i].len]++;
for(int i=1;i<=n;i++) b[i] += b[i-1];
for(int i=1;i<=ncnt-pl;i++) a[b[pl[i].len]--] = i;
for(int i=ncnt-pl;i>=1;i--) pl[a[i]].fa->cnt += pl[a[i]].cnt;
}
int f[MAXN + 5];
void clear() {
for(int i=0;i<n;i++) f[i] = 0;
for(int i=0;i<=ncnt-pl;i++) pl[i].tag = 0;
}
void get() {
for(int i=1;i<=ncnt-pl;i++) pl[a[i]].tag += pl[a[i]].fa->tag;
for(int i=0;i<n;i++) f[i] = nd[i]->tag;
}
pair<node*, int>trans(pair<node*, int>x, int ch) {
if( x.fi == NULL ) return x;
x.se++;
if( x.se > x.fi->len ) {
x.fi = x.fi->sn[ch];
return x;
}
else {
if( str[x.fi->pos - x.se + 1] - 'a' != ch )
x.fi = NULL;
return x;
}
}
void update(node *k) {k->tag++;}
}S1, S2;
struct edge{
int to; edge *nxt;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
#define rep(x) for(edge *p=adj[x];p;p=p->nxt)
int N, M, SQ; ll ans;
char s[MAXN + 5];
bool vis[MAXN + 5]; int siz[MAXN + 5];
int get_size(int x, int fa) {
siz[x] = 1;
rep(x) {
if( vis[p->to] || p->to == fa ) continue;
siz[x] += get_size(p->to, x);
}
return siz[x];
}
int hvy[MAXN + 5];
int get_G(int x, int fa, int tot) {
int ret = -1; hvy[x] = tot - siz[x];
rep(x) {
if( vis[p->to] || p->to == fa ) continue;
int t = get_G(p->to, x, tot);
hvy[x] = max(hvy[x], siz[p->to]);
if( ret == -1 || hvy[t] < hvy[ret] ) ret = t;
}
if( ret == -1 || hvy[x] < hvy[ret] ) ret = x;
return ret;
}
void dfs2(int x, int f, SAM::node *nw, int type) {
nw = nw->ch[s[x] - 'a'];
if( !nw ) return ;
ans += nw->cnt * type;
rep(x) {
if( vis[p->to] || p->to == f ) continue;
dfs2(p->to, x, nw, type);
}
}
void dfs1(int x, int f) {
dfs2(x, -1, S1.root, 1);
rep(x) {
if( vis[p->to] || p->to == f ) continue;
dfs1(p->to, x);
}
}
typedef pair<SAM::node*, int> pr;
void dfs3(int x, int f, pr nw) {
nw = S1.trans(nw, s[x] - 'a');
if( nw.fi == NULL ) return ;
S1.update(nw.fi);
rep(x) {
if( vis[p->to] || p->to == f ) continue;
dfs3(p->to, x, nw);
}
}
void dfs4(int x, int f, pr nw) {
nw = S2.trans(nw, s[x] - 'a');
if( nw.fi == NULL ) return ;
S2.update(nw.fi);
rep(x) {
if( vis[p->to] || p->to == f ) continue;
dfs4(p->to, x, nw);
}
}
SAM::node *a[MAXN + 5]; int cnt;
void dfs5(int x, int f, pr nw) {
nw = S1.trans(nw, s[x] - 'a');
if( nw.fi == NULL ) return ;
a[++cnt] = nw.fi;
rep(x) {
if( vis[p->to] || p->to == f ) continue;
dfs5(p->to, x, nw);
}
}
void divide(int x, int n) {
if( n <= SQ ) dfs1(x, -1);
else {
vis[x] = true;
S1.clear(), dfs3(x, -1, mp(S1.root, 0)), S1.get();
S2.clear(), dfs4(x, -1, mp(S2.root, 0)), S2.get();
for(int i=0;i<M;i++) ans += 1LL*S1.f[i]*S2.f[M-1-i];
rep(x) {
if( vis[p->to] ) continue;
int k = get_size(p->to, -1);
if( k <= SQ ) {
cnt = 0, dfs5(p->to, x, S1.trans(mp(S1.root, 0), s[x] - 'a'));
// int res = ans;
for(int i=1;i<=cnt;i++) dfs2(p->to, -1, a[i], -1);
// int del = 0;
// S1.clear(), dfs3(p->to, -1, S1.trans(mp(S1.root, 0), s[x] - 'a')), S1.get();
// S2.clear(), dfs4(p->to, -1, S2.trans(mp(S2.root, 0), s[x] - 'a')), S2.get();
// for(int i=0;i<M;i++) del += 1LL*S1.f[i]*S2.f[M-1-i];
// printf("%d %d\n", res - ans, del);
}
else {
S1.clear(), dfs3(p->to, -1, S1.trans(mp(S1.root, 0), s[x] - 'a')), S1.get();
S2.clear(), dfs4(p->to, -1, S2.trans(mp(S2.root, 0), s[x] - 'a')), S2.get();
for(int i=0;i<M;i++) ans -= 1LL*S1.f[i]*S2.f[M-1-i];
}
divide(get_G(p->to, -1, k), k);
}
}
}
int main() {
scanf("%d%d", &N, &M), SQ = 8*(int)sqrt(M);
for(int i=1;i<N;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u - 1, v - 1);
}
scanf("%s%s", s, S1.str);
for(int i=0;i<M;i++) S2.str[i] = S1.str[M-i-1];
S1.build(M), S2.build(M), divide(get_G(0, -1, get_size(0, -1)), N);
printf("%lld\n", ans);
}
@details@
可以把分界点的大小适当调大(显然后一种算法常数更大)。
一开始 T 了还以为是常数问题,结果仔细一看发现我点分治只有第一轮找了重心,后面没有找重心就直接递归了。。。
@bzoj - 1921@ [ctsc2010]珠宝商的更多相关文章
- [CTSC2010]珠宝商 SAM+后缀树+点分治
[CTSC2010]珠宝商 不错的题目 看似无法做,n<=5e4,8s,根号算法? 暴力一: n^2,+SAM上找匹配点的right集合sz,失配了直接退出 暴力二: O(m) 统计过lca=x ...
- P4218 [CTSC2010]珠宝商
P4218 [CTSC2010]珠宝商 神题... 可以想到点分治,细节不写了... (学了个新姿势,sam可以在前面加字符 但是一次点分治只能做到\(O(m)\),考虑\(\sqrt n\)点分治, ...
- CTSC2010 珠宝商
珠宝商 题目描述 Louis.PS 是一名精明的珠宝商,他出售的项链构造独特,很大程度上是因为他的制作方法与众不同.每次 Louis.PS 到达某个国家后,他会选择一条路径去遍历该国的城市.在到达一个 ...
- [BZOJ1921] [CTSC2010]珠宝商
Description Input 第一行包含两个整数 N,M,表示城市个数及特征项链的长度. 接下来的N-1 行, 每行两个整数 x,y, 表示城市 x 与城市 y 有直接道路相连.城市由1~N进行 ...
- 洛谷P4218 [CTSC2010]珠宝商(后缀自动机+点分治)
传送门 这题思路太清奇了……->题解 //minamoto #include<iostream> #include<cstdio> #include<cstring ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- Bluestein's Algorithm
网上很少有人提到,写的也很简单,事实上就是很简单... \(Bluestein's\ Algorithm\),用以解决任意长度\(DFT\). 考虑\(DFT\)的形式:\[\begin{aligne ...
- Sam做题记录
Sam做题记录 Hihocoder 后缀自动机二·重复旋律5 求一个串中本质不同的子串数 显然,答案是 \(\sum len[i]-len[fa[i]]\) Hihocoder 后缀自动机三·重复旋律 ...
- 【BZOJ1921】【CTSC2010】珠宝商(点分治,后缀自动机)
[BZOJ1921][CTSC2010]珠宝商(点分治,后缀自动机) 题面 洛谷 BZOJ权限题 题解 如果要我们做暴力,显然可以以某个点为根节点,然后把子树\(dfs\)一遍,建出特征串的\(SAM ...
随机推荐
- 用了这么多年的 Java 泛型,你对它到底有多了解?
作为一个 Java 程序员,日常编程早就离不开泛型.泛型自从 JDK1.5 引进之后,真的非常提高生产力.一个简单的泛型 T,寥寥几行代码, 就可以让我们在使用过程中动态替换成任何想要的类型,再也不用 ...
- 王艳 201771010127《面向对象程序设计(java)》第十六周学习总结
一:理论部分 1.程序:是一段静态的代码,它是应用程序执行的蓝本. 2.进程:是程序的一次动态执行,它对应了从代码加载.执行至执行完毕的一个完整过程. 3.多线程:是进程执行过程中产生的多条执行线索. ...
- Word使用技巧——持续更新
Q1:word 2007 打开后默认显示缩略图而不是文档结构图? A1:三步曲 1)打开word,关闭缩略图,保存并关闭文档 2)重新打开word(此时应该没有显示缩略图),勾选上“文档结构图”,保存 ...
- Java-main方法中调用非static方法
java的calss中,在public static void main(String[] args) { }方法中调用非static的方法:在main方法中创建该calss的对象,用对象调用非sta ...
- SPL基础接口
Iterator 迭代器接口 SPL规定,所有实现了Iterator接口的class,都可以用在foreach Loop中.Iterator接口中包含5个必须实现的方法: interface Iter ...
- Yii2.0 URL美化功能Nginx与Apache配置文件
NGinx: location / { index index.html index.htm index.php; try_files $uri $uri/ /index.php$is_args$ar ...
- 华容道题解 NOIP2013 思路题!
第一次发紫题题解,居然在发布前太激动,把刚写好的还没发布的题解一个Ctrl+A和Backspace全删了.(所以这是二稿) luogu题目传送门 前置: 做本题一定要有的一些思想: 1.从简思想: 模 ...
- [Objective-C] Xcode中常用的快捷键操作与插件
古人云“工欲善其事必先利其器”,打造和熟悉一个强大的开发环境,是每个程序员必须的! 在Xcode 6中有许多快捷键的设定可以使得你的编程工作更为高效,对于在代码文件中快速导航.定位Bug以及新增应用特 ...
- eclipse中生成文档注释--javadoc的使用
1.针对于单一的JAVA文件,在终端窗口中,使用 javadoc 文件名.java 即可生成文档注释: 2.在eclipse中生成文档注释: ①单击eclipse菜单栏中的[Project]菜单,该菜 ...
- AUTOSAR-标准文档索引
https://mp.weixin.qq.com/s/6yl5dBP1mSFGVsfE7YRm6w 索引的两种方法: 关键字检索:用Document Search搜索下载,https://www. ...