[題解]P3629 [APIO2010] 巡邏

Sinktank發表於2024-11-26

P3629 [APIO2010] 巡邏

\(k=1\)時,我們一定貪心選擇直徑\(d\)的兩個端點建立道路,所以答案是\(2\times(n-1)-d+1\)

\(k=2\)時,兩條新建的道路恰好形成\(2\)個環,我們透過手玩可以發現一個結論:

  • \(1\)條邊恰好被經過\(1\)次,當且僅當它恰好位於\(1\)個環上。
  • \(1\)條邊恰好被經過\(2\)次,當且僅當它不滿足上面的條件。

可以結合下圖理解。

那麼我們的策略就是儘可能最大化“恰好位於\(1\)個環上”的邊數。

可以證明,\(k=2\)時選擇直徑的兩個端點修路一定可以取到最優解。

我們假設\(AB\)\(CD\)是我們選取的兩條路徑,而\(EF\)是直徑。我們證明,對於\(AB,CD\)的所有選法,改為讓\(EF\)參與一定不會讓答案更劣。

  • \(AB,CD\)沒有公共邊時,直接用\(EF\)代替其中一條路徑即可,沒有討論的必要。
  • \(AB,CD\)有公共邊時,這一段公共部分一定是連續的(否則就有環了),將其設為\(PQ\),討論\(EF\)的位置:
    • \(EF\)\(PQ\)無公共邊時,如下圖,選\(AB,CD\)可以用選\(FB,ED\)代替,因為\(FY\ge AY,EX\ge CX\)
    • \(EF\)\(PQ\)的一部分重合時,如下圖(\(EF\)只與\(PQ\)有重合的情況沒畫,是同理的),\(EF\)可以代替掉\(AB,CD\)的任意一條,因為它的重合長度更短,且自身更長。
    • \(EF\)覆蓋\(PQ\)時,如下圖,我們可以將選\(AB,CD\)看作選\(AD,BC\),而\(AD,BC\)可以用選\(EF,BC\)代替,因為兩種選法重合部分長度相同,而\(EF\ge AD\)。根據\(EF\)覆蓋的位置,這裡的情況會有所不同,但是證明同理。

(其實就是感性理解啦……)

也就是說,\(d'\)\(d\)每有一條邊重合,答案都相較\(2\times(n-1)-d+1\)增加了\(1\);每有一條邊不重合,答案都相較\(2\times(n-1)-d+1\)減少了\(1\)

所以我們可以先跑一遍直徑\(d\),然後把直徑上的邊權值都設為\(-1\),再跑一邊直徑\(d'\),答案即為\(2\times(n-1)-d-d'+2\)

\(1\)次求直徑無負權邊,且需要記錄路徑,所以使用兩次DFS;第\(2\)次有負權邊,不需要記錄路徑,所以使用樹形DP。

時間複雜度\(O(n)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int n,k,d[N],cur,pre[N],f[N],dis;
bool flg;
bitset<N> vis;
vector<int> G[N];
void dfs(int u,int fa){
	for(int i:G[u]){
		if(i==fa) continue;
		d[i]=d[u]+1;
		if(d[i]>d[cur]) cur=i;
		dfs(i,u);
	}
	if(flg) pre[u]=fa;
}
void dfs2(int u,int fa){
	for(int i:G[u]){
		if(i==fa) continue;
		int w=(vis[u]&&vis[i]?-1:1);
		dfs2(i,u);
		dis=max(dis,f[u]+f[i]+w);
		f[u]=max(f[u],f[i]+w);
	}
} 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>k;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].emplace_back(v);
		G[v].emplace_back(u);
	}
	dfs(1,0),d[cur]=0,flg=1,dfs(cur,0);
	if(k==1){
		cout<<2*(n-1)-d[cur]+1<<"\n";
		return 0;
	}
	for(int i=cur;i;i=pre[i]) vis[i]=1;
	dfs2(1,0);
	cout<<2*(n-1)-d[cur]-dis+2<<"\n";
	return 0;
}

相關文章