[洛谷P3629][题解][APIO2010]巡逻
0.Description
在一棵树上加1或2条边,新加的边必须走一次。求出从1出发走过所有点回到1的最小路程。
1.Solution
首先可以算出加边前总共要走的路程是 \((n-1)\times2\) 。
加边操作实际上就是将某一段回溯的路程缩短,由于这个题加的边数非常少,所以我们分类讨论一下。
加一条边
要想让这条边缩短最大的路程,我们可以直接去求树上最长的路径——直径,然后直接减去直径的长度,再加上强制走的这条加边。
加两条边
感性理解可得两条边的第一条一定也是加直径(证明有时间补上)。接下来我们要选剩下的最长路径,但显而易见的是,此时我们选的路径如果和第一条有交集,那么交集部分还要多走一遍,于是我们可以把这些边权都赋值为-1,可以在最后减去路径长度时方便地表示多走一条边,然后就直接求直径即可,此时可以保证选到的边与第一条路径交集最小。注意:由于此时边权有负数,求树的直径时需要用dp。
总结
我们先求出树的直径的长度,记为 \(res_1\) ,如果加一条边则输出 \((n-1)\times2-res_1+1\) 。如果加两条边,则按照上面的方法求出第二条路径的长度,记为 \(res_2\) ,输出 \((n-1)\times2-res_1+1-res_2+1\) 。
2.Code
#define N 100010
int n,k,bg,ed,vis[N];
int res1,res2,pos,dis[N],f[N],fa[N];
struct Edge {
int to,nxt,wei;
}e[N<<1];
int head[N],cnt=-1;
inline void ade(int u,int v,int w){
e[++cnt].to=v,e[cnt].wei=w;
e[cnt].nxt=head[u],head[u]=cnt;
}
//第一次求树的直径,用两次DFS
void DFS(int now,int ff){
cerr<res1){
res1=dis[now],pos=now;
}
fa[now]=ff;
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to,w=e[i].wei;
if(v!=ff){
dis[v]=dis[now]+w;
DFS(v,now);
}
}
}
//第二次求树的直径,用dp
void DFS2(int now,int ff){
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to,w=e[i].wei;
if(v!=ff){
DFS2(v,now);
res2=max(res2,f[now]+f[v]+w);
f[now]=max(f[now],f[v]+w);
}
}
}
//把第一条路径上的边权赋为-1
void Modify(int now,int ff){
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to;
if(v!=ff){
if(vis[v])e[i].wei=e[i^1].wei=-1;//注意把反向边也更改了
Modify(v,now);
}
}
}
int main(){
Read(n),Read(k);
memset(head,-1,sizeof(head));
for(rg int i=1;i