BZOJ4719 [Noip2016]天天爱跑步

来源:互联网 发布:python命令行 编辑:程序博客网 时间:2024/05/07 17:57

Description

  • 小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
  • 这个游戏的地图可以看作一棵包含N个结点和N1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1N的连续正整数。
  • 现在有M个玩家,第个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的)
  • 小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。 在结点j的观察员会选择在第Wj秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点j。小C想知道每个观察员会观察到多少人?
  • 注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。 即对于把结点j作为终点的玩家:若他在第Wj秒后到达终点,则在结点j的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点j的观察员可以观察到这个玩家。

Input

  • 第一行有两个整数NM 。其中N代表树的结点数量,同时也是观察员的数量,M代表玩家的数量。
  • 接下来N1行每行两个整数UV,表示结点U到结点V有一条边。
  • 接下来一行N个整数,其中第个整数为Wj,表示结点出现观察员的时间。
  • 接下来M行,每行两个整数SiTi,表示一个玩家的起点和终点。
  • 对于所有的数据,保证1Si,TiN,0WjN

Output

  • 输出1行N个整数,第个整数表示结点的观察员可以观察到多少人。

Sample Input

  • 6 3
    2 3
    1 2
    1 4
    4 5
    4 6
    0 2 5 1 2 3
    1 5
    1 3
    2 6

Sample Output

  • 2 0 0 1 1 1

Sample Explaintion

  • 对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
  • 对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
  • 对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
  • 对于4号点,玩家1被观察到,共1人被观察到。
  • 对于5号点,玩家1被观察到,共1人被观察到。
  • 对于6号点,玩家3被观察到,共1人被观察到。

Data Range

Problem Address

  • BZOJ4719 洛谷P1600 UOJ#261

Solution

【45pts】暴搜 + 乱搞

  • 对于前5个测试点,直接暴搜出每一条SiTi路径,O(nm)暴力统计即可
  • 对于第9~12个测试点,因为所有的Si=1,我们令1为树根,记sum[x]表示以x为根的子树中Ti的个数,这样当遍历到任意一点x时,就共有sum[x]个玩家在这一时刻跑到了点x,直接判断加入答案即可,复杂度为O(n)

Code-1

#include <iostream>#include <cstdio>using namespace std;const int N = 3e5 + 5, M = 6e5 + 5;int w[N], Ans[N], Del[N]; bool vis[N], f;int n, m, s[N], t[N];struct Edge{    int to; Edge *nxt;}a[M], *T = a, *lst[N];inline int get(){    char ch; int res;    while ((ch = getchar()) < '0' || ch > '9');    res = ch - '0';    while ((ch = getchar()) >= '0' && ch <= '9')     res = (res << 3) + (res << 1) + ch - '0';    return res;}inline void put(int x){    if (x > 9) put(x / 10);    putchar(x % 10 + 48);}inline void addEdge(const int &x, const int &y){    (++T)->nxt = lst[x]; lst[x] = T; T->to = y;    (++T)->nxt = lst[y]; lst[y] = T; T->to = x;}inline void Dfs1(const int &x, const int &I){    if (x == t[I]) return (void)(f = true);    int y;    for (Edge *e = lst[x]; e; e = e->nxt)    if (!vis[y = e->to])    {        vis[y] = true;        Dfs1(y, I);        if (f) return ;        vis[y] = false;    }}inline void Dfs2(const int &x, const int &st){    if (w[x] == st) Ans[x]++; int y;    for (Edge *e = lst[x]; e; e = e->nxt)    if (vis[y = e->to])    {        vis[y] = false;        Dfs2(y, st + 1);    }}inline void Dfs3(const int &x, const int &fa, const int &st){    if (w[x] == st) Ans[x] += Del[x]; int y;    for (Edge *e = lst[x]; e; e = e->nxt)     if ((y = e->to) != fa) Dfs3(y, x, st + 1);}inline void Dfs4(const int &x, const int &fa){    int y;    for (Edge *e = lst[x]; e; e = e->nxt)     if ((y = e->to) != fa)      {         Dfs4(y, x);         Del[x] += Del[y];     }}int main(){    n = get(); m = get();    for (int i = 1; i < n; ++i) addEdge(get(), get());    for (int i = 1; i <= n; ++i) w[i] = get();    for (int i = 1; i <= m; ++i) s[i] = get(), t[i] = get();    if (n <= 993)    {        for (int i = 1; i <= m; ++i)        {            f = false;             vis[s[i]] = true; Dfs1(s[i], i);             vis[s[i]] = false; Dfs2(s[i], 0);        }    }    else    {        for (int i = 1; i <= m; ++i) Del[t[i]]++;        Dfs4(1, 0); Dfs3(1, 0, 0);    }    for (int i = 1; i <= n; ++i)      put(Ans[i]), putchar(' ');}

