标签: 线段树

[洛谷4643]【模板】动态dp 题解

[洛谷4643]【模板】动态dp 题解

题目地址:洛谷:【P4643】【模板】动态dp – 洛谷

题目描述

给定一棵 n 个点的树,点带点权。
有 m 次操作,每次操作给定 x, y ,表示修改点 x 的权值为 y 。
你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

输入输出格式

输入格式:
第一行, n, m ,分别代表点数和操作数。
第二行, V1, V2, …, Vn ,代表 n 个点的权值。
接下来 n-1 行, x, y ,描述这棵树的 n-1 条边。
接下来 m 行, x, y ,修改点 x 的权值为 y 。

输出格式:
对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。
保证答案在 int 范围内

输入输出样例

输入样例#1:

10 10
-11 80 -99 -76 56 38 92 -51 -34 47 
2 1
3 1
4 3
5 2
6 2
7 1
8 2
9 4
10 7
9 -44
2 -17
2 98
7 -58
8 48
3 99
8 -61
9 76
9 14
10 93

输出样例#1:

186
186
190
145
189
288
244
320
258
304

说明

对于30%的数据, 1≤n,m≤10
对于60%的数据, 1≤n,m≤1000
对于100%的数据, 1≤n,m≤10^5

题解

参考资料:Luogu P4643 【模板】动态dp – 胡小兔 – 博客园
首先这个题不带修改就是一个“没有上司的舞会”模型,用$dp[u][0/1]$代表$u$这个点选或不选其子树中的最大权独立集的权值和,DFS向上转移即可。
现在我们需要动态地维护树上的DP值,我们需要一种方法来使权值能快速更新。在这道题里,我们的转移方程是
$$ \begin{aligned}
dp[u][0] &= \sum_{v \text{ is a son of } u} \max \{ dp[v][0], dp[v][1] \} \\
dp[u][1] &= \sum_{v \text{ is a son of } u} dp[v][0]
\end{aligned} $$
首先不好搞的是儿子的数量比较多求和不好做,我们先考虑把树给剖掉,使用$g[u][0/1]$记录$u$这个点选或不选其自身和所有轻儿子的答案的和,而$f[u][0/1]$则记录总的答案,即合并轻重儿子考虑。现在,方程变成了这样
$$ \begin{aligned}
f[u][0] &= g[u][0] + \max \{ f[v][0], f[v][1] \} \\
f[u][1] &= g[u][1] + f[v][0]
\end{aligned} $$
其中的$v$是$u$的重儿子。到现在为止,似乎也没有把转移的形式变得可以树上操作,而且怎么求$g[u][0/1]$的值也不知道。这时,我们想到了矩阵。
矩阵的乘法可以理解为向量运算,即$\mathbf{A} \times \mathbf{B}$可以理解为$\mathbf{B}$中的每一个行向量$\mathbf{b_1}$乘上$\mathbf{A}$中每一行对应的系数$\{ a_{1, 1}, a_{1, 2}, \dots, a_{1, n} \}$再加起来得到的行向量组(或者也可以用列向量的形式理解)。
在这里,我们需要重新定义向量加法和标量乘法。
$$ \begin{aligned}
\mathbf{a} + \mathbf{b} = \mathbf{c} &\Rightarrow c_i = \max \{ a_i, b_i \} \\
\mathbf{a} \times b = \mathbf{c} &\Rightarrow c_i = a_i + b
\end{aligned} $$
我们发现上面的重新定义的运算具有很好的性质,可以按照这样的定义构造向量空间,来定义新的矩阵乘法。新的矩阵乘法如下
$$ \mathbf{A} \times \mathbf{B} = \mathbf{C} \Rightarrow C_{i, j} = \max_k \{ A_{i, k} + B_{k, j} \} $$
这样,我们就可以构造一个矩阵来进行DP的转移了
$$ \begin{bmatrix} f[u][0] \\ f[u][1] \end{bmatrix} = \begin{bmatrix} f[v][0] \\ f[v][1] \end{bmatrix} \times \begin{bmatrix} g[u][0] & g[u][0] \\ g[u][1] & 0 \end{bmatrix} $$
那么,只要我们知道了整棵树上的转移矩阵(即右侧的那个$2 \times 2$矩阵),就可以通过线段树维护重链矩阵乘法,从而快速求某条重链顶端的答案了。我们无需设置一个列向量来乘上这个转移矩阵,这是因为,叶子节点的$f[u][0]=g[u][0]=0, f[u][1]=g[u][1]=w_u$,看上去就跟列向量一样了。
接下来考虑修改问题,我们考虑更新这个结点的矩阵后,沿重链上跳,每个重点的顶的父亲都会受到影响,此时只需要把影响大力算出来改一下就好。
我的初始化是逐个点改权值,复杂度是$O(n \log^2 n)$的,实际上可以先$O(n)$地做一遍DP,然后用DP值构造矩阵,这个复杂度是$O(n+n \log n)$,相比而言常数更优秀,不过代码更长,考虑到这个题没卡常,就懒得写了233
总复杂度是$O((n+m) \log^2 n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

struct Matrix {
    LL m[2][2];
    Matrix() {
        memset(m, 0, sizeof(m));
    }
    inline LL* operator[](const int &x) const {
        return const_cast<LL*>(m[x]);
    }
    inline Matrix operator*(const Matrix &rhs) const {
        Matrix res;
        for(int i = 0; i <= 1; i++) {
            for(int j = 0; j <= 1; j++) {
                for(int k = 0; k <= 1; k++) {
                    res[i][j] = std::max(res[i][j], m[i][k] + rhs[k][j]);
                }
            }
        }
        return res;
    }
    inline Matrix& operator*=(Matrix &rhs) {
        return *this = *this * rhs;
    }
};

int n, m, w[MAXN];
std::vector<int> gra[MAXN];

int fa[MAXN], siz[MAXN], dep[MAXN], son[MAXN], top[MAXN], end[MAXN], dfn[MAXN], vn[MAXN], clk;

void dfs1(int u) {
    siz[u] = 1;
    for(auto v : gra[u]) {
        if(v == fa[u]) continue;
        fa[v] = u; dep[v] = dep[u] + 1;
        dfs1(v);
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int tp) {
    dfn[u] = ++clk; vn[dfn[u]] = u; top[u] = tp; 
    end[tp] = std::max(end[tp], dfn[u]);
    if(son[u]) {
        dfs2(son[u], tp);
    }
    for(auto v : gra[u]) {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

Matrix tr[MAXN << 2], val[MAXN];

#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)

void modify(int o, int l, int r, int x) {
    if(l == r) {
        tr[o] = val[vn[l]]; return;
    }
    if(x <= mid) modify(lch, l, mid, x);
    else modify(rch, mid + 1, r, x);
    tr[o] = tr[lch] * tr[rch];
}

Matrix query(int o, int l, int r, int ll, int rr) {
    if(l == ll && r == rr) return tr[o];
    if(rr <= mid) return query(lch, l, mid, ll, rr);
    else if(ll > mid) return query(rch, mid + 1, r, ll, rr);
    else return query(lch, l, mid, ll, mid) * query(rch, mid + 1, r, mid + 1, rr);
}

inline Matrix query(int u) {
    return query(1, 1, n, dfn[top[u]], end[top[u]]);
}

inline void modify(int u, int v) {
    val[u][1][0] += v - w[u];
    w[u] = v;
    Matrix m1, m2;
    while(u) {
        m1 = query(top[u]);
        modify(1, 1, n, dfn[u]);
        m2 = query(top[u]);
        u = fa[top[u]];
        val[u][0][0] += std::max(m2[0][0], m2[1][0]) - std::max(m1[0][0], m1[1][0]);
        val[u][0][1] = val[u][0][0];
        val[u][1][0] += m2[0][0] - m1[0][0];
    }
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        w[i] = readint();
    }
    for(int i = 1, u, v; i < n; i++) {
        u = readint(); v = readint();
        gra[u].push_back(v); gra[v].push_back(u);
    }
    dfs1(1);
    dfs2(1, 1);
    for(int i = 1; i <= n; i++) {
        int t = w[i]; w[i] = 0;
        modify(i, t);
    }
    while(m--) {
        int x = readint(), y = readint();
        modify(x, y);
        Matrix res = query(1);
        printf("%lld\n", std::max(res[0][0], res[1][0]));
    }
    return 0;
}
[CF916E]Jamie and Tree 题解

[CF916E]Jamie and Tree 题解

题目地址:Codeforces:Problem – 916E – Codeforces、洛谷:【CF916E】Jamie and Tree – 洛谷

题目描述

To your surprise, Jamie is the final boss! Ehehehe.
Jamie has given you a tree with n vertices, numbered from 1 to n. Initially, the root of the tree is the vertex with number 1. Also, each vertex has a value on it.
Jamie also gives you three types of queries on the tree:
1 v — Change the tree’s root to vertex with number v.
2 u v x — For each vertex in the subtree of smallest size that contains u and v, add x to its value.
3 v — Find sum of values of vertices in the subtree of vertex with number v.
A subtree of vertex v is a set of vertices such that v lies on shortest path from this vertex to root of the tree. Pay attention that subtree of a vertex can change after changing the tree’s root.
Show your strength in programming to Jamie by performing the queries accurately!

题意简述

给你一棵有根树,最开始根是1,点上有点权,三种操作:

  1. 换根
  2. 子树点权加一个数
  3. 求子树点权和

输入输出格式

输入格式:
The first line of input contains two space-separated integers n and q (1 ≤ n ≤ 10^5, 1 ≤ q ≤ 10^5) — the number of vertices in the tree and the number of queries to process respectively.
The second line contains n space-separated integers a1, a2, …, an ( - 10^8 ≤ ai ≤ 10^8) — initial values of the vertices.
Next n - 1 lines contains two space-separated integers ui, vi (1 ≤ ui, vi ≤ n) describing edge between vertices ui and vi in the tree.
The following q lines describe the queries.
Each query has one of following formats depending on its type:
1 v (1 ≤ v ≤ n) for queries of the first type.
2 u v x (1 ≤ u, v ≤ n,  - 10^8 ≤ x ≤ 10^8) for queries of the second type.
3 v (1 ≤ v ≤ n) for queries of the third type.
All numbers in queries’ descriptions are integers.
The queries must be carried out in the given order. It is guaranteed that the tree is valid.

输出格式:
For each query of the third type, output the required answer. It is guaranteed that at least one query of the third type is given by Jamie.

输入输出样例

输入样例#1:

6 7
1 4 2 8 5 7
1 2
3 1
4 3
4 5
3 6
3 1
2 4 6 3
3 4
1 6
2 2 4 -5
1 4
3 3

输出样例#1:

27
19
5

输入样例#2:

4 6
4 3 5 6
1 2
2 3
3 4
3 1
1 3
2 2 4 3
1 1
2 2 4 -3
3 1

输出样例#2:

18
21

说明

The following picture shows how the tree varies after the queries in the first sample.
07735ffae7dce2f6d940feb822f0bfe1eb25d264 - [CF916E]Jamie and Tree 题解

题解

雅礼集训Day 1 Prob 1,然而并没能A掉。
换根用LCT做比较快,而子树和用DFS序线段树做比较快。我们要从里面选择一种方法实现。
LCT做的话,需要用到维护子树(包含虚边连的子树)信息的LCT,还要用LCT实现找LCA的算法。具体做法是,找access u和access v路径上最深的相同点。
用线段树做会好写一些,换根对子树加法求和的影响实际上可以讨论出来。试想一条现在根(即1)到换根后的根的路径,对于子树加法的操作,如果u到v的路径与当前根到1的路径无交集时,换根对它产生不了影响,例如样例1中,换根到6后的u=4, v=5这个点对就属于该种情况,这种情况的判定是u和v在根为1的树(下称原树)的LCA与现在的根再求一次LCA,若得到的结果不是原来的LCA,说明属于此种情况;反之,我们需要找到两条路径交集中最深的那个点,可以在LCA(u, rt)和LCA(v, rt)中取最深点,这个点在换根后的树上是最浅的点,子树加法时,只需要加这个点上面的那部分即可,即全树加x,再在当前根到1的路径上的这个点的子节点对应的子树处-x即可,查询同理加全树减这一部分权值即可。
需要保证操作是严格$O(\log n)$的否则会被卡,复杂度$O(n \log n)$。

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

std::vector<int> gra[MAXN];

int n, q, a[MAXN];

int fa[MAXN], siz[MAXN], son[MAXN], dep[MAXN];

inline void dfs1(int u) {
    siz[u] = 1;
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa[u]) continue;
        fa[v] = u; dep[v] = dep[u] + 1;
        dfs1(v); siz[u] += siz[v];
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

int top[MAXN], dfn[MAXN], vn[MAXN], clk;

inline void dfs2(int u, int tp) {
    dfn[u] = ++clk; vn[dfn[u]] = u; top[u] = tp;
    if(son[u]) dfs2(son[u], tp);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

inline int querylca(int u, int v) {
    int tu = top[u], tv = top[v];
    while(tu != tv) {
        if(dep[tu] > dep[tv]) {
            std::swap(u, v); std::swap(tu, tv);
        }
        v = fa[tv]; tv = top[v];
    }
    if(dep[u] < dep[v]) return u; else return v;
}

LL sum[MAXN << 2], tag[MAXN << 2];

#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)

inline void build(int o, int l, int r) {
    if(l == r) {
        sum[o] = a[vn[l]]; return;
    }
    build(lch, l, mid);
    build(rch, mid + 1, r);
    sum[o] = sum[lch] + sum[rch];
}

inline void pushdown(int o, int l, int r) {
    if(tag[o]) {
        tag[lch] += tag[o]; tag[rch] += tag[o];
        sum[lch] += tag[o] * (mid - l + 1); sum[rch] += tag[o] * (r - mid);
        tag[o] = 0;
    }
}

inline void add(int o, int l, int r, int ll, int rr, LL v) {
    if(l >= ll && r <= rr) {
        sum[o] += v * (r - l + 1); tag[o] += v; return;
    }
    pushdown(o, l, r);
    if(ll <= mid) add(lch, l, mid, ll, rr, v);
    if(rr > mid) add(rch, mid + 1, r, ll, rr, v);
    sum[o] = sum[lch] + sum[rch];
}

inline LL query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) return sum[o];
    pushdown(o, l, r); LL res = 0;
    if(ll <= mid) res += query(lch, l, mid, ll, rr);
    if(rr > mid) res += query(rch, mid + 1, r, ll, rr);
    return res;
}

int rt = 1;

int main() {
    n = readint(); q = readint();
    for(int i = 1; i <= n; i++) {
        a[i] = readint();
    }
    for(int i = 1, u, v; i < n; i++) {
        u = readint(); v = readint();
        gra[u].push_back(v); gra[v].push_back(u);
    }
    dfs1(1); dfs2(1, 1); build(1, 1, n);
    while(q--) {
        int op, u, v, x;
        op = readint();
        if(op == 1) {
            rt = readint();
        } else if(op == 2) {
            u = readint(); v = readint(); x = readint();
            int lca = querylca(u, v), l1 = querylca(u, rt), l2 = querylca(v, rt);
            if(rt == 1 || querylca(lca, rt) != lca) {
                add(1, 1, n, dfn[lca], dfn[lca] + siz[lca] - 1, x);
            } else {
                lca = dep[l1] > dep[l2] ? l1 : l2;
                add(1, 1, n, 1, n, x);
                if(lca != rt) {
                    int p = rt;
                    while(dep[fa[top[p]]] > dep[lca]) p = fa[top[p]];
                    p = vn[dfn[p] - (dep[p] - dep[lca] - 1)];
                    add(1, 1, n, dfn[p], dfn[p] + siz[p] - 1, -x);
                }
            }
        } else {
            u = readint();
            if(rt == 1 || querylca(u, rt) != u) {
                printf("%lld\n", query(1, 1, n, dfn[u], dfn[u] + siz[u] - 1));
            } else {
                LL res = 0;
                res += query(1, 1, n, 1, n);
                if(u != rt) {
                    int p = rt;
                    while(dep[fa[top[p]]] > dep[u]) p = fa[top[p]];
                    p = vn[dfn[p] - (dep[p] - dep[u] - 1)];
                    res -= query(1, 1, n, dfn[p], dfn[p] + siz[p] - 1);
                }
                printf("%lld\n", res);
            }
        }
    }
    return 0;
}
[BZOJ3439]Kpm的MC密码 题解

[BZOJ3439]Kpm的MC密码 题解

题目地址:BZOJ:Problem 3439. — Kpm的MC密码

题目描述

Kpm当年设下的问题是这样的:
现在定义这么一个概念,如果字符串s是字符串c的一个后缀,那么我们称c是s的一个kpm串。
系统将随机生成n个由a…z组成的字符串,由1…n编号(s1,s2…,sn),然后将它们按序告诉你,接下来会给你n个数字,分别为k1…kn,对于每一个ki,要求你求出列出的n个字符串中所有是si的kpm串的字符串的编号中第ki小的数,如果不存在第ki小的数,则用-1代替。(比如说给出的字符串是cd,abcd,bcd,此时k1=2,那么”cd”的kpm串有”cd”,”abcd”,”bcd”,编号分别为1,2,3其中第2小的编号就是2)(PS:如果你能在相当快的时间里回答完所有n个ki的查询,那么你就可以成功帮kpm进入MC啦~~)

题意简述

给n个字符串,按1~n编号。对于每一个字符串s,询问s是后缀的字符串中,编号第k大的编号。

输入输出格式

输入格式:
第一行一个整数 n 表示字符串的数目
接下来第二行到n+1行总共n行,每行包括一个字符串,第i+1行的字符串表示编号为i的字符串
接下来包括n行,每行包括一个整数ki,意义如上题所示

输出格式:
包括n行,第i行包括一个整数,表示所有是si的kpm串的字符串的编号中第ki小的数

输入输出样例

输入样例#1:

3
cd
abcd
bcd
2
3
1

输出样例#1:

2
-1
2

说明

样例解释
“cd”的kpm 串有”cd”,”abcd”,”bcd”,编号为1,2,3,第2小的编号是2,”abcd”的kpm串只有一个,所以第3小的编号不存在,”bcd”的kpm串有”abcd”,”bcd”,第1小的编号就是2。
数据范围与约定
对于100%的数据,1<=n<=100000

题解

要查后缀是某串的字符串有哪些,自然可以想到反着插入Trie树,找该串末结点对应的子树中的结尾标记。既然是子树查询k大,我们可以按DFS序建主席树来查。
复杂度O(n \log n)

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

int ch[MAXN][26], ed[MAXN], tot;
int head[MAXN], val[MAXN], nxt[MAXN], ltot;

inline void insert(char *str, int id) {
    int n = strlen(str + 1), p = 0;
    for(int i = n; i >= 1; i--) {
        int t = str[i] - 'a';
        if(!ch[p][t]) ch[p][t] = ++tot;
        p = ch[p][t];
    }
    val[++ltot] = id; nxt[ltot] = head[p]; head[p] = ltot; ed[id] = p;
}

int dfn[MAXN], vn[MAXN], siz[MAXN], clk;

inline void dfs(int u) {
    dfn[u] = ++clk; vn[dfn[u]] = u;
    siz[u] = 1;
    for(int i = 0; i < 26; i++) {
        if(ch[u][i]) {
            dfs(ch[u][i]); siz[u] += siz[ch[u][i]];
        }
    }
}

struct Node {
    int lch, rch, val, id;
} tr[MAXN << 4];
int rt[MAXN], stot;

inline void insert(int &o, int l, int r, int x, int id) {
    if(tr[o].id != id) {
        tr[++stot] = tr[o]; o = stot; tr[o].id = id;
    }
    tr[o].val++;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(x <= mid) insert(tr[o].lch, l, mid, x, id);
    else insert(tr[o].rch, mid + 1, r, x, id);
}

inline int query(int o1, int o2, int l, int r, int k) {
    if(l == r) return l;
    int mid = (l + r) >> 1, lsiz = tr[tr[o2].lch].val - tr[tr[o1].lch].val;
    if(k <= lsiz) return query(tr[o1].lch, tr[o2].lch, l, mid, k);
    else return query(tr[o1].rch, tr[o2].rch, mid + 1, r, k - lsiz);
}

int n, k;
char s[MAXN];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        insert(s, i);
    }
    dfs(0);
    for(int i = 1; i <= clk; i++) {
        rt[i] = rt[i - 1];
        for(int j = head[vn[i]]; j; j = nxt[j]) {
            insert(rt[i], 1, n, val[j], i);
        }
    }
    for(int i = 1; i <= n; i++) {
        scanf("%d", &k);
        if(tr[rt[dfn[ed[i]] + siz[ed[i]] - 1]].val - tr[rt[dfn[ed[i]] - 1]].val < k) {
            puts("-1"); continue;
        }
        printf("%d\n", query(rt[dfn[ed[i]] - 1], rt[dfn[ed[i]] + siz[ed[i]] - 1], 1, n, k));
    }
    return 0;
}