标签: 分治

[HNOI2015]开店 题解

[HNOI2015]开店 题解

题目地址:洛谷:【P3241】[HNOI2015]开店 – 洛谷、BZOJ:Problem 4012. — [HNOI2015]开店

题目描述

风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n个地方,编号为 1 到 n,被 n-1 条带权的边连接起来。每个地方都住着一个妖怪,其中第 i 个地方的妖怪年龄是 x_i。妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方 u(u为编号),然后在 u开一家面向年龄在 L到R 之间(即年龄大于等于 L、小于等于 R)的妖怪的店。也有可能 u这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在 L 到 R 之间的妖怪,到点 u 的距离的和是多少(妖怪到 u 的距离是该妖怪所在地方到 u 的路径上的边的权之和),幽香把这个称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

题意简述

给你一棵$n$个点的二叉树,每个点有点权,每条边有边权,有$Q$个询问,每次询问树上所有点权在$[L, R]$范围内的点到$u$的距离之和,强制在线。

输入输出格式

输入格式:
第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖怪的年龄上限。
第二行n个用空格分开的数 x_1、x_2、…、x_n,x_i 表示第i 个地点妖怪的年龄,满足0<=x_i<A。(年龄是可以为 0的,例如刚出生的妖怪的年龄为 0。)
接下来 n-1 行,每行三个用空格分开的数 a、b、c,表示树上的顶点 a 和 b 之间有一条权为c(1 <= c <= 1000)的边,a和b 是顶点编号。
接下来Q行,每行三个用空格分开的数 u、 a、 b。对于这 Q行的每一行,用 a、b、A计算出 L和R,表示询问“在地方 u开店,面向妖怪的年龄区间为[L,R]的方案的方便值是多少”。对于其中第 1 行,L 和 R 的计算方法为:L=min(a%A,b%A), R=max(a%A,b%A)。对于第 2到第 Q行,假设前一行得到的方便值为 ans,那么当
前行的 L 和 R 计算方法为: L=min((a+ans)%A,(b+ans)%A), R=max((a+ans)%A,(b+ans)%A)。

输出格式:
对于每个方案,输出一行表示方便值。

输入输出样例

输入样例#1:

10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4

输出样例#1:

1603 
957 
7161 
9466 
3232 
5223 
1879 
1669 
1282 
0

说明

满足 n<=150000,Q<=200000。对于所有数据,满足 A<=10^9

题解

我们使用边分治解决这个问题,因为树是二叉树,具有很优秀的性质,适合使用边分治。我们考虑在分治过程中记录下子分治结构所有点到中心边两端点的距离的集合,并以此计算权值下标的距离前缀和。我们将分治树的树形态保存下来,对于每个点显然对应着分治树的一个树链,对于树链上的每一个中心边,求出询问点对侧半棵树点权在区间内的点的距离之和,可以利用二分查找和之前的前缀和求出,这些点对答案的贡献=距离之和+点数*(中心边边权+询问点到该侧中心边端点的距离)。
简单地说就是用一个边分树优化了暴力查找的过程,使得查找涉及的点规模降至$O(\log n)$,因此每次查找的复杂度是$O(\log^2 n)$的,即总复杂度为$O(n \log^2 n)$。虽然这个复杂度与树链剖分是一致的,但是常数比树剖不知道大到哪去了,因此在洛谷上得开O2过,而且由于维护的信息多,相对空间占用也比树剖大。
以及,如果随机小数据没卵问题,随机大数据小概率出问题,那大概可能是因为维护信息用了个set,而set会去重,要改成multiset QAQ

代码

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

#include <algorithm>
#include <queue>
#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 * 10 + c - '0';
    return res * neg;
}

const int MAXN = 150005;

int n, q;
LL A, w[MAXN];

struct Edge {
    int to;
    LL w;
    int nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void addedge(int u, int v, LL w) {
    gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
    gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}

bool del[MAXN];
int siz[MAXN], ct, ctsiz;

void dfs_size(int u, int fa) {
    siz[u] = 1;
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(v == fa || del[i >> 1]) continue;
        dfs_size(v, u);
        siz[u] += siz[v];
    }
}

void findct(int u, int fa, int tot) {
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(v == fa || del[i >> 1]) continue;
        findct(v, u, tot);
        int vsiz = std::max(siz[v], tot - siz[v]);
        if(vsiz < ctsiz) {
            ct = i; ctsiz = vsiz;
        }
    }
}

struct Info {
    LL did, s, dis;
};

struct Info2 {
    LL w, dis, cnt;
    inline bool operator<(const Info2 &rhs) const {
        return w < rhs.w;
    }
    inline bool operator<=(const Info2 &rhs) const {
        return w <= rhs.w;
    }
    inline bool operator>(const Info2 &rhs) const {
        return w > rhs.w;
    }
    inline bool operator>=(const Info2 &rhs) const {
        return w >= rhs.w;
    }
};