【100pts】LCA + 树上差分 + 桶维护子树信息

  • 思路一直跟着人跑显然是会TLE的,我们考虑把问题进行转化:
  • 我们先求出Si,Ti的最近公共祖先记作LCAi,因为SiLCAiLCAiTi两段路径跑的方向不同,我们考虑拆开分别求这两种路径上的观察员观察到的情况,如果点LCAi重复算了两遍再扣除掉。
  • 对于路径SiLCAi上的观察员(点)x,当他能观察到玩家i时必然满足deep[x]+Wx=deep[Si],那么问题就被转化为在以x为根的子树内,有多少个玩家满足deep[x]+Wx=deep[Si]并且x在路径SiLCAi上。
  • 这样似乎仍不好解决,我们再记一个桶Dnum[j],表示满足deep[Si]=j并且当前遍历到的点x在路径SiLCAi上的玩家个数,那么我们每次遍历到一个点,只要查询一下对应的Dnum[deep[x]+Wx]就可以了(deep[x]+Wx可能会超出原树的最大深度,注意判断)。
  • 考虑怎么统计Dnum[j](对于所有的路径SiLCAi):
    1. 因为要只算到以x为根的子树内,Dnum再开一维空间显然也不够,那我们只能对于所有子树共用一个数组,在遍历以x为根的子树之前,先存储下当前的Dnum[deep[x]+Wx],遍历完后再与原来的相减才能加入最后的答案中。
    2. 每次遍历到任意一点x,我们必然要令Dnum[deep[x]]加上Si=x的玩家个数;查询完Dnum[deep[x]+Wx]后,同样也要令每一个Ti=x的玩家的Dnum[deep[Si]]减一。
  • 对于路径LCAiTi,我们用同样的方法统计。这时候观察员x观察到玩家i的条件就为deep[Ti]deep[x]=LeniWx,也就是deep[x]Wx=deep[Ti]leni
  • 那么这时每次遍历到任意一点x,我们就要令每一个Ti=x的玩家的Dnum[deep[Ti]leni]加一(这里Dnum数组的意义与之前对于路径SiLCAi统计时有所不同,但也只是用一个桶统计对应的玩家个数),查询完Dnum[deep[x]Wx]后处理LCAi=x的玩家也是同理(deep[Ti]leni可能为负数,因此可以统一加上3×105令其为正)。
  • 以下代码用TarjanLCA,因此总复杂度为O(n+m)(并查集复杂度忽略不计)

Code-2

