NOIP2016 DAY1T2 天天爱跑步

1 篇文章 0 订阅
1 篇文章 0 订阅

NOIP2016 DAY1T2 天天爱跑步

描述
小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含n个结点和n - 1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。
现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家 在第0秒 同时从 自己的起点 出发,以 每秒跑一条边 的速度,不间断地沿着最短路径向着 自己的终点 跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)
小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点j的观察员会选择在第Wj秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也 正好 到达了结点j。小C想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。即对于把结点j作为终点的玩家:若他在第Wj秒前到达 终点,则在结点j的观察员 不能观察到 该玩家;若他 正好 在第Wj秒到达终点,则在结点j的观察员 可以观察到 这个玩家。
格式
输入格式
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量, m代表玩家的数量。
接下来n - 1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。 接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1 <= Si, Ti <= n,0 <= Wj <= n。
输出格式
输出1行n个整数,第j个整数表示结点j的观察员可以观察到多少人。
样例1
样例输入1
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
Copy
样例输出1
2 0 0 1 1 1
Copy
样例2
样例输入2
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5
Copy
样例输出2
1 2 1 0 1
Copy
限制
每个测试点时限2秒。
【子任务】
每个测试点的数据规模及特点如下表所示。提示:数据范围的个位上的数字可以帮助判断是哪一种数据类型。

提示
【样例1说明】
对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
对于4号点,玩家1被观察到,共1人被观察到。
对于5号点,玩家2被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。
来源
NOIP 2016 提高组 Day 1 第二题

1、思路:lca + 路径分解 + 线段树
2、约定:
s:起点
t:终点
lca:s,t的最近公共祖先
w:题目中的w
dep:结点的深度
ans:结点i的答案
3、事实:
(1)s->lca : w[i]+dep[i]=dep[s]
t->lca : w[i]-dep[i]+大整数=dep[s]-2*dep[lca]+大整数
(2)ans[i] = | {w[i]+dep[i]=dep[s] | i在s->lca的路径上} | + | {w[i]-dep[i]+大整数=dep[s]-2*dep[lca]+大整数 | i在s->lca->t的路径上} |
4、过程:
dfs:获得树的基本信息

lca:计算s,t的公共祖先
(1)将链以lca为分界点断开。实现时应注意细节。

rmq:支持单点修改单点求值。
(1)声明两个线段树分别维护s->lca,lca的下一个结点->t的两段路径

dfs2:以dfs序在线维护ans值。
(1)访问结点x,并记录此时ans[x]的值为ans1
(2)for x 的儿子child != x的父亲
——(2.1)访问结点child
(3)将在x开始的路径加入两个线段树
(4)记录此时ans[x]的值为ans2
(5)ans[x] = ans2 - ans1(排除其它子树对结果的影响)
(6)将在x结束的路径移出两个线段树
*黑体字是较难想到的,也是此题难点。
5、时间复杂度:O(nlogn)
6、反思
(1)矛盾的特殊性寓于普遍性中:s->lca : w[i]+dep[i]=dep[s]
;t->lca : w[i]-dep[i]+大整数=dep[s]-2*dep[lca]+大整数
(2)通过与已有的模型比较发现矛盾的特殊性:lca两侧路径方向不同,想到拆路径。为了用rmq将等式变量分开到等号两侧。
(3)排除其它子树的影响:应用前缀和的思想。
(4)曾经的错误:未排除子树的影响;值域rmq的值域范围给小了;lca的细节。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int MAXN = 300000 + 10;
const int VAL = MAXN * 2;

inline int read() {
    int x = 0; char ch = getchar();
    while (ch<'0'||ch>'9') ch = getchar();
    while ('0'<=ch && ch <= '9') {
      x = x * 10 + ch - '0';
      ch = getchar();
    }
    return x;
}

int n;
int w[MAXN];        //观察员出现时间 
int f[MAXN][40];    //倍增数组 
int dep[MAXN];      //结点的深度 
int ans[MAXN];      //第i个结点的答案 
vector<int> map[MAXN], node[MAXN], node2[MAXN], node3[MAXN], node4[MAXN]; 

