[题解]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\)),而选\(FB,ED\iff\)选\(EF,BD\)。
- 当\(EF\)与\(PQ\)的一部分重合时,如下图(\(EF\)只与\(PQ\)有重合的情况没画,是同理的),\(EF\)可以代替掉\(AB,CD\)的任意一条,因为它的重合长度更短,且自身更长。
- 当\(EF\)覆盖\(PQ\)时,如下图,我们可以将选\(AB,CD\)看作选\(AD,BC\),而\(AD,BC\)可以用选\(EF,BC\)代替,因为两种选法重合部分长度相同,而\(EF\ge AD\)。根据\(EF\)覆盖的位置,这里的情况会有所不同,但是证明同理。
(其实就是感性理解啦……)
有了这个结论,我们第一条路径就可以大胆选择直径\(d\)。接下来我们考虑另一条路径\(d'\)怎么找,才能让答案最大。
我们知道\(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;
}