[BZOJ 1912] patrol 巡逻
Link:https://www.lydsy.com/JudgeOnline/problem.php?id=1912
Algorithm:
K=0:res=(n-1)*2 每条边恰好走2遍
K=1:res=res-树上最长链+1
由于每形成环,环上的边对答案的贡献都会-1,因此只要将树上最长链连成环即可
K=2:res=res-树上当前最长链+1
将原树上直径的边的边权赋为-1,表示如果原直径边同时出现在第2个环时对答案贡献增加1(变为2)
证明:第二次求最长链相当于对第一次的“反悔”操作,重复的边表示没有变化
相当于是把两条交错的最长链变成了两条分开的链,其和必然是不交错的两条链中最大的(否则可以用最长链代替)
Code:
#include <bits/stdc++.h> using namespace std; inline int read() { char ch;int num,f=0; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } const int MAXN=1e5+10; int n,k,ch[MAXN][2],mx[MAXN][2],mx_node,dia,res=0; struct edge { int to,nxt,val; }G[MAXN*2]; int head[MAXN],cnt=0; void add_edge(int u,int v) { G[++cnt].to=v;G[cnt].nxt=head[u];head[u]=cnt;G[cnt].val=1; } void dfs(int u,int anc) //一遍dfs求树的直径 { mx[u][0]=mx[u][1]=0; for(int i=head[u];i;i=G[i].nxt) { int v=G[i].to; if(v==anc) continue; dfs(v,u); if(mx[u][0]<mx[v][0]+G[i].val) { mx[u][1]=mx[u][0],mx[u][0]=mx[v][0]+G[i].val; ch[u][1]=ch[u][0],ch[u][0]=i; } else if(mx[u][1]<mx[v][0]+G[i].val) mx[u][1]=mx[v][0]+G[i].val,ch[u][1]=i; } if(mx[u][0]+mx[u][1]>dia) dia=mx[u][0]+mx[u][1],mx_node=u; } int main() { n=read();k=read(); for(int i=1;i<n;i++) { int x=read(),y=read(); add_edge(x,y);add_edge(y,x); } dfs(1,0);res=2*(n-1)-dia+1; if(k==1) return cout << res,0; for(int i=ch[mx_node][0];i;i=ch[G[i].to][0]) G[i].val=G[i^1].val=-1; for(int i=ch[mx_node][1];i;i=ch[G[i].to][0]) G[i].val=G[i^1].val=-1; dia=0;dfs(1,0);res=res-dia+1; cout << res; return 0; }
Review:
1、树的直径的求法
以前只会双dfs法
其实可以一遍dfs,
使用记录最长子链和次长子链的方法,如果存在最长“父链”比次长子链大的情况,其会被包含在祖先的情况中
Resources:https://blog.csdn.net/ilsswfr/article/details/52078089
2、如果对无向边有修改操作,用链式前向星记录
因为正反向边的序号相邻,可以一起更新
3、求解高维度问题时(k个不相交链的最大长度和),
可以采取贪心求解每个子问题(当前最长链) + 构建反悔机制(走完后边权赋为-1)来解决问题