洛谷P1600 天天爱跑步

题目描述

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

这个游戏的地图可以看作一一棵包含 nn 个结点和 n-1n1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从11 到nn 的连续正整数。

现在有mm 个玩家,第ii 个玩家的起点为 S_iSi ,终点为 T_iTi 。每天打卡任务开始时,所有玩家在第00 秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)

小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点jj 的观察员会选择在第W_jWj 秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第W_jWj 秒也理到达了结点 jj 。 小C想知道每个观察员会观察到多少人?

注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点jj 作为终点的玩家: 若他在第W_jWj 秒前到达终点,则在结点jj 的观察员不能观察到该玩家;若他正好在第W_jWj 秒到达终点,则在结点jj 的观察员可以观察到这个玩家。

输入输出格式

输入格式:

第一行有两个整数nn 和mm 。其中nn 代表树的结点数量, 同时也是观察员的数量, mm 代表玩家的数量。

接下来 n- 1n1 行每行两个整数uu 和 vv ,表示结点 uu 到结点 vv 有一条边。

接下来一行 nn 个整数,其中第jj 个整数为W_jWj , 表示结点jj 出现观察员的时间。

接下来 mm 行,每行两个整数S_iSi ,和T_iTi ,表示一个玩家的起点和终点。

对于所有的数据,保证1\leq S_i,T_i\leq n, 0\leq W_j\leq n1Si,Tin,0Wjn 。

输出格式:

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

输入输出样例

输入样例#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 
输出样例#1: 
2 0 0 1 1 1 
输入样例#2:  复制
5 3 
1 2 
2 3 
2 4 
1 5 
0 1 0 3 0 
3 1 
1 4
5 5 
输出样例#2:  复制
1 2 1 0 1 

说明

【样例1说明】

对于1号点,W_i=0Wi=0 ,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共有2人被观察到。

对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。

对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。

对于4号点,玩家1被观察到,共1人被观察到。

对于5号点,玩家1被观察到,共1人被观察到。

对于6号点,玩家3被观察到,共1人被观察到。

【子任务】

每个测试点的数据规模及特点如下表所示。 提示: 数据范围的个位上的数字可以帮助判断是哪一种数据类型。

LCA+树上差分+桶。。。

set[x] : 以x为LCA的路径的起点的集合。

num[x]: 以x为路径起点的路径条数。

set2[x]: 以x为终点的路径的起点集合。

set3[x]: 以x为LCA的路径的终点的集合。

正解并不是对一个个玩家进行操作。

而是先对全部玩家进行一些预处理,然后用两个类似的dfs函数对整棵树处理。

最后再做一些微调,就输出答案。

对于玩家在树上的路径(u,v)

我们可以对其进行拆分。

拆分成: u ---> LCA(u,v) 与 LCA(u,v) ---> v 两条路径。

对于这一步,因为我们在一开始已经说明是先对每个玩家进行预处理,LCA 我用了树链剖分

我们先考虑 u ---> LCA(u,v) 这条路径,这是一条向“上”跑的路径。

对与这条路径上的点i来说,当且仅当deep[i]+w[i] = deep[u]时,u节点对i节点是有贡献的。

那么也就是说,只要符合deep[i]+w[i]的全部是玩家起点的点,就能对i点产生贡献。

ans[i]加上的其实就是i的子树对i的贡献,因为我们在处理好子树之后的,我们已经处理好了对i有影响的节点,

所以我们只要加上先后之间的桶差值就相当于统计了答案。

其作用是删去桶中以i为LCA的路径的起点深度桶的值,因为当我们遍历完i节点的孩子时,

对于以i节点为LCA的路径来说,这条路径上的信息对i的祖先节点是不会有影响的。

所以要将其删去。

我们再来考虑向下的路径,即LCA(u,v) --->v。

对于向下走的路径,我们也思考,在什么条件下,这条路径上的点会获得贡献呢?

很明显的,当 dis(u,v)-deep[v] = w[i]-deep[i] 等式成立的时候,这条路径将会对i点有贡献。

对于桶bucket来说,我们在计算的过程中其下标可能是负值,所以我们在操作桶时要将其下标右移 n, 即点数。

