BZOJ4719 [Noip2016]天天爱跑步

58 篇文章 0 订阅
3 篇文章 0 订阅

Description

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

Input

  • 第一行有两个整数 N N N M M M 。其中 N N N代表树的结点数量,同时也是观察员的数量, M M M代表玩家的数量。
  • 接下来 N − 1 N - 1 N1行每行两个整数 U U U V V V,表示结点 U U U到结点 V V V有一条边。
  • 接下来一行 N N N个整数,其中第个整数为 W j W_j Wj,表示结点出现观察员的时间。
  • 接下来 M M M行,每行两个整数 S i S_i Si T i T_i Ti,表示一个玩家的起点和终点。
  • 对于所有的数据,保证 1 ≤ S i , T i ≤ N , 0 ≤ W j ≤ N 1 \le S_i,T_i \le N,0 \le W_j \le N 1Si,TiN,0WjN

Output

  • 输出1行 N N 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 1 1号点, W 1 = 0 W_1=0 W1=0,故只有起点为 1 1 1号点的玩家才会被观察到,所以玩家 1 1 1和玩家 2 2 2被观察到,共 2 2 2人被观察到。
  • 对于 2 2 2号点,没有玩家在第 2 2 2秒时在此结点,共 0 0 0人被观察到。
  • 对于 3 3 3号点,没有玩家在第 5 5 5秒时在此结点,共 0 0 0人被观察到。
  • 对于 4 4 4号点,玩家 1 1 1被观察到,共 1 1 1人被观察到。
  • 对于 5 5 5号点,玩家 1 1 1被观察到,共 1 1 1人被观察到。
  • 对于 6 6 6号点,玩家 3 3 3被观察到,共 1 1 1人被观察到。

Problem Address

Solution

【45pts】暴搜 + 乱搞
  • 对于前 5 5 5个测试点,直接暴搜出每一条 S i → T i S_i \to T_i SiTi路径, O ( n m ) O(nm) O(nm)暴力统计即可
  • 对于第 9 9 9~ 12 12 12个测试点,因为所有的 S i = 1 S_i = 1 Si=1,我们令 1 1 1为树根,记 s u m [ x ] sum[x] sum[x]表示以 x x x为根的子树中 T i T_i Ti的个数,这样当遍历到任意一点 x x x时,就共有 s u m [ x ] sum[x] sum[x]个玩家在这一时刻跑到了点 x x x,直接判断加入答案即可,复杂度为 O ( n ) O(n) 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 + 树上差分 + 桶维护子树信息
  • 思路一直跟着人跑显然是会 T L E TLE TLE的,我们考虑把问题进行转化:
  • 我们先求出 S i , T i S_i,T_i Si,Ti的最近公共祖先记作 L C A i LCA_i LCAi,因为 S i → L C A i S_i \to LCA_i SiLCAi L C A i → T i LCA_i \to T_i LCAiTi两段路径跑的方向不同,我们考虑拆开分别求这两种路径上的观察员观察到的情况,如果点 L C A i LCA_i LCAi重复算了两遍再扣除掉。
  • 对于路径 S i → L C A i S_i \to LCA_i SiLCAi上的观察员(点) x x x,当他能观察到玩家 i i i时必然满足 d e e p [ x ] + W x = d e e p [ S i ] deep[x] + W_x = deep[S_i] deep[x]+Wx=deep[Si],那么问题就被转化为在以 x x x为根的子树内,有多少个玩家满足 d e e p [ x ] + W x = d e e p [ S i ] deep[x] + W_x = deep[S_i] deep[x]+Wx=deep[Si]并且 x x x在路径 S i → L C A i S_i \to LCA_i SiLCAi上。
  • 这样似乎仍不好解决,我们再记一个桶 D n u m [ j ] Dnum[j] Dnum[j],表示满足 d e e p [ S i ] = j deep[S_i] = j deep[Si]=j并且当前遍历到的点 x x x在路径 S i → L C A i S_i \to LCA_i SiLCAi上的玩家个数,那么我们每次遍历到一个点,只要查询一下对应的 D n u m [ d e e p [ x ] + W x ] Dnum[deep[x] + W_x] Dnum[deep[x]+Wx]就可以了( d e e p [ x ] + W x deep[x] + W_x deep[x]+Wx可能会超出原树的最大深度,注意判断)。
  • 考虑怎么统计 D n u m [ j ] Dnum[j] Dnum[j](对于所有的路径 S i → L C A i S_i \to LCA_i SiLCAi):
  1. 因为要只算到以 x x x为根的子树内, D n u m Dnum Dnum再开一维空间显然也不够,那我们只能对于所有子树共用一个数组,在遍历以 x x x为根的子树之前,先存储下当前的 D n u m [ d e e p [ x ] + W x ] Dnum[deep[x] + W_x] Dnum[deep[x]+Wx],遍历完后再与原来的相减才能加入最后的答案中。
  2. 每次遍历到任意一点 x x x,我们必然要令 D n u m [ d e e p [ x ] ] Dnum[deep[x]] Dnum[deep[x]]加上 S i = x S_i = x Si=x的玩家个数;查询完 D n u m [ d e e p [ x ] + W x ] Dnum[deep[x] + W_x] Dnum[deep[x]+Wx]后,同样也要令每一个 L C A i = x LCA_i = x LCAi=x的玩家的 D n u m [ d e e p [ S i ] ] Dnum[deep[S_i]] Dnum[deep[Si]]减一。
  • 对于路径 L C A i → T i LCA_i \to T_i LCAiTi,我们用同样的方法统计。这时候观察员 x x x观察到玩家 i i i的条件就为 d e e p [ T i ] − d e e p [ x ] = L e n i − W x deep[T_i] - deep[x] = Len_i - W_x deep[Ti]deep[x]=LeniWx,也就是 d e e p [ x ] − W x = d e e p [ T i ] − l e n i deep[x] - W_x = deep[T_i] - len_i deep[x]Wx=deep[Ti]leni
  • 那么这时每次遍历到任意一点 x x x,我们就要令每一个 T i = x T_i = x Ti=x的玩家的 D n u m [ d e e p [ T i ] − l e n i ] Dnum[deep[T_i] - len_i] Dnum[deep[Ti]leni]加一(这里 D n u m Dnum Dnum数组的意义与之前对于路径 S i → L C A i S_i \to LCA_i SiLCAi统计时有所不同,但也只是用一个桶统计对应的玩家个数),查询完 D n u m [ d e e p [ x ] − W x ] Dnum[deep[x] - W_x] Dnum[deep[x]Wx]后处理 L C A i = x LCA_i = x LCAi=x的玩家也是同理( d e e p [ T i ] − l e n i deep[T_i] - len_i deep[Ti]leni可能为负数,因此可以统一加上 3 × 1 0 5 3 \times 10^5 3×105令其为正)。
  • 以下代码用 T a r j a n Tarjan Tarjan L C A LCA LCA,因此总复杂度为 O ( n + m ) O(n + m) 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值