BZOJ4719 [Noip2016]天天爱跑步
来源:互联网 发布:python命令行 编辑:程序博客网 时间:2024/05/07 17:57
Description
- 小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
- 这个游戏的地图可以看作一棵包含
N 个结点和N−1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1 到N 的连续正整数。 - 现在有
M 个玩家,第个玩家的起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的) - 小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。 在结点
j 的观察员会选择在第Wj 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第Wj 秒也理到达了结点j 。小C想知道每个观察员会观察到多少人? - 注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。 即对于把结点
j 作为终点的玩家:若他在第Wj 秒后到达终点,则在结点j 的观察员不能观察到该玩家;若他正好在第Wj 秒到达终点,则在结点j 的观察员可以观察到这个玩家。
Input
- 第一行有两个整数
N 和M 。其中N 代表树的结点数量,同时也是观察员的数量,M 代表玩家的数量。 - 接下来
N−1 行每行两个整数U 和V ,表示结点U 到结点V 有一条边。 - 接下来一行
N 个整数,其中第个整数为Wj ,表示结点出现观察员的时间。 - 接下来
M 行,每行两个整数Si 和Ti ,表示一个玩家的起点和终点。 - 对于所有的数据,保证
1≤Si,Ti≤N,0≤Wj≤N 。
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 个测试点,直接暴搜出每一条Si→Ti 路径,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 ,因为Si→LCAi 和LCAi→Ti 两段路径跑的方向不同,我们考虑拆开分别求这两种路径上的观察员观察到的情况,如果点LCAi 重复算了两遍再扣除掉。 - 对于路径
Si→LCAi 上的观察员(点)x ,当他能观察到玩家i 时必然满足deep[x]+Wx=deep[Si] ,那么问题就被转化为在以x 为根的子树内,有多少个玩家满足deep[x]+Wx=deep[Si] 并且x 在路径Si→LCAi 上。 - 这样似乎仍不好解决,我们再记一个桶
Dnum[j] ,表示满足deep[Si]=j 并且当前遍历到的点x 在路径Si→LCAi 上的玩家个数,那么我们每次遍历到一个点,只要查询一下对应的Dnum[deep[x]+Wx] 就可以了(deep[x]+Wx 可能会超出原树的最大深度,注意判断)。 - 考虑怎么统计
Dnum[j] (对于所有的路径Si→LCAi ):- 因为要只算到以
x 为根的子树内,Dnum 再开一维空间显然也不够,那我们只能对于所有子树共用一个数组,在遍历以x 为根的子树之前,先存储下当前的Dnum[deep[x]+Wx] ,遍历完后再与原来的相减才能加入最后的答案中。 - 每次遍历到任意一点
x ,我们必然要令Dnum[deep[x]] 加上Si=x 的玩家个数;查询完Dnum[deep[x]+Wx] 后,同样也要令每一个Ti=x 的玩家的Dnum[deep[Si]] 减一。
- 因为要只算到以
- 对于路径
LCAi→Ti ,我们用同样的方法统计。这时候观察员x 观察到玩家i 的条件就为deep[Ti]−deep[x]=Leni−Wx ,也就是deep[x]−Wx=deep[Ti]−leni 。 - 那么这时每次遍历到任意一点
x ,我们就要令每一个Ti=x 的玩家的Dnum[deep[Ti]−leni] 加一(这里Dnum 数组的意义与之前对于路径Si→LCAi 统计时有所不同,但也只是用一个桶统计对应的玩家个数),查询完Dnum[deep[x]−Wx] 后处理LCAi=x 的玩家也是同理(deep[Ti]−leni 可能为负数,因此可以统一加上3×105 令其为正)。 - 以下代码用
Tarjan 求LCA ,因此总复杂度为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);}
阅读全文
1 0
- BZOJ4719 [Noip2016]天天爱跑步
- bzoj4719 [Noip2016]天天爱跑步
- 【NOIP2016】bzoj4719 天天爱跑步
- 【BZOJ4719】[Noip2016]天天爱跑步
- BZOJ4719 [Noip2016]天天爱跑步
- bzoj4719 [Noip2016]天天爱跑步
- bzoj4719 [Noip2016]天天爱跑步
- bzoj4719 [Noip2016]天天爱跑步
- bzoj4719: [Noip2016]天天爱跑步
- 【bzoj4719】[Noip2016]天天爱跑步
- NOIP2016 && bzoj4719天天爱跑步
- [bzoj4719][树链剖分][Noip2016]天天爱跑步
- [题解]bzoj4719 NOIP2016天天爱跑步
- bzoj4719/洛谷1600 noip2016天天爱跑步
- BZOJ4719(NOIP2016)[天天爱跑步]--LCA+DFS
- NOIP2016 day1T2--BZOJ4719 天天爱跑步--LCA+差分
- [BZOJ4719][NOIP2016]天天爱跑步-LCA+树上差分
- 洛谷P1600 天天爱跑步(NOIp2016)(BZOJ4719)
- 分享Java基础知识:带你快速回顾常用基础知识
- 主席数
- Android数据库高手秘籍(五)——LitePal的存储操作
- React 2 hello world
- LightOJ
- BZOJ4719 [Noip2016]天天爱跑步
- 毛豆科技谈企业网站推广改如何做整体方案分析,干货!
- JavascriptDOM
- 相机标定原理(3)
- 数组 门牌号 的理解
- [设计模式](一):OOP设计原则
- 培养知识创收时代的技能型IT人才
- UIVIew设置圆角
- java多线程面试题