2 条题解

  • 0
    @ 2026-8-5 15:28:51

    D50 树的P3629 [APIO2010] 巡逻

    // 两次DFS+树形DP O(n)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=100005;
    int h[N],to[N<<1],ne[N<<1],w[N<<1],idx;
    void add(int u,int v){
      to[++idx]=v,w[idx]=1,ne[idx]=h[u],h[u]=idx;
    }
    int d[N],pre[N],col[N];
    int n,k,p,d1,d2;
    
    void dfs(int u,int fa){
      if(d[u]>d[p]) p=u; //记录直径端点
      pre[u]=fa;         //记录直径路径
      for(int i=h[u];i;i=ne[i]){
        int v=to[i];
        if(v!=fa){
          d[v]=d[u]+w[i]; //记录v到根的距离
          dfs(v,u);
        }
      }
    }
    void dfs2(int u,int fa){
      for(int i=h[u];i;i=ne[i]){
        int v=to[i];
        if(v!=fa){
          if(col[u] && col[v]) w[i]=-1; //直径边取反
          dfs2(v,u);
          d2=max(d2,d[u]+d[v]+w[i]); //记录经过u的直径
          d[u]=max(d[u],d[v]+w[i]);  //记录u的最长链长度
        }
      }
    }
    int main(){
      scanf("%d%d",&n,&k);
      for(int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
      }
      dfs(1,0); d[p]=0;
      dfs(p,0); d1=d[p]; //两次DFS求直径d1,记录直径路径pre
      
      if(k==1){
        cout<<2*(n-1)-d1+1;
        return 0;
      }
      for(int i=p;i;i=pre[i])col[i]=1; //直径染色
      memset(d,0,sizeof d);
      dfs2(1,0); //树形DP求直径d2
      cout<<2*(n-1)-(d1+d2)+2;
    }
    
    • 0
      @ 2025-10-8 16:57:17

      /* by: hansang(scy修改) 不建新边时,要走 2*(n-1)次,每条道路经过两次 初始化ans=2*(n-1) (1) 当 K=1 随机建一条边,必定形成一个环 环里的每条旧边只用走一次,环越大答案越小 找出树的直径,把直径的头尾连边 设不建新边时树的直径长度为 dis,则有当 K=1,答案为 ans-dis+1 (因为新边也走了一次,之前的 ans没算,要加上1)

      (2) 当 K=2 假设像之前一样求直径,如果两条直径有重叠 则重叠部分里的边被减了两次,相当于重叠部分的点没走过。 实际上重叠的边要走两次。所以求第二条路径前,把第一条直径的边权为-1 设第二次求树的直径为 dis,答案为 ans-dis+1

      求直径的方法: 第一遍求直径时,因为要记录边,使用2遍 dfs1别找到直径的两端(分选任意一点为根, dfs1找到最深的点L,然后让L为根,dfs1找到最深的点R,L到R的距离为直径之一), 第二遍求直径时,边权有负数不能使用 dfs1,则考虑使用 dfs2 */

      #include <bits/stdc++.h>
      using namespace std;
      const int N=1e5+10;
      struct edge{int x, y, c, pre;} a[2*N]; int alen, last[N];
      void ins(int x, y, int c) {alen++; a[alen]=edge{x, y, c, last[x]}; last[x]=alen;}
      bool v[N]; int dis, fa, f[N], b[N], d[N];
      void dfs1(int x)
      {
      	v[x]=1; //记录,防止重复经过一个点 
      	for(int k=last[x]; k; k=a[k].pre)
      	{
      		int y=a[k].y; if(v[y]==1) continue;
      		d[y]=d[x]+a[k].c; f[y]=x; b[y]=k; //赋值,记录点和边 
      		if(dis < d[y]) dis=d[y], fa=y; //更新直径长度 
      		dfs1(y);
      	}
      }
      void dfs2(int x)
      {
      	v[x]=1;
      	for(int k=last[x]; k; k=a[k].pre) 
      	{
      		int y=a[k].y; if(v[y]==1) continue;
      		dfs2(y); //注意下面这两句的顺序 
      		dis=max(dis, d[x]+d[y]+a[k].c); //以 x为顶点的直径 
      		d[x]=max(d[x], d[y]+a[k].c); //用 y去更新 x往下最长的链 
      	}
      	d[x]=max(d[x], 0); //当 x没有孩子节点 或 d[x]为负数 
      	dis=max(dis, d[x]);
      }
      int main()
      {
      	int n, K; scanf("%d%d", &n, &K);
      	int ans=(n-1)<<1; // ans=2*(n-1) 
      	alen=1; memset(last, 0, sizeof(last));
      	for(int i=1; i<n; i++)
      	{
      		int x, y; scanf("%d%d", &x, &y); //这里原代码可能有笔误,应为&x, &y
      		ins(x, y, 1); ins(y, x, 1); //边权为 1 
      	}
      	int L, R; 
      	memset(v, 0, sizeof(v));
      	d[1]=0;f[1]=0; dis=-N; dfs1(1); L=fa; //任选一点出发,记录最长的链的另一个端点L 
      	memset(v, 0, sizeof(v)); 
      	d[L]=0;f[L]=0; dis=-N; dfs1(L); R=fa; //从L出发,记录最长的链的另一个端点R,这样就得到直径的两个端点了 
      	
      	ans=ans-dis+1; //计算 ans 
      	
      	if(K==2)
      	{
      		memset(v, 0, sizeof(v));
      		memset(d, -63, sizeof(d)); //有负权边,d赋值负无穷 
      		for(int i=R; i; i=f[i]) //从 R开始,遍历直径上的点 
      		{
      			a[b[i]].c=-1; //把边取反 
      			a[b[i]^1].c=-1; //另一条反向边 
      		}
      		dis=0; dfs2(1); ans=ans-dis+1; //计算 ans
      	}
      	printf("%d\n", ans);
      	return 0;
      }
      
      • 1

      D50【树形DP:树的直径】 [APIO2010] 巡逻

      信息

      ID
      1438
      时间
      1000ms
      内存
      64MiB
      难度
      8
      标签
      递交数
      139
      已通过
      26
      上传者