NOIP2016 天天爱跑步

题目链接

传送门

题目分析

虽然好像看起来不难,但是就是想不到啊
部分分感觉可以拿\(60pts\),但还没打代码验证完,分析暂时不放上来

2019/11/7 UPD:
把最后一档特殊点的部分分想了一下,这下应该\(80pts\)了,口胡一下部分分思路吧,不保证对

  • #1~#2:所有人起点等于自己终点:
    起点所在点\(w_i\)若为\(0\)则能观测到,否则不能,其他点不能

  • #3~#4:\(w_i = 0\)
    每个点上有起点则可被观测到,否则不能

  • #5:对于每条路径暴力模拟即可

  • #6~#8:树退化为一条链,,其中\(1\)\(2\)有边,\(2\)\(3\)有边,\(\cdots\)\(n - 1\)\(n\)有边
    考虑每个观测点\(i\),它能观测到的点只可能是来自\(i - w_i\)\(i + w_i\)两个点的,于是考虑在每个起点挂一个\(vector\)存储每条路径的长度,然后对于每个\(vector\)排一下序,对于每个查询直接定位到往前\(w_i\)个点和往后第\(w_i\)个点,在\(vector\)上二分找到第一个路径长度超过\(w_i\)的,然后更新答案,均摊复杂度\(O(nlog2(n))\)

  • #9~#12:对于每条路径的终点在树上打一下\(+1\)标记,类似树上差分,然后查询\(w_i = dep_i\)的点的子树和即可

  • #13~#16:类似上一档部分分,查询每个观测点\(i\)满足\(dep_i+w_i = dep_st\)的子树中节点的起点个数
    考虑在\(dfs\)序上做,记录一下每个节点的进栈序和出栈序,开桶维护\(dep\),扫描到每个进栈序的时候在桶中统计一下,对于一个节点的查询,记录它进栈和出栈时桶\(dep_i + w_i\)中的节点个数,相减得到增量即得到答案

\(Luogu\)题解第一页的@一扶苏一大佬写了每档部分分的分析,可能有些不一样,也可以去看他的

看一看\(NOIP2016\)年鉴,发现其实正解就是许多档部分分的想法的整合,这启示我们在拿到一道难题的时候,如果对正解暂时没有什么思路,可以考虑尽可能多的想部分分怎么打,一是你反正还是要对拍,二是部分分的做法可能内嵌\(std\)

\(\forall s_i = 1\)的部分分入手,发现\(\forall s_i = 1\)时其实就是对于每一条路径头尾差分一下,然后对于每个\(dep_i = w_i\)的点统计一下它的子树和
那么正解呢
考虑把每一条路径拆成上行和下行两条路径,感觉和上面的部分分有共通之处,能不能继续考虑差分呢
答案是可以的
先考虑上行,还是在起点打\(+1\)标记,但是这样还不行,万一在\(LCA\)的地方拐下去了呢,所以还要在\(LCA\)的地方减一下
考虑这样的话一个点要满足什么条件
设起点为\(st\),那么有\(dep_st - w_i = dep_i\)时,从起点出发,跑过这个点的时候可以被观测到
移项一下 \(dep_st = dep_i + w_i\),即在\(dep_st\)这一层上打一下标记,查询时查询\(dep_i + w_i\)这层的子树和
下行类似,在终点\(ed\)处打标记,柿子列出来发现是\(Len - (dep_ed - dep_i) = w_i\),其中\(Len\)指跑步的路径长度
移项一下是\(Len - dep_ed = w_i - dep_i\),然后同上打一下标记,注意为了保证\(LCA\)处的贡献可以正常统计,上行和下行的\(-1\)标记有一个要打在\(fa[LCA]\)
对于每一层子树和的统计,我们对着每一个\(dep\)开一棵线段树,然后动态开点一下,查询子树和就是\(dfs\)序上连续一段

#include<bits/stdc++.h>
#define N (600000 + 10)
using namespace std;
inline int read() {
    int cnt = 0, f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
    while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
    return cnt * f;
}
int n, m, u, v, w[N], tot;
struct node {int s, t, l;} a[N];
int nxt[N], first[N], to[N];
void add(int x, int y) {nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
int fa[N], siz[N], dep[N], son[N], top[N], num[N], idx;
void dfs1(int cur, int father) {
    fa[cur] = father, dep[cur] = dep[father] + 1, siz[cur] = 1;
    for (register int i = first[cur]; i; i = nxt[i]) {
        int v = to[i];
        if (v == father) continue;
        dfs1(v, cur);
        siz[cur] += siz[v];
        if (siz[son[cur]] < siz[v]) son[cur] = v;
    }
}
void dfs2(int cur, int tp) {
    top[cur] = tp, num[cur] = ++idx;
    if (son[cur]) dfs2(son[cur], tp);
    for (register int i = first[cur]; i; i = nxt[i]) {
        int v = to[i];
        if (num[v]) continue;
        dfs2(v, v);
    }
}
int lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    } return dep[u] < dep[v] ? u : v;
}
int ans[N];
int now, rt[N], ls[N * 20], rs[N * 20], val[N * 20];
void modify(int &x, int l, int r, int pos, int v) {
    if (!x) x = ++now; val[x] += v; if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) modify(ls[x], l, mid, pos, v);
    else modify(rs[x], mid + 1, r, pos, v);
}
int query(int x, int l, int r, int L, int R) {
    if (!x) return 0; if (L <= l && R >= r) return val[x];
    int mid = (l + r) >> 1, ans = 0;
    if (L <= mid) ans += query(ls[x], l, mid, L, R);
    if (R > mid) ans += query(rs[x], mid + 1, r, L, R);
    return ans;
}
void clear() {for (register int i = 1; i <= now; ++i) ls[i] = rs[i] = val[i] = 0; memset(rt, 0, sizeof rt); now = 0;}
int main() {
    n = read(), m = read();
    for (register int i = 1; i < n; ++i) {
        u = read(), v = read();
        add(u, v), add(v, u);
    }
    dfs1(1, 0), dfs2(1, 1);
    for (register int i = 1; i <= n; ++i) w[i] = read();
    for (register int i = 1; i <= m; ++i) a[i].s = read(), a[i].t = read(), a[i].l = lca(a[i].s, a[i].t);
    /*-----------go up------------*/ 
    for (register int i = 1; i <= m; ++i) {
        modify(rt[dep[a[i].s]], 1, n, num[a[i].s], 1);
        if (fa[a[i].l]) modify(rt[dep[a[i].s]], 1, n, num[fa[a[i].l]], -1);
    }
    for (register int i = 1; i <= n; ++i) ans[i] = query(rt[dep[i] + w[i]], 1, n, num[i], num[i] + siz[i] - 1);
    /*----------go up-------------*/
    clear();
    /*----------go down -----------*/
    for (register int i = 1; i <= m; ++i) {
        int Len = dep[a[i].t] + dep[a[i].s] - 2 * dep[a[i].l];
        int cur = Len - dep[a[i].t] + n;
        modify(rt[cur], 1, n, num[a[i].t], 1);
        modify(rt[cur], 1, n, num[a[i].l], -1);
    }
    for (register int i = 1; i <= n; ++i) ans[i] += query(rt[w[i] - dep[i] + n], 1, n, num[i], num[i] + siz[i] - 1);
    for (register int i = 1; i <= n; ++i) printf("%d ", ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值