T2:AC自动机 (ac.cpp)

题目背景

YZD在每天学习20小时的勤奋研究下,终于开发出了AC自动机!但是,这台AC自动机有一些bug,比如两个部件之间经常会出一些莫名其妙的问题,所以他想要随时知道被损坏的两个部件之间有多少根电线.

题目描述

AC自动机的结构是一个有着n个节点,2n - 2条边的有向图,

前n-1条边,从部件1连向部件2-n,形成了一颗生成树。

后n-1条边,从部件2-n依次连向部件1.

你需要支持两种操作:查询两个点的最短路和修改一条边的长度。

输入输出格式

输入格式

第一行两个整数n,q,为点的数目和询问的数目;

下面2n-2行,每行三个整数u, v, w, 表示从u到v连一条有向边。

下面q行,每行3个整数k,u,v,若k=1,询问从u到v的最短路, 若k=2,修改第u条边长度为v。

输出格式

对于1询问, 每行输出一个整数,表示最短路的长度

样例

输入

5 3

1 2 3

1 3 4

3 4 5

3 5 6

2 1 1

3 1 2

4 1 3

5 1 4

1 3 2

2 3 -3

1 3 2

输出

5

3

范围

对于40 %的数据,没有2操作。

对于100 %的数据, n,q <= 1e5.


我们一起就着代码(std捋顺思路w:

void pre() {
scanf("%lld %lld", &n, &q);
for(ll i = 1; i < n; i++) {
ll u = read(), v = read();
add_edge(u, v, read());//对于树边,建边
fa[v][0] = u;//fa[i][j]表示i点的2^j的祖先是谁,显然v的2^0的祖先也就是它的父亲u
}
for(ll i = 1; i < n; i++) {
ll u = read(), v = read();
f[u] = read();//因为后n-1条边终点全部都为1,因此我们可以用数组f[u]记录由u连向1的这些边的边权;
e[++cnt] = edge(u, v, f[u]);//同样建边;
}
for(ll i = 1; i <= 18; i++)//处理fa数组,利用你的爷爷是你父亲的父亲的思路
for(ll j = 1; j <= n; j++)
fa[j][i] = fa[fa[j][i-1]][i-1];
dfs(1, 0);
build(1, 1, dfn);
}

准备函数,主要进行输入、建图、dfs和建树部分。

建图的话,对于后n-1条边,因为都是从x=>1,需要用f数组存下每条边的权值(其中f[u]表示从u=>1的边的权值);

然后看dfs:

void dfs(ll x, ll fa) {//求一段dfn序,目的是来建一棵线段树
//对于一棵子树来说,它所在dfn区间一定是一段连续的线段
//因此l[x] r[x]分别记录以x 为根的子树的dfn开始与结束
l[x] = ++dfn;
que[dfn] = x; //que数组记录每一个dfn值对应的原图中的点是多少
for(ll i = 0; i < to[x].size(); i++) {//作为std与我们不同的存图方法,意义也是遍历x的所有出边
ll v = e[to[x][i]].to;
if(v != fa) {
dep[v] = dep[x] + e[to[x][i]].w;//dep[v]记录1到v的路径长度;
deep[v] = deep[x] + 1;//然并卵的一步;
dfs(v, x);
}
}
r[x] = dfn;
}

接下来是建树(讲句废话qyf讲的这里超级糙但是我当时看了好久w)

void update(ll k) {
t[k].min = std::min(t[k<<1].min, t[k<<1|1].min);
} void build(ll k, ll l, ll r) {
t[k].l = l, t[k].r = r, t[k].tag1 = t[k].tag2 = 0;//tag1 是dep的改变标记,tag2 是f的改变标记,建树时显然都赋值为0;
if(l == r) {
t[k].min = dep[que[l]] + f[que[l]];//记录线段树中以k为根的(对应原图中某一段点)所有点从1到某个点再回到1的最小值是多少
t[k].dat = dep[que[l]];//变量dat只在k为叶子节点(也就是l==r时会有值)
//记录的是原图中从1到某个点的路径长度(其实就是dep);
return;
}
ll mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1|1, mid+1, r);
update(k);//建完左右子树以后,再在左右子树中取原节点的最小值
}

然后是solve函数:

void pushdown(ll k) {
if(t[k].l == t[k].r) return;
if(t[k << 1].l == t[k<<1].r) t[k<<1].dat += t[k].tag1;
if(t[k << 1|1].l == t[k<<1|1].r)t[k<<1|1].dat += t[k].tag1;
t[k<<1].min += t[k].tag2 + t[k].tag1;
t[k<<1|1].min += t[k].tag2 + t[k].tag1;
t[k<<1].tag1 += t[k].tag1;
t[k<<1].tag2 += t[k].tag2;
t[k<<1|1].tag1 += t[k].tag1;
t[k<<1|1].tag2 += t[k].tag2;
t[k].tag1 = t[k].tag2 = 0;
}
void solve() {
while(q--) {
ll k = read(), x = read(), y = read();
if(k == 2) {
if(x > (n-1)) {
modify(1, l[e[x].from], l[e[x].from], y - f[e[x].from], 1);
f[e[x].from] = y;
}
else {
ll delta = y - e[x].w;
ll u = e[x].to;
modify(1, l[u], r[u], delta);
e[x].w = y;
}
}
else {
ll k = get(y, deep[x]);
if(k == x) ans = pos(1, l[y]) - pos(1, l[x]);//有祖先后辈关系时,答案为从1~y的路径-从1~x的路径
else ans = query(1, l[x], r[x]) - pos(1, l[x]) + pos(1, l[y]);//如果没有祖先后辈关系,答案为以x为根的子树中,最小的从1到子树中某个点z再返回1的路径长度-从1到x的路径长度,再加上从1到y的路径长度。
//注意查询的是dfs序列w
printf("%lld\n", ans);
}
}
}

对于修改:

  1. 显然要讲的:modify函数中\(ll\ arg=0\);这里在调用函数时,可以不需要写arg这个参数,如果不写,arg默认为0,如果写,arg就是你写的那个值;此题中,arg0,对应区间修改,arg1,对应单点修改。
  2. 如果修改的是n-1条以后的边(也就是非树边):
    • 首先一定要做的是pushdown,也就是标记下传。
    • 因为是n-1条以后的边,因此影响的只有某个点u到1点的距离,那么对于这个点u的子树是没有影响的,所以只需要修改的是从1到这个点u路径上的所有点的min,并且在这个点上打上tag2。因此,我们只需要单点修改点u所以,对于dat与tag1,我们不需要修改,需要修改的就只是min与tag2.
    • 然后递归建树,update即可;
  3. 如果修改的边在1~n-1之间(树边)
    • 那么除了从1u的所有点的值来说,对于某一个点u的子树来说,子树中所有的点都会相应地发生一定的改变,因此我们修改的是l[u]r[u]这个区间内的值;
    • 因为修改的是树边,所以dep的值会修改,因此需要标记tag1,min也要相应的加上对应的值,如果修改到底层的叶子节点,也要相应的把dat的值加上。
    • 然后递归建树,update
void modify(ll k, ll x, ll y, ll a, ll arg = 0) {
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(x <= l && r <= y) {
if(arg == 0) {
t[k].tag1 += a;
t[k].min += a;
if(t[k].l == t[k].r) t[k].dat += a;
}
else {
t[k].tag2 += a;
t[k].min += a;
}
return;
}
if(x <= mid) modify(k << 1, x, y, a, arg);
if(y > mid) modify(k << 1|1, x, y, a, arg);
update(k);
}

对于查询,求x=>y的路劲最小值,只有两种情况:

  1. x,y有祖先后辈关系(并且要求必须x是y的祖先),这个时候x到y的路径有且只有一条,就是从x到y的树上路径。(假设x=1,y=5,那么x=>y必然有且只有1=>3=>5这一条路径。)因此求这类的最短路,也就是求从1=>5的树上路径\(-\)从1=>3的树上路径。
  2. x,y没有祖先后辈关系或者y是x的祖先。那么这个时候x到y的最小路径也就是从x点走到点1,然后再从1走到y点的路径或者从x走到它子树中的某一点,再由某一点走到1,再由1走到y的路径中,取最小的和得到的结果。

//用于判断x与y是否有祖先后辈关系,返回y与x同一深度的祖先
ll get(ll x, ll d) {//x:原图中solve的y点的编号,d:solve中x点的深度
ll D = deep[x];//求出编号w
ll p = D - d;//求深度
if(p < 0) return -1;//如果此时原x的深度比原y的深度大,说明原x一定不会是原y的祖先,因此直接返回-1;
//否则将原y跳到与x相同的深度
ll u = x;
for(ll i = 18; i >= 0 && p; i--) {
if((1<<i) <= p) {
p -= (1<<i);
u = fa[u][i];
}
}
return u;
} ll query(ll k, ll x, ll y) {//查询区间x到y的最小值
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(x <= l && r <= y) return t[k].min;
ll ans = inf;
if(x <= mid) ans = std::min(ans, query(k << 1, x, y));
if(y > mid) ans = std::min(ans, query(k << 1|1, x, y));
return ans;
} ll pos(ll k, ll p) {//求1到某个点p的路径长度(也就是dat)
//也就是单点查询
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(l == r) return t[k].dat;
if(p <= mid) return pos(k << 1, p);
else return pos(k << 1|1, p);
}

好了大概是写完了w?

