樹形dp專欄

前言

本身樹形dp太菜了,要重點搞c++

219D Choosing Capital for Treeland

終於本身作了一道不算那麼毒瘤的換根dpapi

\(f[u]\) 表示以 \(u\) 爲根,子樹內總共須要交換的邊數, \(up[u]\) 表示以 \(u\) 爲根,子樹外總共須要交換的邊數。spa

Dfs1 求出 \(f[u]\) ,就有:3d

\[f[u]=\sum_{v∈son[u]} f[v] + (edge[u->v] == 1)\]code

edge[u->v] 表示 u->v 這條邊的方向是否是 u->vblog

Dfs2 求出 \(up[v]\)注意,是從u點求u的兒子點v),容斥一下,就有:get

\[up[v]=f[u]-f[v]+up[u]+(+1 / -1)\]it

(+1 / -1) 是看 edge[u->v]是否等於 1,是的話就有多一條邊交換方向,不是的話就要-1,由於多算了一條邊class

Code遍歷

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
inline int read() {
    int x=0,f=1; char ch=getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 2e5+7;
int n,cnt;
int head[N],f[N],up[N];
struct Edge {
    int next,to,flag;
}edge[N<<1];
inline void add(int u,int v,int flag) {
    edge[++cnt] = (Edge)<%head[u],v,flag%>;
    head[u] = cnt;
}
void Dfs1(int u,int fa) {
    for(int i=head[u];i;i=edge[i].next) {
        int v = edge[i].to;
        if(v != fa) {
            Dfs1(v,u);
            f[u] += f[v] + (edge[i].flag==0); //反邊 
        }
    }
}
void Dfs2(int u,int fa) {
    for(int i=head[u];i;i=edge[i].next) {
        int v = edge[i].to;
        if(v != fa) {
            up[v] = f[u] - f[v] + up[u];
            if(edge[i].flag == 1) up[v]++;
            else up[v]--;
            Dfs2(v,u);
        }
    }
}
int main()
{
    n = read();
    for(int i=1,u,v;i<=n-1;++i) {
        u = read(), v = read();
        add(u,v,1), add(v,u,0);
    }
    Dfs1(1,0);
    Dfs2(1,0);
    int ans = INF;
    for(int i=1;i<=n;++i)
        ans = min(ans,f[i]+up[i]);
    printf("%d\n",ans);
    for(int i=1;i<=n;++i)
        if(f[i]+up[i] == ans) printf("%d ",i);
    return 0;
}

533B Work Group

這題都看了題解纔會(雖說想到了題解這個狀態,但不會轉移)。。。dp功力還不行啊qwq

\(f[u][0/1]\) 表示子樹總和爲 偶數/奇數 的最大價值(且包括根,可是根能夠選或不選)

有人也許會說,奇數怎麼可呢,不是說必定要偶數嗎?(其實這個本身推推數據在紙上畫畫就差很少知道了)

像下面這張圖

黑色表明選了。不選根就能夠選奇數的兒子唄。轉移

\[f[u][0]=\max_{v∈son[u]}\{f[v][0]+f[u][0],f[v][1]+f[v][1]\}\]

\[f[u][1]=\max_{v∈son[u]}\{f[v][0]+f[u][1],f[v][1]+f[u][0]\}\]

最後 \(f[u][1]=\max\{f[u][1],f[u][0]+p[u]\}\)

(1表明奇,0表明偶)1+1=0,0+0=0; 1+0=0+1=1; 這個很好理解。

但是這個 \(f[u][0]\) 來更新 \(f[u][0]\) 怎麼理解呢?

其實就是前面這個 \(f[u][0]\) 是咱們待更新的,後面這個 \(f[u][0]\) 是以前遍歷的子樹裏的總最優解,意義有所不一樣。遍歷過程當中的 \(f[u][0]\) 稍別與\(f[u][0]\)的定義的,只有全部更新結束後它纔是u的子樹選偶數個的最大值qwq。(這個不是很簡單的東西嗎,你怎麼想了這麼久啊我確實想了這麼久

Code

#include<bits/stdc++.h>
#define INF 1e18
#define int long long
using namespace std;
inline int read() {
    int x=0,f=1; char ch=getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 2e5+7;
int n,cnt;
int head[N],p[N];
int f[N][2];    //f[i,0/1] 表示以i爲根 子樹總數是偶數/是奇數的  
struct Edge {
    int next,to;
}edge[N<<1];
inline void add(int u,int v) {
    edge[++cnt] = (Edge)<%head[u],v%>;
    head[u] = cnt;
}
void Dfs1(int u,int fa) {
    //printf("QLL:: %d %d\n",u,fa);
    f[u][1] = -INF; //f[u,1] 開始不能是奇數 
    for(int i=head[u];i;i=edge[i].next) {
        int v = edge[i].to;
        if(v != fa) {
            Dfs1(v,u);
            int x0 = f[u][0], x1 = f[u][1];
            f[u][0] = max(f[u][0],max(x0+f[v][0],x1+f[v][1]));
            f[u][1] = max(f[u][1],max(x0+f[v][1],x1+f[v][0]));
        }
    }
    f[u][1] = max(f[u][1],f[u][0]+p[u]);
    //printf("ELL :: %d %d %d\n",u,f[u][0],f[u][1]);
}
signed main()
{
    n = read();
    for(int i=1,u;i<=n;++i) {
        u = read(); p[i] = read();
        if(u!=-1) add(u,i), add(i,u);
    }
    Dfs1(1,0);
    printf("%lld\n",max(f[1][0],f[1][1]));
    return 0;
}

700B Connecting Universities

挺思惟的一題。從點與點的配對徹底沒有辦法下手,從總體的考慮,一條邊能夠有幾條路徑通過,這題就迎刃而解了

假如一條邊連的兩個點 \((x,y)\)\(x\)這邊這一團的大學有 \(f[x]\) 座,\(y\)這邊的這一團大學有\(f[y]\),咱們必定要讓 \(\min(f[x],f[y])\) 座大學通過這條邊與另外一端的大學配對。爲何?

\(f[x]<f[y]\) 若是 \(x\) 這邊這\(f[x]\)個大學不去通過這條邊 \((x,y)\) 向另外一端配對,在 \(x\) 這邊這一端本身給本身配對,答案就不是最優,本身給本身配對沒有發展,最後:

\[ans=\sum\min(f[u],2*K-f[u])\]

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x=0,f=1; char ch=getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 200007;
int n,m,cnt,K,ans;
int head[N],f[N];
struct Edge {
    int next,from,to;
}edge[N<<1];
inline void add(int u,int v) {
    edge[++cnt] = (Edge)<%head[u],u,v%>;
    head[u] = cnt;
}
void Dfs(int u,int fa) {
    for(int i=head[u];i;i=edge[i].next) {
        int v = edge[i].to;
        if(v != fa) {
            Dfs(v,u); f[u] += f[v];
        }
    }
    ans += min(f[u],K-f[u]);
}
signed main()
{
    n = read(), K = read(); K <<= 1;
    for(int i=1,x;i<=K;++i)
        x = read(), f[x]++;
    for(int i=1,u,v;i<=n-1;++i) {
        u = read(), v = read();
        add(u,v), add(v,u);
    }
    Dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}

Anton and Tree

又是一道思惟難度很高的題。。。

做爲提升組選手應該都能想到把一團相同顏色的點縮成一團。這個圖就變成了黑白相間的一棵樹。

首先這是樣例裏的樹

如我所說,把同色點縮成一個點就是這個樣子

接下來就是推推結論了,(做爲提升組選手應該會以爲很好推

先給結論: 最少點擊次數=(縮點後樹的直徑+1)/2。爲何呢?

咱們不妨把直徑拎出來,假設如今的直徑就是下面這個圖:

咱們最優策略是對直徑中間的一點點擊一下,這樣它周圍的兩個點就和他縮在一塊兒了,就至關於這條鏈的長度-2,好比下面這張圖;

所以對把直徑單獨拎出來最後縮成一個點的次數能夠算出是 \(\frac{d+1}{2}\) (固然這裏的直徑d是指邊數,結果向下取整)

爲何這棵樹要操做的次數就是直徑要操做的次數呢?

對此,咱們能夠把其餘點當作直徑上一些點的分支,以下圖:

本身手推一下直徑的縮點過程,發現縮點的同時,分支也縮了一些點進去,並且,縮了一圈。進而發現這些分支會先縮完,爲何?

每縮完一個點,這個點的周圍就會縮小一圈,那樹上最長的一條鏈是什麼啊?直徑唄。因此比直徑小的會在縮直徑的同時一塊兒縮完(這樣講能理解吧,由於我很菜,不會嚴格的證實,再有問題就本身手推吧

Code

#include<bits/stdc++.h>
using namespace std;
inline int read() {
    int x=0,f=1; char ch=getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 200007;
int n,m,cnt,mxc,ans;
int head[N];
bool col[N];
struct Edge {
    int next,to;
}edge[N<<1];
inline void add(int u,int v) {
    edge[++cnt] = (Edge)<%head[u],v%>;
    head[u] = cnt;
}
void Dfs(int u,int fa,int dep) {
    if(dep > ans) {
        mxc = u; ans = dep;
    }
    for(int i=head[u];i;i=edge[i].next) {
        int v = edge[i].to;
        if(v != fa) {
            if(col[u]==col[v]) Dfs(v,u,dep);
            else Dfs(v,u,dep+1);
        }
    }
}
int main()
{
    n = read();
    for(int i=1;i<=n;++i) col[i] = read();
    for(int i=1,u,v;i<=n-1;++i) {
        u = read(), v = read();
        add(u,v), add(v,u);
    }
    Dfs(1,0,0);
    Dfs(mxc,0,0);
    printf("%d\n",(ans+1)>>1);
    return 0;
}
相關文章
相關標籤/搜索