一類樹上問題的總結

有時,我們會遇到這樣的問題:
在一個樹上選定一些點,每個點能覆蓋一定範圍的點……之類
比如:

通常,我們有兩種做法:

方法一

我們對於每個點,考慮距離它最近的被選點

需要先證明一個顯然的結論:對於每個i,以i爲最近點的j是一個連通塊。

換句話說,如果距離a的最近被選點爲i,距離b的最近被選點也是i,那麼a到b的路徑上的點的最近被選點都是i。
考慮一條鏈:設Ax是鏈上第x個點,那麼點y到Ax的距離fy(x)隨x的增加,先下降,再上升。(這個顯然)。
那麼假設a到b路徑上的c,最近點不是i而是j。
那麼,\(dis(a,i)<dis(a,j),dis(c,i)>dis(c,j),dis(b,i)<dis(b,j)\)
就是說\(fi\)\(fj\)有兩個交點。但這是不可能的。
所以,如果距離a的最近被選點爲i,距離b的最近被選點也是i,那麼a到b的路徑上的點的最近被選點都是i一定成立。

這樣,這樣,我們設\(dp(i,j)\)表示i的最近點是j的狀態。
(如果需要,再維護\(f(i)\)表示\(dp(i,j)\)的最小值。)
那麼,對於i的每個兒子v,枚舉它的最近點a,則轉移到\(dp(v,a)\)
注意,如果\(j=a\),那麼對於v來說,a已經選擇過了,則轉移時把選j的影響去掉。

由於形成連通塊,正確性可以保證。

優點

適用範圍較廣,非常好寫。

缺點

複雜度至少爲\(O(n^2)\)

方法二

維護子樹信息

對於每個子樹,有2種信息:

  1. 當前子樹中,最遠的沒被覆蓋的點。(可能沒有)
  2. 當前子樹中,最近的被選點還能向上覆蓋多少。(可能沒有)

理論上,共有4種情況,
但實際上,如果既有沒被覆蓋的點,又有能向上覆蓋的被選點,由於沒被覆蓋的點需要上面的一個點覆蓋,那麼對於這個點來說,下面的向上覆蓋的被選點已經失去意義了。

所以,只有3種情況,可以用2個dp數組,外加一維記錄。

轉移時依次合併,共有4種情況。
由於需要枚舉兩個值,因此,若覆蓋範圍爲D,則複雜度爲\(O(nD^2)\)
因爲是對兩個值取max等操作,所以通過前綴最值可以優化至\(O(nD)\)

優點

複雜度較低,便於優化。

缺點

只適用於覆蓋問題,若需要在轉移時知道每個點最近的被選點等信息,則只能用方法一。
同時,情況較多,代碼較難寫。

例題:

- CF70E Information Reform

題解
由於要知道確切距離,只能用方法一。

- P3267 [JLOI2016/SHOI2016]偵察守衛

由於複雜度的要求,直接套用方法二即可。

代碼

(未加優化)

