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;
}