std::vector<Info> pinfo[MAXN];
typedef std::pair<LL, LL> PII;
std::priority_queue<PII, std::vector<PII>, std::greater<PII> > cinfom;
std::vector<Info2> cinfo[MAXN][2];
int eid[MAXN], ctot;

void dfs_dis(int u, int fa, LL dis, int did, int s) {
    cinfom.push(PII(w[u], dis));
    pinfo[u].push_back(Info {did, s, dis});
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(v == fa || del[i >> 1]) continue;
        dfs_dis(v, u, dis + gra[i].w, did, s);
    }
}

inline void work(int u, int s, int did) {
    dfs_dis(u, 0, 0, did, s);
    LL lst = -1, lid = 0;
    cinfo[did][s].push_back(Info2 {-1, 0, 0});
    while(!cinfom.empty()) {
        if(cinfom.top().first != lst) {
            cinfo[did][s].push_back(Info2 {cinfom.top().first, 0, 0});
            lst = cinfom.top().first; lid++;
        }
        cinfo[did][s][lid].dis += cinfom.top().second;
        cinfo[did][s][lid].cnt++;
        cinfom.pop();
    }
    for(int i = 1; i < cinfo[did][s].size(); i++) {
        cinfo[did][s][i].dis += cinfo[did][s][i - 1].dis;
        cinfo[did][s][i].cnt += cinfo[did][s][i - 1].cnt;
    }
}

void divide(int u) {
    dfs_size(u, 0);
    ct = -1; ctsiz = n; findct(u, 0, siz[u]);
    if(ct == -1) return;
    int x = gra[ct].to, y = gra[ct ^ 1].to, t = ++ctot;
    del[ct >> 1] = true;
    work(x, 0, t); work(y, 1, t);
    eid[t] = ct;
    divide(x); divide(y);
}

inline LL query(int u, LL l, LL r) {
    LL res = 0;
    for(int i = 0; i < pinfo[u].size(); i++) {
        Info info = pinfo[u][i];
        int s = info.s;
        std::vector<Info2>::iterator
            lit = std::lower_bound(cinfo[info.did][s ^ 1].begin(), cinfo[info.did][s ^ 1].end(), Info2 {l, 0, 0}) - 1,
            rit = std::upper_bound(cinfo[info.did][s ^ 1].begin(), cinfo[info.did][s ^ 1].end(), Info2 {r, 0, 0}) - 1;
        LL dis = rit->dis - lit->dis, cnt = rit->cnt - lit->cnt;
        res += dis + 1ll * cnt * (gra[eid[info.did]].w + info.dis);
    }
    return res;
}

int main() {
    memset(head, -1, sizeof(head));
    n = readint(); q = readint(); A = readint();
    for(int i = 1; i <= n; i++) {
        w[i] = readint();
    }
    for(int i = 1, a, b, c; i < n; i++) {
        a = readint(); b = readint(); c = readint();
        addedge(a, b, c);
    }
    divide(1);
    LL lastans = 0;
    while(q--) {
        LL u = readint(), a = readint(), b = readint(),
            l = std::min((a + lastans) % A, (b + lastans) % A),
            r = std::max((a + lastans) % A, (b + lastans) % A);
        printf("%lld\n", lastans = query(u, l, r));
    }
    return 0;
}
[IOI2011]Race 题解

[IOI2011]Race 题解

题目地址:洛谷:【P4149】[IOI2011]Race – 洛谷、BZOJ:Problem 2599. — [IOI2011]Race

题目描述

给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.

输入输出格式

输入格式:
第一行 两个整数 n, k
第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始)

输出格式:
一个整数 表示最小边数量 如果不存在这样的路径 输出-1

输入输出样例

输入样例#1:

4 3
0 1 1
1 2 2
1 3 4

输出样例#1:

2

说明

N <= 200000, K <= 1000000

题解

参考资料:bzoj千题计划160:bzoj2599: [IOI2011]Race – TRTTG – 博客园
考虑点分治的做法,类似于洛谷点分治模板题。但是这个题有点卡常,你需要做一些调整。

  1. 你不可能像洛谷模板题那样O(n^2)组合了,因此我们可以计算一个cnt数组,为到重心的每个距离的最小边数,每次到一棵子树计算距离的时候,先更新全局答案,再计算该子树对cnt的更改,最后还要用DFS将cnt数组还原至默认值。毕竟K范围那么大,每次都memset也不现实。
  2. dis超过K的DFS可以剪枝剪掉。
  3. 其实上面更新答案的方法还可以用双指针法,严格O(n)更快。这位仁兄写的就是双指针:bzoj2599: [IOI2011]Race(点分治) – mzl120918 – 博客园

