1. 程式人生 > 實用技巧 >CF1292C Xenon's Attack on the Gangs|DP,貪心

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\)

的放置,若 \(1\) 不在 \(0\) 邊的左或右,方案將不會更優。證明如下。

現有兩個方案

A:.... 2 1 0 ..... B: .... 1 2 0 ....

假設...並沒有差別,那麼

  1. 按照題意,不考慮 \(0\) 左右的內部答案,因為都是 \(0\)

  2. 左邊的...到右邊的...答案沒有差別

  3. \(1\) 到右邊的...,B答案>A答案(全是 \(2\)

  4. \(2\) 到右邊的...,A答案>B答案(全是 \(1\)

顯然,\(2\) 中的B答案等於 \(3\) 中的A答案,即兩方案的答案事實上是相同的,若考慮將上文方案中的 \(2\) 換為大於 \(2\)

的數,則B顯然會比A。因此,\(1\) 須放在 \(0\) 的左右。具體的說,若 \(0\) 左邊有 \(x\) 個節點,右邊有 \(y\) 個節點,那麼需比較 \((x-1)\times y\)\(x \times (y-1)\)

接著往下考慮,可以證明對於每個數,使該數在已編號的序列的左、右將不會更劣。方案亦與上文類似。

其實,我們可以看到,每個邊對答案的貢獻是經過該邊的次數 \(\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;
}