本身樹形dp太菜了,要重點搞c++
終於本身作了一道不算那麼毒瘤的換根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; }
這題都看了題解纔會(雖說想到了題解這個狀態,但不會轉移)。。。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; }
挺思惟的一題。從點與點的配對徹底沒有辦法下手,從總體的考慮,一條邊能夠有幾條路徑通過,這題就迎刃而解了
假如一條邊連的兩個點 \((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; }
又是一道思惟難度很高的題。。。
做爲提升組選手應該都能想到把一團相同顏色的點縮成一團。這個圖就變成了黑白相間的一棵樹。
首先這是樣例裏的樹
如我所說,把同色點縮成一個點就是這個樣子
接下來就是推推結論了,(做爲提升組選手應該會以爲很好推)
先給結論: 最少點擊次數=(縮點後樹的直徑+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; }