搜索+剪枝还是跑的不太慢的。

代码

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

inline int max(int a, int b) {
    return a > b ? a : b;
}

inline int min(int a, int b) {
    return a < b ? a : b;
}

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

const int MAXN = 200005, INF = 1e9;

int n, k, mn = INF, siz[MAXN], msz[MAXN], rt;
bool vis[MAXN];

struct Edge {
    int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void addedge(int u, int v, int w) {
    gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
    gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}

inline void findrt(int u, int f, int tot) {
    siz[u] = 1; msz[u] = 0;
    for(register int i = head[u]; ~i; i = gra[i].nxt) {
        register int v = gra[i].to;
        if(v == f || vis[v]) continue;
        findrt(v, u, tot);
        siz[u] += siz[v];
        msz[u] = max(msz[u], siz[v]);
    }
    msz[u] = max(msz[u], tot - siz[u]);
    if(msz[u] < msz[rt]) rt = u;
}

int cnt[1000005];

inline void dfs(int u, int f, int dis, int dep, int fun) {
    if(fun == 2 && dis == k) mn = min(mn, dep);
    if(dis >= k) return;
    if(fun == 2) cnt[dis] = cnt[dis] ? min(cnt[dis], dep) : dep;
    else if(fun == 1) { if(cnt[k - dis]) mn = min(mn, dep + cnt[k - dis]); }
    else cnt[dis] = 0;
    for(register int i = head[u]; ~i; i = gra[i].nxt) {
        register int v = gra[i].to, w = gra[i].w;
        if(v == f || vis[v]) continue;
        dfs(v, u, dis + w, dep + 1, fun);
    }
}

inline void work(int u) {
    for(register int i = head[u]; ~i; i = gra[i].nxt) {
        register int v = gra[i].to, w = gra[i].w;
        if(vis[v]) continue;
        dfs(v, u, w, 1, 1);
        dfs(v, u, w, 1, 2);
    }
    for(register int i = head[u]; ~i; i = gra[i].nxt) {
        register int v = gra[i].to, w = gra[i].w;
        if(vis[v]) continue;
        dfs(v, u, w, 1, 3);
    }
}

inline void divide(int u) {
    work(u);
    vis[u] = true;
    for(register int i = head[u]; ~i; i = gra[i].nxt) {
        register int v = gra[i].to;
        if(vis[v]) continue;
        rt = 0; findrt(v, 0, siz[v]);
        divide(rt);
    }
}

int u, v, w;

int main() {
    memset(head, -1, sizeof(head));
    n = readint(); k = readint();
    for(register int i = 1; i < n; i++) {
        u = readint() + 1; v = readint() + 1; w = readint();
        addedge(u, v, w);
    }
    msz[0] = INF; rt = 0; findrt(1, 0, n);
    divide(rt);
    printf("%d", mn != INF ? mn : -1);
    return 0;
}
[CFGym100633D]LWDB 题解

[CFGym100633D]LWDB 题解

题目地址:Codeforces:Problem – 100633D – Codeforces

题目描述

The Large Wood Database is created to securely store and paint any existing tree. Update for LWDB provides new functionality, so it is time to think over the graph theory. A weighed tree is stored in the LWDB. In the query language for LWDB Management System (LWDB MS) two types of queries are available:

  1. «1 v d c» — paint all tree-vertices at the distance not exceeding d from the vertice v in color c. Initial color for any vertices is 0.
  2. «2 v» — return the color of the vertice v.

It is required to prototype LWDB MS and respond to all user’s queries.
给你一棵带边权的树,要求操作1.改变一个节点d距离内的所有点颜色为c;2.询问一个点颜色。

输入输出格式

输入格式:
The first line contains an integer N (1 ≤ N ≤ 105) — the number of tree vertices. The following N-1 lines contain the description of branches, three numbers in each line ai, bi, wi (1 ≤ ai, bi ≤ N, ai ≠ bi, 1 ≤ wi ≤ 104), where i-th branch with weight wi connects vertices ai and bi. The next line contains integer Q (1 ≤ Q ≤ 105) — number of queries. In each of Q following lines there are two types of queries:

