T2 AC自动机

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就是你写的那个值;此题中,arg==0,对应区间修改,arg==1,对应单点修改。
  2. 如果修改的是n-1条以后的边(也就是非树边):
    • 首先一定要做的是pushdown,也就是标记下传。
    • 因为是n-1条以后的边,因此影响的只有某个点u到1点的距离,那么对于这个点u的子树是没有影响的,所以只需要修改的是从1到这个点u路径上的所有点的min,并且在这个点上打上tag2。因此,我们只需要单点修改点u所以,对于dat与tag1,我们不需要修改,需要修改的就只是min与tag2.
    • 然后递归建树,update即可;
  3. 如果修改的边在1~n-1之间(树边)
    • 那么除了从1~u的所有点的值来说,对于某一个点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();
}

转载于:https://www.cnblogs.com/zhuier-xquan/p/11528291.html

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、 1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READmE.文件(md如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通;、本 3项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看ReadmE.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、资 1源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READMe.m文件(如d有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值