zoukankan      html  css  js  c++  java
  • 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
    ", 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之间(树边)
      • 那么除了从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的路径中,取最小的和得到的结果。

    1568536073071

    //用于判断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
    ", ans);
        }
        }
    }
    int main() {
    #ifdef orz
        freopen("input", "r", stdin);
    #endif
        pre();
        solve();
    }
    
  • 相关阅读:
    异步FIFO的Verilog实现
    二进制格雷码与二进制自然码
    握手协议
    电容充放电和开关电容
    Vivado自定义IP封装流程
    【转】warning 之 [IP_Flow 19-3153]
    【转】mipi-csi-2解读
    版本管理-link
    [转载]yuv和yCbCr的差异
    【转】用verilog实现RGB格式图像到YCbCr或YUV格式的转换及其验证方法 (RGB2YCrCb)(RGB2YUV)
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11528291.html
Copyright © 2011-2022 走看看