  1. Numbers 1, v, d, c (1 ≤ v ≤ N, 0 ≤ d ≤ 109, 0 ≤ c ≤ 109).
  2. Numbers 2, v (1 ≤ v ≤ N).

Input numbers are integers.

输出格式:
For each second type query output the color of requested vertice in a separate line.

输入输出样例

输入样例#1:

5
1 2 30
1 3 50
3 4 70
3 5 60
8
1 3 72 6
2 5
1 4 60 5
2 3
2 2
1 2 144 7
2 4
2 5

输出样例#1:

6
6
0
5
7

题解

对于这道题,我们在点分树上统计的信息变成了每一次染色操作。
对于一个距离为d,染色点为u的染色操作,我们在它到树根这条链上的重心打标记,直到u到某个重心的距离超出了染色距离。标记的含义是,从这个重心出发的若干距离范围内的点都被打上标记。在每个重心维护一个单调栈,使得栈内标记的作用范围d以时间顺序单调递减。
查询时,也从查询点u往根走,二分找到能影响到这个点的标记,维护当前找到的最新标记,该标记就是最终给这个点染色的标记。
可以脑补一下这个过程来意识流证明其正确性。

代码

下面的代码参考了【Codeforces 100633D】LWDB – Qizy’s Database。Qizy同学的代码还是非常优美的。利用预处理和倍增减小常数,实现的抽象和封装也很优美。学习了。

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

#include <vector>
#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;
    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, INF = 1e9;

int fa[MAXN][20], dep[MAXN], dis[MAXN];

struct Edge {
    int to, w;
};
std::vector<Edge> gra[MAXN];

inline void dfs(int u, int f) {
    fa[u][0] = f;
    dep[u] = dep[f] + 1;
    for(Edge e : gra[u]) {
        int v = e.to, w = e.w;
        if(v == f) continue;
        dis[v] = dis[u] + w;
        dfs(v, u);
    }
}

inline int caldis(int u, int v) {
    int res = dis[u] + dis[v];
    if(dep[u] < dep[v]) std::swap(u, v);
    for(int i = 19; i >= 0; i--) {
        if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
    }
    if(u == v) return res - (dis[u] << 1);
    for(int i = 19; i >= 0; i--) {
        if(fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    return res - (dis[fa[u][0]] << 1);
}

int sum, rt, rod[MAXN][20], siz[MAXN], msz[MAXN], anc[MAXN][20], len[MAXN];
bool vis[MAXN];

struct Data {
    int dis, col, id;
    inline bool operator<(const Data &rhs) const {
        return dis < rhs.dis;
    }
    inline bool operator<=(const Data &rhs) const {
        return dis <= rhs.dis;
    }
};

Data tmp;
std::vector<Data> stk[MAXN];
std::vector<Data>::reverse_iterator it;

inline void getrt(int u, int fa) {
    siz[u] = 1;
    msz[u] = 0;
    for(Edge e : gra[u]) {
        int v = e.to;
        if(v == fa || vis[v]) continue;
        getrt(v, u);
        siz[u] += siz[v];
        msz[u] = std::max(msz[u], siz[v]);
    }
    msz[u] = std::max(msz[u], sum - siz[u]);
    if(msz[u] < msz[rt]) rt = u;
}

inline void dac(int u) {
    anc[u][0] = u;
    vis[u] = true;
    for(Edge e : gra[u]) {
        int v = e.to;
        if(vis[v]) continue;
        sum = siz[v];
        rt = 0;
        getrt(v, u);
        len[rt] = len[u] + 1;
        memcpy(anc[rt] + 1, anc[u], sizeof(anc[u]) - 4);
        dac(rt);
    }
    for(int i = 1; i < len[u]; i++) {
        rod[u][i] = caldis(u, anc[u][i]);
    }
}

inline void modify(int u, int d, int col, int id) {
    tmp.col = col; tmp.id = id;
    for(int i = 0; i < len[u]; i++) {
        int f = anc[u][i];
        tmp.dis = d - rod[u][i];
        if(tmp.dis >= 0) {
            while(!stk[f].empty() && stk[f].back() <= tmp) stk[f].pop_back();
            stk[f].push_back(tmp);
        }
    }
}

inline int query(int u) {
    Data res = {0, 0, 0};
    for(int i = 0; i < len[u]; i++) {
        int f = anc[u][i]; tmp.dis = rod[u][i];
        it = std::lower_bound(stk[f].rbegin(), stk[f].rend(), tmp);
        if(it != stk[f].rend() && res.id < it->id) res = *it;
    }
    return res.col;
}

int n, ut, vt, wt, m, op, dt, ct;

int main() {
    n = readint();
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint(); wt = readint();
        gra[ut].push_back(Edge {vt, wt});
        gra[vt].push_back(Edge {ut, wt});
    }
    dfs(1, 1);
    for(int k = 1; k < 20; k++) {
        for(int i = 1; i <= n; i++) {
            fa[i][k] = fa[fa[i][k - 1]][k - 1];
        }
    }
    msz[0] = INF;
    sum = n;
    rt = 0;
    getrt(1, 0);
    len[rt] = 1;
    dac(rt);
    m = readint();
    for(int i = 1; i <= m; i++) {
        op = readint();
        if(op == 1) {
            ut = readint(); dt = readint(); ct = readint();
            modify(ut, dt, ct, i);
        } else {
            ut = readint();
            printf("%d\n", query(ut));
        }
    }
    return 0;
}