CF1292C Xenon's Attack on the Gangs|DP,貪心
題目連結
題目大意:
給一棵\(n\)個節點的樹,將\([0,n-2]\)分配到每一條邊,定義\(s(s,t)\)為\(s\)到\(t\)路徑邊權的\(mex\),求\(s(s,t)\)和的最大值
名詞解釋:\(mex\):沒有出現的最小自然數(即\([0,+\infty]\))
題目思路:
emm,感覺假如這棵樹變為一條鏈的情況可以討論一下,對本題有啟發。(其實就是鏈上方案推廣到樹上,兩者是相通的。但鏈的情況顯然更好想)
先考慮 \(0\) 的放置,根據定義,若 \(0\) 放置在一條左邊有 \(x\) 節點,右邊有 \(y\) 節點的邊,則貢獻為 \(x\times y\),最大化該值即可。
再考慮 \(1\)
現有兩個方案
A:.... 2 1 0 ..... B: .... 1 2 0 ....
假設...
並沒有差別,那麼
-
按照題意,不考慮 \(0\) 左右的內部答案,因為都是 \(0\)
-
左邊的...到右邊的...答案沒有差別
-
\(1\) 到右邊的...,B答案>A答案(全是 \(2\))
-
\(2\) 到右邊的...,A答案>B答案(全是 \(1\))
顯然,\(2\) 中的B答案等於 \(3\) 中的A答案,即兩方案的答案事實上是相同的,若考慮將上文方案中的 \(2\) 換為大於 \(2\)
接著往下考慮,可以證明對於每個數,使該數在已編號的序列的左、右將不會更劣。方案亦與上文類似。
其實,我們可以看到,每個邊對答案的貢獻是經過該邊的次數 \(\times 1 /0\) 。即只有兩種情況。因此,我們在按照上面的構造方案的情況下無需再考慮編號問題。
按上面的方法,一條鏈的方案便可構造出來。
分割線
回到正文,現在我們考慮的是樹,但可以視為若干條鏈。如上文所說,鏈和樹的做法裡某些東西是相通的。
仿照鏈的做法,此時放置方案應使 \(0\) 到一個儘量大的數都放在一條特定的鏈上。其他方案不會更優。至於證明,可仿照鏈的證明方式。
那麼現在我們要找到這條特定的鏈。通過貪心來找到這條特定的鏈不大可行。考慮每一條鏈都求出該鏈為特定鏈時,這棵樹的總答案。
這裡可以通過記憶化搜尋求解,通過 \((u,v)\) 的答案可求 \(u\),\(v\) 的子節點的答案。
記 \(f(u,v)\) 為將 \(u\) 到 \(v\) 作為上文的特定的鏈的答案。則
\(f(u,v)=max(f(fa_u,v),f(i,fa_v))+siz_u \times siz_v\)。
但是,隨著 \((u,v)\) 的改變,樹的形態可能會發生一定改變,這意味著 \(fa_x\) 和 \(siz_x\) 會發生改變,解決方案是將每個節點作根時的\(fa\)和\(siz\)狀況都預處理下來,在 \((u,v)\) 狀況下,\(fa_u\) 和 \(siz_u\) 分別是 \(v\) 作為根時的 \(fa\) 和 \(siz\),這個畫圖可以理解。
至此,本題基本完成,以下是本題程式碼。
#include<bits/stdc++.h>
using namespace std;
int cc,to[6000],net[6000],fr[6000];bool vis[6000];
int rot,fa[3005][3005];
int n,u,v;long long ans,f[3005][3005],siz[3005][3005];
void addedge(int u,int v)
{
cc++;
to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void dfs(int x)
{
vis[x]=true;
for (int i=fr[x];i;i=net[i])
{
if (!vis[to[i]])
{
fa[rot][to[i]]=x;
siz[rot][to[i]]=1;
dfs(to[i]);
siz[rot][x]+=siz[rot][to[i]];
}
}
}
long long dp(int u,int v)
{
if (f[u][v]) return f[u][v];
if (u==v) return 0;
f[u][v]=max(dp(fa[v][u],v)+siz[v][u]*siz[u][v],
dp(u,fa[u][v])+siz[v][u]*siz[u][v]);
return f[u][v];
}
int main()
{
cin>>n;
for (int i=1;i<n;i++)
{
cin>>u>>v;
addedge(u,v);
addedge(v,u);
}
for ( rot=1;rot<=n;rot++)
{
for (int j=1;j<=n;j++)
vis[j]=false;
siz[rot][rot]=1;fa[rot][rot]=0;dfs(rot);
}//預處理每個節點為根時的fa和siz
for (int i=1;i<=n;i++)
for (int j=1;j<n;j++)
{
dp(i,j);ans=max(ans,f[i][j]);
}//記搜
cout<<ans<<endl;
return 0;
}