1. 程式人生 > 遊戲資訊 >【狼人殺板子介紹】12人燭銘之勳

【狼人殺板子介紹】12人燭銘之勳

原題連結P4092 [HEOI2016/TJOI2016]樹

分析

寫這篇題解是因為好像用了個跟大家都不一樣的思路去寫了這道題目。

這題用的知識點依舊是:樹剖+線段樹

關鍵在於,我們維護的是什麼?在這裡,我們考慮維護一個區間是否存在染色的點

接下來,我們分別說說這兩個操作中具體怎麼操作。

標記操作

這裡很好說,我們只用進行單點修改即可

void modify(int u,int x)
{
    if(tr[u].l==tr[u].r)//當查詢到對應結點時停止。
    {
        tr[u].st = 1;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x<=mid) modify(u<<1,x);
    else modify(u<<1|1,x);
    pushup(u);
}

詢問操作

接下來,就是重頭戲了。我們如何進行詢問?

在這裡我用了類似於深搜的方式進行詢問。

對於一個點y,我們想求其最近的被染色的祖先,需要我們不斷的沿鏈向上回溯。

當然,這步很基礎。接下來,我們具體說說如何實現搜尋式的查詢。

  • 想找到距離y最近的被染色祖先,那我們在遞迴時就先從線段的右兒子找起(若右兒子有被染色的節點)則我們直接向其中遞迴,找到那個節點。
  • 否則,若是右兒子裡,我們就去左兒子裡面找(如果左兒子中有的話)
  • 若左右兒子都沒有的話,我們直接返回-1表示該區間沒有我們想要找到的點

我們來看看程式碼。

int query(int u,int l,int r)
{
    if(tr[u].l==tr[u].r) return rid[tr[u].l];//rid表示的是,線上段樹中的節點,在實際的樹中的編號。
    int mid = tr[u].l + tr[u].r >> 1;
    int res = -1;//初始化為-1
    if(r>mid&&tr[u<<1|1].st) res = query(u<<1|1,l,r);//若右兒子有染色的節點,優先遞迴右兒子
    if(res==-1&&l<=mid&&tr[u<<1].st) res = query(u<<1,l,r);//若右兒子中找不到對應的染色節點,則判斷左兒子有無被染色的點,若有則遞迴尋找
    return res;//最後返回找到的點,或返回並未找到點
}

到了這裡,題目就結束了,有一點也需要注意當我們從節點不斷沿鏈上找時,若是找到了及時退出,這樣才是我們要找的答案

來看看程式碼

Ac_code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,M = N<<1;
struct Node//線段樹的結構體
{
    int l,r;
    bool st;//區間是否存在被染色節點
}tr[N<<2];
int h[N],ne[M],e[M],idx;//鏈式向前星
int sz[N],dep[N],fa[N],son[N];
int id[N],top[N],rid[N],ts;//這兩行是樹鏈剖分的陣列,其中不太常規的是rid,其含義表示,線段樹中結點所對應的實際樹中的結點的編號
int n,m;

void add(int a,int b)//加邊函式
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

void dfs1(int u,int pa,int depth)
{
    sz[u] = 1,dep[u] = depth,fa[u] = pa;
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(j==pa) continue;
        dfs1(j,u,depth+1);
        if(sz[j]>sz[son[u]]) son[u] = j;
        sz[u] += sz[j];
    }
}

void dfs2(int u,int tp)//樹剖的初始化
{
    top[u] = tp,id[u] = ++ts,rid[ts] = u;
    if(!son[u]) return ;
    dfs2(son[u],tp);
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(j==fa[u]||j==son[u]) continue;
        dfs2(j,j);
    }
}

void pushup(int u)
{
    tr[u].st = tr[u<<1].st|tr[u<<1|1].st;
}

void build(int u,int l,int r)
{
    tr[u] = {l,r};
    if(l==r) return ;
    int mid = l + r >> 1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}

void modify(int u,int x)
{
    if(tr[u].l==tr[u].r)//當查詢到對應結點時停止。
    {
        tr[u].st = 1;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x<=mid) modify(u<<1,x);
    else modify(u<<1|1,x);
    pushup(u);
}

int query(int u,int l,int r)
{
    if(tr[u].l==tr[u].r) return rid[tr[u].l];//rid表示的是,線上段樹中的節點,在實際的樹中的編號。
    int mid = tr[u].l + tr[u].r >> 1;
    int res = -1;//初始化為-1
    if(r>mid&&tr[u<<1|1].st) res = query(u<<1|1,l,r);//若右兒子有染色的節點,優先遞迴右兒子
    if(res==-1&&l<=mid&&tr[u<<1].st) res = query(u<<1,l,r);//若右兒子中找不到對應的染色節點,則判斷左兒子有無被染色的點,若有則遞迴尋找
    return res;//最後返回找到的點,或返回並未找到點
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);//h陣列初始化
    for(int i=0;i<n-1;i++)
    {
        int a,b;scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    dfs1(1,-1,1);
    dfs2(1,1);
    build(1,1,n);
    modify(1,1);//千萬別忘了,1號節點初始時就被染色了
    while(m--)
    {
        char op[2];
        int x;
        scanf("%s%d",op,&x);
        if(*op=='C') modify(1,id[x]);
        else
        {
            int res = -1;
            while(top[x]!=1)//不斷沿鏈上翻
            { 
                res = query(1,id[top[x]],id[x]);
                if(res!=-1) break;//若已經找到,就直接退出
                x = fa[top[x]];
            }
            if(res==-1) res = query(1,1,id[x]);
            printf("%d\n",res);
        }
    }
    return 0;
}