标签:

[HAOI2015]树上操作 题解

[HAOI2015]树上操作 题解

题目地址:洛谷:【P3178】[HAOI2015]树上操作 – 洛谷、BZOJ:Problem 4034. — [HAOI2015]树上操作

题目描述

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

输入输出格式

输入格式:
第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

输出格式:
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

输入输出样例

输入样例#1:

5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3

输出样例#1:

6
9
13

说明

对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。

题解

树剖那篇文章的例题还裸。

代码

 // Code by KSkun, 2018/3
#include <cstdio>

#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;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

int n, m, w[MAXN], ut, vt, op, xt, at;
int fa[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], ptn[MAXN], top[MAXN], dep[MAXN], cnt;
std::vector<int> gra[MAXN];

inline void addedge(int u, int v) {
    gra[u].push_back(v);
    gra[v].push_back(u);
}

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

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

struct Node {
    LL val, add;
} sgt[MAXN << 2];

inline void pushdown(int o, int l, int r) {
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(sgt[o].add) {
        sgt[lch].add += sgt[o].add;
        sgt[rch].add += sgt[o].add;
        sgt[lch].val += sgt[o].add * (mid - l + 1);
        sgt[rch].val += sgt[o].add * (r - mid);
        sgt[o].add = 0;
    }
}

inline void build(int o, int l, int r) {
    if(l == r) {
        sgt[o].val = w[ptn[l]];
        return;
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    build(lch, l, mid);
    build(rch, mid + 1, r);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

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

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

inline void addpoint(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x], v);
}

inline void addsubtree(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x] + siz[x], v);
}

inline LL querychain(int x) {
    int tp = top[x];
    LL res = 0;
    while(x) {
        res += query(1, 1, n, dfn[tp], dfn[x]);
        x = fa[tp];
        tp = top[x];
    }
    return res;
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) w[i] = readint();
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedge(ut, vt);
    }
    dfs1(1);
    dfs2(1, 1);
    build(1, 1, n);
    while(m--) {
        op = readint();
        xt = readint();
        if(op != 3) at = readint();
        if(op == 1) {
            addpoint(xt, at);
        }
        if(op == 2) {
            addsubtree(xt, at);
        }
        if(op == 3) {
            printf("%lld\n", querychain(xt));
        }
    }
    return 0;
}
[SPOJ-COT]Count on a tree 题解

[SPOJ-COT]Count on a tree 题解

题目地址:洛谷:【SP10628】COT – Count on a tree – 洛谷、SPOJ:SPOJ.com – Problem COT、BZOJ:Problem 2588. — Spoj 10628. Count on a tree

题目描述

You are given a tree with N nodes. The tree nodes are numbered from 1 to N. Each node has an integer weight.
We will ask you to perform the following operation:

  • u v k : ask for the kth minimum weight on the path from node u to node v

给你一棵带点权的树,每次询问求u到v路径上的k小权值。

输入输出格式

输入格式:
In the first line there are two integers N and M. (N, M <= 100000)
In the second line there are N integers. The ith integer denotes the weight of the ith node.
In the next N-1 lines, each line contains two integers u v, which describes an edge (u, v).
In the next M lines, each line contains three integers u v k, which means an operation asking for the kth minimum weight on the path from node u to node v.

输出格式:
For each operation, print its result.

输入输出样例

输入样例#1:

8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
2 5 2
2 5 3
2 5 4
7 8 2 

输出样例#1:

2
8
9
105
7 

题解

这是一个主席树的题目。考虑每个点建一个权值线段树,维护从根到该点的路径权值。DFS一遍,对于一个点,从其父亲处插入该点的权值。权值需要离散化处理一下。直接钦点1为树根也行。询问的时候,查询该点LCA,查询时路径上的答案为u+v-lca-fa[lca],考虑像区间k小值那样,四个线段树一起跑。这里我的LCA是倍增写法。

代码

