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));
    }
}
發佈了79 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章