附一个总体代码:

#include <cstdio>
#include <cctype>
#include <vector>
#define ll long long
#include <algorithm>
const ll maxn = 200005;
const ll inf = 1e13;
struct edge{
ll from, to, w;
edge(ll c, ll a, ll b):from(c), to(a),w(b){}
edge(){}
}e[maxn << 1];
ll n, q, cnt, dfn, ans;
ll fa[maxn][20];
ll f[maxn], dep[maxn], deep[maxn], l[maxn], que[maxn], r[maxn];
std::vector<ll> to[maxn];
void add_edge(ll u, ll v, ll w) {
e[++cnt] = edge(u, v, w);
to[u].push_back(cnt);
}
ll read() {
ll x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
struct seg{
ll l, r, min, dat, tag1, tag2; //tag1:dep, tag2:f
}t[maxn<<2];
void update(ll k) {
t[k].min = std::min(t[k<<1].min, t[k<<1|1].min);
}
void pushdown(ll k) {
if(t[k].l == t[k].r) return;
if(t[k << 1].l == t[k<<1].r) t[k<<1].dat += t[k].tag1;
if(t[k << 1|1].l == t[k<<1|1].r)t[k<<1|1].dat += t[k].tag1;
t[k<<1].min += t[k].tag2 + t[k].tag1;
t[k<<1|1].min += t[k].tag2 + t[k].tag1;
t[k<<1].tag1 += t[k].tag1;
t[k<<1].tag2 += t[k].tag2;
t[k<<1|1].tag1 += t[k].tag1;
t[k<<1|1].tag2 += t[k].tag2;
t[k].tag1 = t[k].tag2 = 0;
}
void build(ll k, ll l, ll r) {
t[k].l = l, t[k].r = r, t[k].tag1 = t[k].tag2 = 0;
if(l == r) {
t[k].min = dep[que[l]] + f[que[l]];
t[k].dat = dep[que[l]];
return;
}
ll mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1|1, mid+1, r);
update(k);
}
void modify(ll k, ll x, ll y, ll a, ll arg = 0) {
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(x <= l && r <= y) {
if(arg == 0) {
t[k].tag1 += a;
t[k].min += a;
if(t[k].l == t[k].r) t[k].dat += a;
}
else {
t[k].tag2 += a;
t[k].min += a;
}
return;
}
if(x <= mid) modify(k << 1, x, y, a, arg);
if(y > mid) modify(k << 1|1, x, y, a, arg);
update(k);
}
ll pos(ll k, ll p) {
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(l == r) return t[k].dat;
if(p <= mid) return pos(k << 1, p);
else return pos(k << 1|1, p);
}
ll query(ll k, ll x, ll y) {
pushdown(k);
ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
if(x <= l && r <= y) return t[k].min;
ll ans = inf;
if(x <= mid) ans = std::min(ans, query(k << 1, x, y));
if(y > mid) ans = std::min(ans, query(k << 1|1, x, y));
return ans;
}
void dfs(ll x, ll fa) {//求一段dfn序来建线段树
//对于一棵子树来说,它所在dfn区间一定是一段连续的线段
//因此l[x] r[x]分别记录以x 为根的子树的dfn开始与结束
l[x] = ++dfn;
que[dfn] = x;
for(ll i = 0; i < to[x].size(); i++) {
ll v = e[to[x][i]].to;
if(v != fa) {
dep[v] = dep[x] + e[to[x][i]].w;
deep[v] = deep[x] + 1;
dfs(v, x);
}
}
r[x] = dfn;
}
void pre() {
scanf("%lld %lld", &n, &q);
for(ll i = 1; i < n; i++) {
ll u = read(), v = read();
add_edge(u, v, read());//对于树边,建边
fa[v][0] = u;//fa[i][j]表示i点的2^j的祖先是谁,显然v的2^0的祖先也就是它的父亲u
}
for(ll i = 1; i < n; i++) {
ll u = read(), v = read();
f[u] = read();//因为后n-1条边终点全部都为1,因此我们可以用数组f[u]记录由u连向1的这些边的边权;
e[++cnt] = edge(u, v, f[u]);//同样建边;
}
for(ll i = 1; i <= 18; i++)//处理fa数组,利用你的爷爷是你父亲的父亲的思路
for(ll j = 1; j <= n; j++)
fa[j][i] = fa[fa[j][i-1]][i-1];
dfs(1, 0);
build(1, 1, dfn);
}
ll get(ll x, ll d) {
ll D = deep[x];
ll p = D - d;
if(p < 0) return -1;
ll u = x;
for(ll i = 18; i >= 0 && p; i--) {
if((1<<i) <= p) {
p -= (1<<i);
u = fa[u][i];
}
}
return u;
}
void solve() {
while(q--) {
ll k = read(), x = read(), y = read();
if(k == 2) {
if(x > (n-1)) {
modify(1, l[e[x].from], l[e[x].from], y - f[e[x].from], 1);
f[e[x].from] = y;
}
else {
ll delta = y - e[x].w;
ll u = e[x].to;
modify(1, l[u], r[u], delta);
e[x].w = y;
}
}
else {
ll k = get(y, deep[x]);
if(k == x) ans = pos(1, l[y]) - pos(1, l[x]);
else ans = query(1, l[x], r[x]) - pos(1, l[x]) + pos(1, l[y]);
printf("%lld\n", ans);
}
}
}
int main() {
#ifdef orz
freopen("input", "r", stdin);
#endif
pre();
solve();
}