#include <stdio.h>
int dp1[500010][21],dp2[500010][21],D;
int fr[500010],ne[1000010],v[1000010],bs=0;
int sz[500010],g1[21],g2[21],inf=999999999;
bool bk[500010];
void addb(int a,int b)
{
    v[bs]=b;
    ne[bs]=fr[a];
    fr[a]=bs;
    bs+=1;
}
void dfs(int u,int f)
{
    for(int i=fr[u];i!=-1;i=ne[i])
    {
        if(v[i]!=f)
            dfs(v[i],u);
    }
    for(int a=0;a<=D;a++)
        dp1[u][a]=dp2[u][a]=inf;
    dp1[u][0]=0;
    bool dy=true;
    for(int i=fr[u];i!=-1;i=ne[i])
    {
        if(v[i]==f)continue;
        if(dy)
        {
            for(int a=0;a<=D;a++)
            {
                dp1[u][a]=dp1[v[i]][a];
                dp2[u][a]=dp2[v[i]][a];
            }
            dy=false;
            continue;
        }
        for(int a=0;a<=D;a++)
            g1[a]=g2[a]=inf;
        for(int a=0;a<=D;a++)
        {
            for(int b=0;b<=D;b++)
            {
                int t=a,o=dp1[u][a]+dp1[v[i]][b];if(b>t)t=b;
                if(o<g1[t])
                    g1[t]=o;
            }
        }
        for(int a=0;a<=D;a++)
        {
            for(int b=0;b<=D;b++)
            {
                int o=dp1[u][a]+dp2[v[i]][b];
                if(a-2>=b)
                {
                    if(o<g1[a])
                        g1[a]=o;
                }
                else
                {
                    if(o<g2[b])
                        g2[b]=o;
                }
            }
        }
        for(int a=0;a<=D;a++)
        {
            for(int b=0;b<=D;b++)
            {
                int o=dp2[u][a]+dp1[v[i]][b];
                if(b-2>=a)
                {
                    if(o<g1[b])
                        g1[b]=o;
                }
                else
                {
                    if(o<g2[a])
                        g2[a]=o;
                }
            }
        }
        for(int a=0;a<=D;a++)
        {
            for(int b=0;b<=D;b++)
            {
                int t=a,o=dp2[u][a]+dp2[v[i]][b];if(b>t)t=b;
                if(o<g2[t])
                    g2[t]=o;
            }
        }
        for(int i=0;i<=D;i++)
        {
            dp1[u][i]=g1[i];
            dp2[u][i]=g2[i];
        }
    }
    for(int i=1;i<=D;i++)
        g1[i-1]=dp1[u][i];
    for(int i=D-1;i>=0;i--)
        g2[i+1]=dp2[u][i];
    g1[D]=g2[0]=inf;
    if(dp1[u][0]+sz[u]<g1[D])g1[D]=dp1[u][0]+sz[u];
    for(int i=0;i<=D;i++)
    {
        if(dp1[u][i]+sz[u]<g1[D])
            g1[D]=dp1[u][i]+sz[u];
    }
    for(int i=0;i<D;i++)
    {
        if(dp2[u][i]+sz[u]<g1[D])
            g1[D]=dp2[u][i]+sz[u];
    }
    if(bk[u])
    {
        if(dp1[u][0]<g2[0])
            g2[0]=dp1[u][0];
    }
    else
    {
        if(dp1[u][0]<g1[0])
            g1[0]=dp1[u][0];
    }
    for(int i=0;i<=D;i++)
    {
        dp1[u][i]=g1[i];
        dp2[u][i]=g2[i];
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&D);
    for(int i=1;i<=n;i++)
    {
        fr[i]=-1;
        scanf("%d",&sz[i]);
    }
    scanf("%d",&m);
    for(int i=0;i<m;i++)
    {
        int a;
        scanf("%d",&a);
        bk[a]=true;
    }
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        addb(a,b);
        addb(b,a);
    }
    dfs(1,0);
    int jg=inf;
    for(int i=0;i<=D;i++)
    {
        if(dp1[1][i]<jg)
            jg=dp1[1][i];
    }
    printf("%d",jg);
    return 0;
}

模擬賽的題

【題目背景】
Po 姐姐很愛他的妹子,可是 Po 姐姐並沒有妹子。於是 Po 姐姐決定去找妹子。
【題目描述】
A 國有 n 個城鎮,由 n-1 條道路連接,構成了一個樹形結構。每條道路的長度都是一樣
的。
Po 姐姐最近得到了一個信息:在 A 國的某些城鎮,可能出現質量上乘的妹子。
爲了捕獲這些妹子, Po 姐姐製作了 m 個傳送器,準備將這些傳送器安置在一些城鎮中。
一旦某個城鎮出現了妹子,Po 姐姐可以立刻傳送到某個傳送器所在的城鎮,然後沿道路移
動到妹子所在的城鎮。Po 姐姐並不願意走太多的路,因此他會選擇離妹子最近的一個傳送
器傳送,然後走最短路到達妹子所在的城鎮。
Po 姐姐想要通過合理安排傳送器的位置,使得所有妹子可能出現的城鎮離最近傳送器
的距離的最大值最小。
由於 Po 姐姐懶癌發作,請你幫他寫一個程序來解決這個問題。Po 姐姐懶得寫 SPJ,你
只需要輸出最大距離的最小值就行了。
【數據輸入】
第一行是兩個正整數 n,m,表示 A 國城鎮的數量和傳送器的數量
接下來 n 個整數,每個整數都是 1 或 0,如果第 i 個整數是 1 代表第 i 個城鎮可能出現
質量上乘的妹子
接下來 n-1 行,每行兩個正整數 x,y,表示 x 與 y 之間有一條雙向通行的道路
【數據輸出】
輸出一行一個正整數,代表所有妹子可能出現的城鎮離最近傳送器的最大距離的最小值

此題無需dp,用方法二,貪心選擇。
題解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章