1. 程式人生 > >[NOIP10.3模擬賽]3.w題解--神奇樹形DP

[NOIP10.3模擬賽]3.w題解--神奇樹形DP

spa tchar 如果 tdi 聯通 端點 names 由於 ret

題目鏈接:

閑扯:

這題考場上把子任務都敲滿了,5個namespace,400行11k

結果爆0了哈哈,因為寫了個假快讀只能讀入一位數,所以手測數據都過了,交上去全TLE了

把邊分成三類:0. 需要染色的 1. 不需要染色的 2. 染不染色無所謂

考場上首先發現一個性質,就是一定存在一種最優解沒有染任何一條本來不需要的染色邊。

為啥?其實也挺顯然的,因為你染色跨過這條邊還得染這條邊一次,不如直接只染左右的聯通塊,這樣總路徑長度還能更小

但是第三種邊的呢?有個子任務就是枚舉它染不染。

然後鏈上的情況就搞了個貪心的做法,如果對於一條第三種情況的邊,如果兩邊的聯通塊是需要染色的,顯然選這條邊是更優的.但是註意考慮多條這種邊連在一起的情況.

然後樹上的版本貪心似乎就GG了

然後晚上盯著毫無註釋std和僅有三行的題解,畫了一面的草稿紙,終於看懂了...

分析:

首先我們需要假如我們已經有了最優情況的一種邊集,怎麽求出最小操作次數,其實是邊集的點集中度數為奇數的點的個數除以2。為什麽?顯然最優情況下,每一個奇數度數點恰好是一條操作路徑的結尾,由於一條操作路徑連接兩個點所以除以2

然後對於一個以x為根的子樹,如果已經取得了最優解,可以通過分情況考慮更新父親,顯然是具有最優子結構的.於是使用樹形DP

定義\(f[x][0/1]\)表示在以x為根的子樹中,不染色/染色x與其父親相連的最優代價

(註意代價是一個二元組\(cost(x,y)\)代表奇數點個數和染色路徑總長度,這裏比較代價大小根據題意,就不贅述了)

為了分情況轉移我們需要求出兩個值,\(npt\)是x在x為根的子樹中假如x不是任意一條染色路徑的端點的最優代價;\(pt\)是x在x為根的子樹中假如x是某一條染色路徑的端點時的最優代價(註意這裏的路徑端點都是從子樹引出來的路徑)

怎麽求出\(npt\)\(pt\)呢?我們可以將所有\(x\)的兒子\(v\)回溯到\(x\)的過程中逐個統計,接下來比較神奇可以借助圖像理解

\(npt=min(npt+f[v][0],pt+f[v][1])\)

解釋: 畫圖,若\(v\)到其父親\(x\)邊沒有染色,說明原來如果x不是路徑端點的話現在還不會是端點;類似的,若\(v\)到父親\(x\)的邊染色了,並且\(x\)

此時是一條路徑的端點,那麽這時候我們可以把路徑延長到\(v\)的子樹中,這樣是解更優並且\(x\)這樣就不會是路徑端點了

\(pt = min(npt+f[v][1],pt+f[v][0])\)

解釋:與上面類似就太懶不想打了,有問題可以luogu或QQ聯系我

在考慮DP中\(f\)數組的轉移

再強調一下定義:定義\(f[x][0/1]\)表示在以x為根的子樹中,不染色/染色x與其父親相連的最優代價

\(x\)與其父親相連的邊的屬性為\(p\)

首先根據在"閑扯"中的性質以及題意,若\(p=1,f[now][1]=(inf,inf)\);若\(p=0,f[now][0]=(inf,inf)\)

然後現在先考慮\(f[now][0]\),\(f[now][0]=min(npt,cost(pt.x+1,pt.y))\) , 這裏的\(cost\)可以理解為\(make\)_\(pair\)

解釋:如果\(x\)不是染色路徑的端點,而且x到父親的邊也不染色,那麽顯然代價不變.但是如果\(x\)是某一條路徑的端點,那麽這時候我們需要加上\(x\)這個點的貢獻(它是個端點那肯定是奇數度數)

再考慮\(f[now][1],f[now][1]=min(cost(npt.x+1,npt+1),cost(pt.x,pt.y+1))\)

解釋:如果\(x\)不是路徑端點,那麽\(x\)如果和父親相連的邊染色的需要新開一條路徑,所以見上;如果\(x\)是路徑端點,那麽我們可以把這條路徑引上去,x點的貢獻就不用計算,只需要讓路徑長度加1就好了

然後就沒了,代碼很短但很難想

代碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <iostream>
#include <vector>
#define ll long long 
#define ri register int 
using std::min;
using std::max;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c==‘-‘;
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;return ;
}
const int maxn=100005;
const int inf=0x7ffffff;
struct Edge{
    int ne,to,w;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to,int x){
    edge[++num_edge].ne=h[f];
    edge[num_edge].to=to;
    edge[num_edge].w=x;
    h[f]=num_edge;
}
struct Dat{
    int x,y;
    Dat(){x=y=inf;}
    Dat(int _x,int _y){x=_x,y=_y;}
    Dat operator +(const Dat &b)const{
        return Dat(x+b.x,y+b.y);
    }
    bool operator <(const Dat &b)const{
        return x==b.x?y<b.y:x<b.x;
    }
}f[maxn][2];
int n;
void dfs(int now,int fa,int t){
    int v;
    Dat npt=Dat(0,0),pt=Dat(inf,inf);//npt--不是路徑端點  pt是路徑端點
    Dat pa=Dat(0,0),pb=Dat(0,0);
    for(ri i=h[now];i;i=edge[i].ne){
        v=edge[i].to;
        if(v==fa)continue;
        dfs(v,now,edge[i].w);
        pa=min(npt+f[v][0],pt+f[v][1]);
        pb=min(npt+f[v][1],pt+f[v][0]);
        npt=pa,pt=pb;
    }
    if(t==1)f[now][0]=Dat(inf,inf);//必須要翻轉
    else {
        f[now][0]=min(npt,Dat(pt.x+1,pt.y));
    }
    if(t==0)f[now][1]=Dat(inf,inf);//性質--翻轉1邊一定更不優
    else {
        f[now][1]=min(Dat(npt.x+1,npt.y+1),Dat(pt.x,pt.y+1));
    }
    return;
}
int main(){
    int x,y,c,d;
    freopen("w.in","r",stdin);
    freopen("w.out","w",stdout);
    read(n);
    for(ri i=1;i<n;i++){
        read(x),read(y),read(c),read(d);
        if(d==2){
            add_edge(x,y,2),add_edge(y,x,2);
        }
        else {
            d=c^d;
            add_edge(x,y,d),add_edge(y,x,d);
        }
    }
    dfs(1,0,0);
    Dat ans=min(f[1][0],f[1][1]);
    printf("%d %d\n",ans.x/2,ans.y);
    return 0;
}

[NOIP10.3模擬賽]3.w題解--神奇樹形DP