void dfs(int x, int fa, int depth) {
    dep[x] = depth;
    for (int i = 0; i < map[x].size(); ++i)
      if (map[x][i] != fa) {
        dfs(map[x][i], x, depth + 1);
        f[map[x][i]][0] = x;
      }
}

int lca1;

int LCA(int l, int r) {
    if (l == r) return l;
    int flag = 1;
    if (dep[l] < dep[r]) swap(l, r), flag = 0;
    for (int i = 30; i >= 0; --i)
      if (dep[f[l][i]] > dep[r])
        l = f[l][i];
    lca1 = flag == 1 ? r : l;
    if (dep[l] != dep[r]) l = f[l][0];
    if (l == r) return l;
    for (int i = 30; i >= 0; --i)
      if (f[l][i] != f[r][i])
        l = f[l][i], r = f[r][i];
    lca1 = flag == 1 ? r : l;
    return f[l][0];
}

struct RMQ{
    int tree[4000000];

    RMQ() {
        memset(tree, 0, sizeof tree);
    }

    void update(int l, int r, int id, int x, int add) {
        if (l == r) { tree[id] += add; return; }
        int mid = l + r >> 1;
        if (x <= mid) update(l, mid, id << 1, x, add);
        else update(mid + 1, r, (id << 1) + 1, x, add); 
        tree[id] = tree[id << 1] + tree[(id << 1) + 1];
    }

    int query(int l, int r, int id, int x) {
        if (l == r) return tree[id];
        int mid = l + r >> 1;
        if (x <= mid) return query(l, mid, id << 1, x);
        else return query(mid + 1, r, (id << 1) + 1, x);
    }
}rmq, rmq2;

/*
    node4[x] : x有开始跑的人的dep(s)
    node3[x] : x有跑到的人的dep(s) - 2*dep(lca) + MAXN
    node2[lca] : dep(s) - 2*dep(lca) + MAXN
    node [lca] : dep(s)
*/

void dfs2(int x, int fa) {
    ans[x] -= rmq.query(0, MAXN, 1, w[x]+dep[x]) + rmq2.query(0, VAL, 1, w[x]-dep[x]+MAXN);
    for (int i = 0; i < map[x].size(); ++i) {
      int y = map[x][i];
      if (fa == y) continue;
      dfs2(y, x);
    }
    for (int i = 0; i < node4[x].size(); ++i)
      rmq.update(0, MAXN, 1, node4[x][i], 1);
    for (int i = 0; i < node3[x].size(); ++i)
      rmq2.update(0, VAL, 1, node3[x][i], 1);
    ans[x] += rmq.query(0, MAXN, 1, w[x]+dep[x]) + rmq2.query(0, VAL, 1, w[x]-dep[x]+MAXN);
    for (int i = 0; i < node[x].size(); ++i)
      rmq.update(0, MAXN, 1, node[x][i], -1);
    for (int i = 0; i < node2[x].size(); ++i)
      rmq2.update(0, VAL, 1, node2[x][i], -1);
}

int main()
{
    freopen("in.txt", "r", stdin);
    freopen("out2.txt", "w", stdout);
    int m, u, v, s, t;
    n = read(), m = read();
    for (int i = 1; i < n; ++i) {
      u = read(), v = read();
      map[u].push_back(v);
      map[v].push_back(u);
    }
    for (int i = 1; i <= n; ++i)
      w[i] = read();
    dfs(1, -1, 1);
    for (int i = 1; i <= 30; ++i)
      for (int j = 1; j <= n; ++j)
        f[j][i] = f[f[j][i-1]][i-1];
    for (int i = 1; i <= m; ++i) {
      s = read(), t = read();
      int lca = LCA(s, t);
      node[lca].push_back(dep[s]);
      node4[s].push_back(dep[s]);
      if (t == lca) continue;
      node2[lca1].push_back(dep[s] - dep[lca] - dep[lca] + MAXN);
      node3[t].push_back(dep[s] - dep[lca] - dep[lca] + MAXN);
    }
    dfs2(1, -1);
    for (int i = 1; i <= n; ++i)
      printf("%d ", ans[i]);
    return 0;
}

也许,是一个新的开始的时候了,无论何人何时何地。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值