NKOJ-Unknow 死亡的颂唱者

死亡的颂唱者

问题描述

老王沉迷于 lol 不能自拔, 召唤师峡谷是一个树状结构, 总共有 n 个节点
(3<=n<=1000), 其中每条边的长度均为 1, 红方英雄全部在叶子节点处, 每个叶子节点处都有一个红方英雄。
可惜的是蓝方英雄死亡颂唱者经济太过领先, 一个大招可以秒掉红方英雄。
死亡颂唱者的大招是, 在颂唱 k 秒后( 1<=k<=n), 对所有敌方英雄造成魔法伤害。
前面说了, 死亡颂唱者发育的非常好, 大招可以秒杀红方英雄。 但是红方为了不损失人头, 决定在峡谷中一些节点处建造一些温泉使得所有英雄都是安全的。 已有的一个基地温泉 s( 1<=s<=n), 死亡颂唱者颂唱 k 秒, 即红方英雄在 k 秒内到达任意一个温泉即是安全的, 英雄的移动速度是 1 个单位每秒。

输入文件

第一个数 t 为数据的组数。
对于每一组数据:
前三个数分别为 n, s, k
后面 n-1 行, 每行 2 个数 a, b。 即 a, b 之间有一条边。

输出文件

对于每组数据, 输出一行, 即最少需要修建的温泉数。

样例 1 输入

1 1
4 12 2
1 2
2 3
3 4
4 5
5 6
7 5
8 5
4 9
10 3
2 12
12 14
13 14
14 11

样例 1 输出

1

样例 2 输入

2 7
4 1
4 6
3 4
4 2
2 7
4 5
5 1
10 4 2
4 7
4 9
9 5
5 10
3 9
8 10
6 8
1 4
7 2

样例 2 输出

2 1

数据规模

对于 40%的数据, 3<=n<=50
对于 100%的数据, 3<=n<=1000 1<=k,s<=n 1<=t<=10

自己动手建泉水这个操作真是闻所未闻

题解

分析

不妨直接将初始泉水当做根节点

首先是建泉水的问题
由于我们要让一个泉水尽可能覆盖到更多的点
所以我们在针对某一个叶子节点建泉水时 应该是建在恰好覆盖它的位置
即 在当前叶子向上走k步的地方建泉水

这个样子就可以保证当前泉水的可以利用的最大覆盖范围为k(距离当前叶子节点距离为2k的点也可以恰好被覆盖到)

否则的话,距离当前叶子节点为2k的点就无法被当前泉水覆盖,这样就很有可能要多建一个泉水

接下来考虑覆盖点的问题
初始泉水的身边k个点肯定是都不用考虑了
那么我们下一个考虑的点应当是离已建泉水最远的那个未被覆盖的点(深度最深的那个点)

当前建的泉水(深度为dp)的覆盖范围是在当前子树深度在dp-k到dp+k的点
当你以最深的点为节点考虑时 对当前子树未覆盖点的覆盖直径是最有可能达到2*k
反之 假如你讨论的是其它点 那么dp-k到dp深度的点就会有更大的可能是已经被覆盖的

举个例子 k=2
对于链 1-2-3-4-5-6-7 1和2已经被覆盖
那么当我们考虑的点为6时 我们就会在4号点建泉水
    那么覆盖的点2-3-4-5-6中 2已经被覆盖 而7恰好没有被覆盖到
而考虑7时 在5号点建泉水
    覆盖点为3-4-5-6-7 恰好完全覆盖

具体解法

初始化

处理出每个点的深度
暴力覆盖根节点可以覆盖到的点(初始泉水覆盖的点)

覆盖

枚举所有点 将所有没有覆盖到的点按照深度从大到小排序
按顺序讨论每一个点 
    如果没有覆盖 就向上k步建泉水 暴力覆盖(结果+1)
    反之 则跳过

总结

这其实是一道非常简单的题目 几乎没有用到任何算法
唯一需要观察到的点就是 建泉水的顺序

在树上搞事情无非就是那么几点

LCA(最近公共祖先)
树形背包
树的直径相关(深度)
树形博弈

搜索(少见)

所以考虑树形的问题其实只需要考虑

动态规划(题目的提示会比较明显,而且情景很复杂 且点与点之间往往存在联系)
深度(一般就是打游戏 或者是涉及到点与点之间的距离什么的)
博弈(谁赢谁输 提示十分明显)

附上对拍代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;

inline int input()
{
    char c=getchar();int o;
    while(c>57||c<48)c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return o;
}

int k,n;
int all=0,star[2345],ent[2345],nxt[2345];
int fa[1234][12],dep[1234],wait[1234];
bool mark[1234];

bool cp(int a,int b){return dep[a]>dep[b];}

void add(int s,int e)
{
    nxt[++all]=star[s];
    star[s]=all;
    ent[all]=e;
}

void init(int ori)
{
    queue<int>go;
    int s,ndep;
    go.push(ori);
    while(go.size())
    {
        s=go.front();go.pop();ndep=dep[s];
        for(int i=1;i<=8;i++)fa[s][i]=fa[fa[s][i-1]][i-1];
        for(int bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
        if(!fa[e][0])
        {
            fa[e][0]=s;
            dep[e]=ndep+1;
            go.push(e);
        }
    }
}

int up(int pot)
{
    for(int i=0;i<=10;i++)
        if(k&(1<<i))pot=fa[pot][i];
    return pot;
}

void mk(int s,int dis,int fa)
{
    mark[s]=1;
    if(dis<k)
    for(int bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
        if(e!=fa)mk(e,dis+1,s);
}

int main()
{
    freopen("singer.in","r",stdin);
    freopen("singer.out","w",stdout);
    int T=input();
    while(T--)
    {
        all=0;
        int ori,s,e,res=0,Pot=0;
        n=input();ori=input();k=input();
        for(int i=1;i<n;i++)
        {
            s=input();e=input();
            add(s,e);add(e,s);
        }
        init(ori);
        mk(ori,0,0);
        for(int i=1;i<=n;i++)if(!mark[i])wait[++Pot]=i;
        sort(wait+1,wait+Pot+1,cp);
        for(int i=1;i<=Pot;i++)
        if(!mark[wait[i]])mk(up(wait[i]),0,0),res++;
        printf("%d\n",res);
        memset(wait,0,sizeof(wait));
        memset(fa,0,sizeof(fa));
        memset(dep,0,sizeof(dep));
        memset(nxt,0,sizeof(nxt));
        memset(star,0,sizeof(star));
        memset(mark,0,sizeof(mark));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值