#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int N = 3e5 + 5, M = N << 1;int lca[N], Ans[N], st[N], ed[N], fa[N], dep[N];int n, m, D, w[N], Dnu[M], num[N]; bool vis[N];char frd[N], *hed = frd + N;char fwt[M << 1], *opt = fwt;const char *tal = hed;inline char nxtChar(){    if (hed == tal)     fread(frd, 1, N, stdin), hed = frd;    return *hed++; }inline int get(){    char ch; int res = 0;    while ((ch = nxtChar()) < '0' || ch > '9');    res = ch ^ 48;    while ((ch = nxtChar()) >= '0' && ch <= '9')     res = (res << 3) + (res << 1) + (ch ^ 48);    return res;}inline void put(int x){    if (x > 9) put(x / 10);    *opt++ = x % 10 + 48;}struct Edge {int to; Edge *nxt;};struct Query {int wit, num; Query *nxt;};Edge p[M], *T = p, *lst[N];Edge a[N], *A = a, *Ast[N];Edge b[N], *B = b, *Bst[N];Edge c[N], *C = c, *Cst[N];Query q[M], *Q = q, *rst[N];inline void AddEdge(const int &x, const int &y){    (++T)->nxt = lst[x]; lst[x] = T; T->to = y;    (++T)->nxt = lst[y]; lst[y] = T; T->to = x;}inline void AskEdge(const int &x, const int &y, const int &I){    (++Q)->nxt = rst[x]; rst[x] = Q; Q->wit = y; Q->num = I;    (++Q)->nxt = rst[y]; rst[y] = Q; Q->wit = x; Q->num = I; }inline void AjnEdge(const int &x, const int &y) {(++A)->nxt = Ast[x]; Ast[x] = A; A->to = y;}inline void BjnEdge(const int &x, const int &y) {(++B)->nxt = Bst[x]; Bst[x] = B; B->to = y;}inline void CjnEdge(const int &x, const int &y) {(++C)->nxt = Cst[x]; Cst[x] = C; C->to = y;}inline void CkMax(int &x, const int &y) {if (x < y) x = y;} inline int Find(const int &x){    if (fa[x] != x) fa[x] = Find(fa[x]);    return fa[x];}inline void Tarjan(const int &x, const int &F){    CkMax(D, dep[x] = dep[F] + 1); fa[x] = x; int y, z;    for (Edge *e = lst[x]; e; e = e->nxt)     if ((y = e->to) != F) Tarjan(y, x), fa[y] = x;    vis[x] = true;    for (Query *e = rst[x]; e; e = e->nxt)     if (vis[y = e->wit] && !lca[z = e->num]) lca[z] = Find(y);}inline void Dfs1(const int &x, const int &F){    int u = dep[x] + w[x], v, y; if (u <= D) v = Dnu[u];    for (Edge *e = lst[x]; e; e = e->nxt)     if ((y = e->to) != F) Dfs1(y, x);    Dnu[dep[x]] += num[x]; if (u <= D) Ans[x] += Dnu[u] - v;    for (Edge *e = Ast[x]; e; e = e->nxt) Dnu[e->to]--;}inline void Dfs2(const int &x, const int &F){    int u = dep[x] - w[x] + N, v = Dnu[u], y;    for (Edge *e = lst[x]; e; e = e->nxt)     if ((y = e->to) != F) Dfs2(y, x);    for (Edge *e = Bst[x]; e; e = e->nxt) Dnu[e->to]++;    Ans[x] += Dnu[u] - v;    for (Edge *e = Cst[x]; e; e = e->nxt) Dnu[e->to]--;  }int main(){    n = get(); m = get();    for (int i = 1; i < n; ++i) AddEdge(get(), get());    for (int i = 1; i <= n; ++i) w[i] = get();    for (int i = 1; i <= m; ++i)    {        st[i] = get(); ed[i] = get(); num[st[i]]++;        AskEdge(st[i], ed[i], i);    }    Tarjan(1, 0);    for (int i = 1; i <= m; ++i)    {        int v = dep[st[i]] + dep[ed[i]] - (dep[lca[i]] << 1);        AjnEdge(lca[i], dep[st[i]]);        BjnEdge(ed[i], dep[ed[i]] - v + N);        CjnEdge(lca[i], dep[ed[i]] - v + N);    }    Dfs1(1, 0); memset(Dnu, 0, sizeof(Dnu)); Dfs2(1, 0);    for (int i = 1; i <= m; ++i)     if (dep[st[i]] == dep[lca[i]] + w[lca[i]]) Ans[lca[i]]--;     for (int i = 1; i < n; ++i)     put(Ans[i]), *opt++ = ' '; put(Ans[n]);    fwrite(fwt, 1, opt - fwt, stdout);}
原创粉丝点击