·如果一条路径的LCA能观察到这条路上的人,我们还需将该LCA去重。

即: if(deep[u] == deep[lca]+w[i])ans[lca]--;

附上苦心敲出的炒鸡恶心的代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#define MAXN 300010
using namespace std;
vector<int> set1[MAXN],set2[MAXN],set3[MAXN];
int n,m,c=1,d=1;
int head[MAXN],deep[MAXN],son[MAXN],size[MAXN],fa[MAXN],id[MAXN],top[MAXN];
int w[MAXN],dis[MAXN],num[MAXN],ans[MAXN],devote[MAXN<<1];
bool vis[MAXN];
struct node1{
    int next,to;
}a[MAXN<<1];
struct node2{
    int s,t,lca,dis;
}b[MAXN];
inline int read(){
	int date=0,w=1;char c=0;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
	return date*w;
}
inline void add(int x,int y){
    a[c].to=y;
    a[c].next=head[x];
    head[x]=c++;
    a[c].to=x;
    a[c].next=head[y];
    head[y]=c++;
}
void dfs1(int rt){
    son[rt]=0;size[rt]=1;
    for(int i=head[rt];i;i=a[i].next){
        int will=a[i].to;
        if(!deep[will]){
            deep[will]=deep[rt]+1;
            dis[will]=dis[rt]+1;
            fa[will]=rt;
            dfs1(will);
            size[rt]+=size[will];
            if(size[son[rt]]<size[will])son[rt]=will;
        }
    }
}
void dfs2(int rt,int f){
    id[rt]=d++;top[rt]=f;
    if(son[rt])dfs2(son[rt],f);
    for(int i=head[rt];i;i=a[i].next){
        int will=a[i].to;
        if(will!=fa[rt]&&will!=son[rt])
        dfs2(will,will);
    }
}
int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    if(deep[x]>deep[y])swap(x,y);
    return x;
}
void dfs3(int rt){
    vis[rt]=true;
    int x=devote[deep[rt]+w[rt]+n];
    for(int i=head[rt];i;i=a[i].next){
        int v=a[i].to;
        if(!vis[v])dfs3(v);
    }
    devote[deep[rt]+n]+=num[rt];
    ans[rt]+=devote[deep[rt]+w[rt]+n]-x;
    int l=set1[rt].size();
    for(int i=0;i<l;i++)devote[deep[set1[rt][i]]+n]--;
    vis[rt]=false;
}
void dfs4(int rt){
    vis[rt]=true;
    int x=devote[w[rt]-deep[rt]+n];
    for(int i=head[rt];i;i=a[i].next){
        int v=a[i].to;
        if(!vis[v])dfs4(v);
    }
    int l=set2[rt].size();
    for(int i=0;i<l;i++)devote[set2[rt][i]+n]++;
    ans[rt]+=devote[w[rt]-deep[rt]+n]-x;
    l=set3[rt].size();
    for(int i=0;i<l;i++)devote[set3[rt][i]+n]--;
    vis[rt]=false;
}
void work(){
    for(int i=1;i<=m;i++){
        b[i].s=read();b[i].t=read();
        b[i].lca=LCA(b[i].s,b[i].t);
        b[i].dis=dis[b[i].s]+dis[b[i].t]-dis[b[i].lca]*2;
        num[b[i].s]++;
        set1[b[i].lca].push_back(b[i].s);
        set2[b[i].t].push_back(b[i].dis-deep[b[i].t]);
        set3[b[i].lca].push_back(b[i].dis-deep[b[i].t]);
    }
    dfs3(1);
    dfs4(1);
    for(int i=1;i<=m;i++)if(deep[b[i].s]==deep[b[i].lca]+w[b[i].lca])ans[b[i].lca]--;
    for(int i=1;i<=n;i++)printf("%d ",ans[i]);
}
void init(){
    int x,y;
    n=read();m=read();
    for(int i=1;i<n;i++){
        x=read();y=read();
        add(x,y);
    }
    for(int i=1;i<=n;i++){
		w[i]=read();
		ans[i]=0;
	}
    deep[1]=1;dis[1]=0;
    dfs1(1);
    dfs2(1,1);
}
int main(){
    init();
    work();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值