T2 AC自动机的更多相关文章

  1. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

  2. [转] AC自动机详解

    转载自:http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d AC自动机详解 AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文 ...

  3. HDU 3341 Lost's revenge AC自动机+dp

    Lost's revenge Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)T ...

  4. BZOJ1195[HNOI2006]最短母串——AC自动机+BFS+状态压缩

    题目描述 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. 输入 第一行是一个正整数n(n<=12),表示给定的字符串的 ...

  5. bzoj 2434 [Noi2011]阿狸的打字机——AC自动机

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2434 dfs AC自动机,走过的点权值+1,回溯的时候权值-1:走到询问的 y 串的节点,看 ...

  6. 「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组

    题目描述 回忆树是一棵树,树边上有小写字母. 一次回忆是这样的:你想起过往,触及心底--唔,不对,我们要说题目. 这题中我们认为回忆是这样的:给定 \(2\) 个点 \(u,v\) (\(u\) 可能 ...

  7. 蓝书2.4 AC自动机

    T1 玄武密码 bzoj 4327 题目大意: 一些字符串 求这些字符串的前缀在母串上的最大匹配长度是多少 思路: 对于所有串建立AC自动机 拿母串在自动机上匹配 对所有点打标记 以及对他们的fail ...

  8. AC自动机处理多串匹配——cf1202E

    si+sj中间有一个切割点,我们在t上枚举这个切割点i,即以t[i]作为最后一个字符时求有多少si可以匹配,以t[i+1]作为第一个字符时有多少sj可以匹配 那么对s串正着建一个ac自动机,反着建一个 ...

  9. Censored! POJ - 1625 AC自动机+大数DP

    题意: 给出一n种字符的字典,有p个禁用的单词, 问能组成多少个不同的长度为m的合法字符串.(m<=50) 题解: 是不是个我们之前做的题目非常非常像,题意都一样. 直接将上次写的AC自动机+矩 ...

随机推荐

  1. POJ 3692 幼儿园做游戏 最大团 模板题

    Kindergarten Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 6191   Accepted: 3052 Desc ...

  2. 【LOJ2316】「NOIP2017」逛公园

    [题目链接] [点击打开链接] [题目概括] 对给定\(K\),起点\(1\)到终点\(n\)中对长度为\([L,L+K]\)的路径计数. \(L\)为\(1\)到\(n\)的最短路长度. [思路要点 ...

  3. AtCoder AGC036C GP 2 (组合计数)

    题目链接 https://atcoder.jp/contests/agc036/tasks/agc036_c 题解 终于有时间补agc036的题了. 这题其实不难的来着--我太菜了考场上没想出来 首先 ...

  4. php 将几个变量合为数组,变量名和值对应

    <?php $firstname = "Bill"; $lastname = "Gates"; $age = "60"; $resul ...

  5. BruteXSS

    0X01安装 我是按照下面这位大牛的博客来的 https://www.cnblogs.com/Pitcoft/p/6341322.html 0X02使用BruteXSS的使用 #在目录 BruteXS ...

  6. 错误“Object reference not set to an instance of an object”的解决方法

    在进行unity游戏制作的C#代码编写时,会遇到“NullReferenceException: Object reference not set to an instance of an objec ...

  7. html实现高亮检索

    实现效果如下: demo.html <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  8. ad2014注册出现:注册 - 激活错误 (0015.111)

    将安装包内的(adlmact.dll & adlmact_libFNP.dll)这两个文件取出并覆盖即可.安装包内文件具体位置:在安装包内搜索“adlmact”出现的两个文件“adlmact_ ...

  9. JavaEE-实验一 Java常用工具类编程

    该博客仅专为我的小伙伴提供参考而附加,没空加上代码具体解析,望各位谅解 1.  使用类String类的分割split 将字符串  “Solutions to selected exercises ca ...

  10. vim技巧1

    在编辑模式或可视模式下输入的命令会另外注明.1. 查找   /xxx(?xxx)       表示在整篇文档中搜索匹配xxx的字符串, / 表示向下查找, ? 表示                   ...