// Code by KSkun, 2018/2
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>

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 int readint() {
    register int res = 0, neg = 1;
    char c = fgc(); 
    while (c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while (c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

int n, m, w[MAXN], ut, vt, kt;
std::vector<int> gra[MAXN];
int tmp[MAXN], tsiz;

// seg tree

struct Node {
    int lch, rch, val;
} tree[MAXN << 5];
int tot = 0, rt[MAXN];

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

inline int query(int lo, int ro, int fo, int ffo, int l, int r, int k) {
    if(l == r) return l;
    int mid = (l + r) >> 1;
    int num = tree[tree[lo].lch].val + tree[tree[ro].lch].val - tree[tree[fo].lch].val - tree[tree[ffo].lch].val;
    if(k <= num) return query(tree[lo].lch, tree[ro].lch, tree[fo].lch, tree[ffo].lch, l, mid, k);
    else return query(tree[lo].rch, tree[ro].rch, tree[fo].rch, tree[ffo].rch, mid + 1, r, k - num);
}

// lca

int l2n, dep[MAXN], anc[MAXN][20];

void dfs(int u) {
    rt[u] = rt[anc[u][0]];
    insert(rt[u], 1, tsiz, w[u]);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == anc[u][0]) {
            continue;
        }
        dep[v] = dep[u] + 1;
        anc[v][0] = u;
        dfs(v);
    }
}

inline void init() {
    for(int j = 1; (1 << j) <= n; j++) {
        for(int i = 1; i <= n; i++) {
            anc[i][j] = anc[anc[i][j - 1]][j - 1];
        }
    }
}

inline int lca(int a, int b) {
    if(dep[a] > dep[b]) std::swap(a, b);
    int del = dep[b] - dep[a];
    for(int i = 0; (1 << i) <= del; i++) {
        if((1 << i) & del) b = anc[b][i];
    }
    if(a != b) {
        for(int i = l2n; i >= 0; i--) {
            if(anc[a][i] != anc[b][i]) {
                a = anc[a][i];
                b = anc[b][i];
            }
        }
        return anc[a][0];
    } else {
        return a;
    }
}

int main() {
    n = readint();
    m = readint();
    l2n = log(n) / log(2);
    for(int i = 1; i <= n; i++) {
        tmp[i] = w[i] = readint(); 
    }
    std::sort(tmp + 1, tmp + n + 1);
    tsiz = std::unique(tmp + 1, tmp + n + 1) - tmp - 1;
    for(int i = 1; i <= n; i++) {
        w[i] = std::lower_bound(tmp + 1, tmp + tsiz + 1, w[i]) - tmp;
    }
    for(int i = 1; i <= n - 1; i++) {
        ut = readint();
        vt = readint();
        gra[ut].push_back(vt);
        gra[vt].push_back(ut);
    }
    dfs(1);
    init();
    while(m--) {
        ut = readint();
        vt = readint();
        kt = readint();
        int lcat = lca(ut, vt);
        printf("%d\n", tmp[query(rt[ut], rt[vt], rt[lcat], rt[anc[lcat][0]], 1, tsiz, kt)]);
    }
    return 0;
}
[SCOI2016]背单词 题解

[SCOI2016]背单词 题解

题目地址:洛谷:【P3294】[SCOI2016]背单词 – 洛谷、BZOJ:Problem 4567. — [Scoi2016]背单词

题目描述

Lweb 面对如山的英语单词,陷入了深深的沉思,”我怎么样才能快点学完,然后去玩三国杀呢?“。这时候睿智的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的:
—————序号 单词—————
1 XXX
2 XXX
……
n-2 XXX
n-1 XXX
n XXX
———————————————
然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1…x-1 都已经被填入):

  1. 如果存在一个单词是它的后缀,并且当前没有被填入表内,那他需要吃 n*n 颗泡椒才能学会;
  2. 当它的所有后缀都被填入表内的情况下,如果在 1…x-1 的位置上的单词都不是它的后缀,那么你吃 x 颗泡椒就能记住它;
  3. 当它的所有后缀都被填入表内的情况下,如果 1…x-1的位置上存在是它后缀的单词,所有是它后缀的单词中,序号最大为 y,那么你只要吃 x-y 颗泡椒就能把它记住。

Lweb 是一个吃到辣辣的东西会暴走的奇怪小朋友,所以请你帮助 Lweb ,寻找一种最优的填写单词方案,使得他记住这 n 个单词的情况下,吃最少的泡椒。

输入输出格式

输入格式:
输入一个整数 n ,表示 Lweb 要学习的单词数。接下来 n 行,每行有一个单词(由小写字母构成,且保证任意单词两两互不相同)1<=n<=100000, 所有字符的长度总和 1<=|len|<=510000
输出格式:
Lweb 吃的最少泡椒数

输入输出样例

输入样例#1:

2
a
ba

输出样例#1:

2

题解

如何处理字符串

把字符串翻转一下后缀问题就变成前缀问题了。前缀问题可以用Trie树处理。直接把这些字符串扔进Trie里就好。

贪心

第一种情况明显是最差的。我们可以让这个单词的所有后缀放在它前面来避免这种情况的发生,所以不需要管第一种情况。
第二种情况只会在它没有后缀的情况发生。
第三种情况考虑一种贪心想法,在Trie树上,一个结点的子树为以这个结点为前缀的字符串,统计子树中字符串的数量size,并求出一种DFS序,使得子树小的儿子优先被访问。这样,子树大的儿子中的字符串对答案的贡献将会整体减小。而DFS序保证了前缀都在这个字符串以前。

统计答案

考虑对Trie树简化建树,将不是字符串结尾的结点全部删去,并对新树DFS。每一个儿子对答案的贡献为儿子的DFS序号-父亲的DFS序号。这个可以用当前DFS序号维护一下。写树形DP大概也是可以的?

代码

// Code by KSkun, 2018/2
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
typedef long long LL;

std::vector<int> gra[510005];
int tot1 = 1, siz[510005], dp[510005];

int ch[510005][26], tot = 1;
bool wrd[510005];

int n;
LL pts = 0, ans = 0;
char str[510005];

inline bool cmp(int a, int b) {
    return siz[a] < siz[b];
}

inline void insert(char* str) {
    int t = 1;
    int len = strlen(str);
    for(int i = len - 1; i >= 0; i--) {
        if(!ch[t][str[i] - 'a']) {
            t = ch[t][str[i] - 'a'] = ++tot;
        } else {
            t = ch[t][str[i] - 'a'];
        }
    }
    wrd[t] = true;
}

inline void rebuild(int fa, int u) {
    if(wrd[u]) {
        gra[fa].push_back(++tot1);
        siz[fa = tot1] = 1;
    }
    for(int i = 0; i < 26; i++) {
        if(ch[u][i]) {
            rebuild(fa, ch[u][i]);
        }
    }
} 

inline void calsize(int u) {
    for(int i = 0; i < gra[u].size(); i++) {
        calsize(gra[u][i]);
        siz[u] += siz[gra[u][i]];
    }
    std::sort(gra[u].begin(), gra[u].end(), cmp);
}

inline void dfs(int u, int w) {
    pts++;
    ans += pts - w;
    w = pts;
    if(wrd[u]) dp[u] = 1;
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        dfs(v, w);
    }
}

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%s", str);
        insert(str);
    }
    rebuild(1, 1);
    calsize(1);
    dfs(1, 1);
    printf("%lld", ans);
    return 0;
}