九省聯考2018 林克卡特樹
Link
Difficulty
演算法難度7,思維難度7,程式碼難度5
Description
給定一棵個點的樹,邊帶權值,要求你選出條鏈,使得權值和最大。
Solution
前面的小部分分我就不說了,說一下和正解有極大聯絡的60分的樹形dp吧。
首先我們考慮設計dp狀態。
第一想法是代表在的子樹中選了條鏈的最大價值,看起來非常美好。
但是仔細想想發現沒法寫狀態轉移方程,因為不知道到底能不能和兒子連邊,也不知道連邊會發生什麼事。
這樣我們就發現我們還要記錄一下每個點的連邊狀態。
代表在的子樹中完整地選了條鏈的最大價值,代表點的度數。
首先初始狀態:
- ,代表這個點可以不選。
- ,代表這個點可以作為鏈最下面的點向上連。
- ,代表這個點可以單獨作為一條鏈,至於為什麼要有這個狀態,只需要想一下極端情況時,合法答案是什麼樣子的就可以了。
- 其他都為負無窮,也就是不合法
考慮轉移狀態,將兒子的狀態合併到點:(代表從列舉到,下面不再描述)
-
,其中
代表不選或者選條鏈。
-
,其中
代表不選,選條鏈,或者選條鏈並且選這條邊。
-
,其中
代表不選,選條鏈,或者選條鏈並且選這條邊增加一條鏈。
-
,其中
這個看起來跟上面的第二個轉移的重複了,事實上並沒有,因為這個轉移既合法,第二個轉移又轉移不到。
-
代表從下面連上來,同樣是第二個轉移沒有轉移到的。
-
,其中
代表不選,在這裡停止這條鏈並計入總數,或者把那兩個度數去掉。
轉移方程大概就是這些了,dp的順序呀,細節呀,就看我的程式碼吧。
這樣的話複雜度有些玄學(調迴圈邊界的話),我不太會算,反正只能有分,會TLE。
本來想把這個dp放到dfs序上說不定就可以到了,後來發現我不會QAQ
這個dp必須先寫一下,因為凸優化的程式碼就是在dp的基礎上改的。
拿到45分之後,我們來看這題正解吧。
凸優化
凸優化就是針對凸函式求極值的優化。
我們這裡不直接探究它的定義及一般情況,我們直接來看這個題,通過這個題來理解凸優化。
首先,通過打表可以發現,答案的函式是上凸的,對於樣例來說畫出來是這樣的:
雖然影象有點兒尖,但是它確實是上凸的。
怎麼直接判斷一個題的答案是否上凸呢?
我們可以感性判斷,比如對於這個題,假如只能選一條鏈的話,一定是選最長的,選兩條的話,增長的就沒有第一條那麼多了,因為最長的已經選過了,這樣來看,增長只會越來越慢,所以它是凸函式。
現在我們知道它是凸函數了,應該怎麼做呢?
我們二分一個權值,代表選一條鏈需要付出的代價,然後我們去掉選多少條鏈那一維,還按照原來的dp做。
這樣子相當於我們拿的直線去切答案函式,在這個基礎上求極值。
但是我們發現這樣求得極值之後,無法判斷下一次變小還是變大。
我們同樣可以發現,切了之後的可以取得極值的點是一段連續的區間。
因此,在此基礎上我們再記錄取得極值的最小的是多少,也就是區間的左端點是多少。
假如題目中的等於左端點的話,直接輸出答案。
假如題目中的一定不在這個區間內(左端點大於),則令,讓選的代價變大,左端點減小。
假如題目中的有可能在這個區間內(左端點小於),則令,讓選的代價變小,左端點增大。
最後令,再做一次得到最終答案,並且把那個選的代價加回來,就好了。
感性理解一下這個過程,感覺挺對的QAQ
然後這個做法就叫凸優化啦,是不是感覺也沒什麼難的?
時間複雜度,還有樹形dp常數挺大,所以跑得比較慢。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=' ';
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 f==1?x:-x;
}
const int N=3e5+5,K=105;
const LL inf=1e18;
int n,k,tot;
int head[N],to[N<<1],Next[N<<1],val[N<<1];
struct data{
LL x,y;
data(){}
data(LL _x,LL _y):x(_x),y(_y){}
inline bool operator < (const data& b) const {
if(x==b.x)return y>b.y;
return x<b.x;
}
inline data operator + (const data& b) const {return data(x+b.x,y+b.y);}
inline data operator + (LL b) const {return data(x+b,y);}
}dp[N][3];
inline void addedge(int x,int y,int l){
to[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
val[tot]=l;
}
LL mid;
inline void dfs(int x,int fa){
dp[x][0]=data(0,0);
dp[x][1]=data(0,0);
dp[x][2]=max(data(0,0),data(-mid,1));
for(int i=head[x];i;i=Next[i]){
int u=to[i];
if(u==fa)continue;
dfs(u,x);
dp[x][2]=max(dp[x][2],max(dp[x][2]+dp[u][0],dp[x][1]+dp[u][1]+val[i]+data(-mid,1)));
dp[x][1]=max(dp[x][1],max(dp[x][1]+dp[u][0],dp[x][0]+dp[u][1]+