zoukankan      html  css  js  c++  java
  • 【洛谷P3629】巡逻【树的直径】

    题目大意:

    题目链接:https://www.luogu.org/problemnew/show/P3629
    有一棵边权为均11的树,要求从点11经过所有的点并会到点11,现在可以加入k(1k2)k(1leq kleq 2)条边,而且加入的边必须仅仅经过一次,求加入边后最少的回到点11的距离。


    思路:

    很容易发现,如果一条边都不加,那么肯定树中的每一条边都要经过两次。那么答案就是2(n1)2(n-1)。但是现在加入一条边(x,y)(x,y),那么从xx走到yy之后就可以直接返回xx,就可以使原来xxyy的路径只走一遍。那么我们为了保证减少更多的路,那么肯定就是在树的直径的两端脸上一条边!
    那么对于k=1k=1的情况就讨论完了,答案就是2(n1)2(n-1)-树的直径+1+1
    那么接下来讨论k=2k=2的情况。
    k=1k=1的基础上,我们要再加一条边,那么会有两种选择:

    1. 新加入的边和之前加入的边互不干扰,即新加入的边所组成的环和原来加入的边所组成的环不重合。
    2. 新加入的边和之前加入的边有地方重合了。那么可以发现,为了保证满足新加入的边也走一遍,重合的地方就会多走一遍。那么之前减去的地方就要加回来。

    那么应该怎么做呢?
    不难看出,第二问的“要加回来”其实就是把边权取反的意思。因为把边权取反之后如果在走这条边就相当于加回来了。
    那么就再跑一遍树的直径,答案就是2(n1)2(n-1)-树的直径11-树的直径2+22+2
    注意第二遍要用树形DPDP,因为会出现负边权。


    代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N=100100;
    int n,k,x,y,q,p,tot=1,sum,ans1,ans2,head[N],father[N],f[N];
    
    struct edge
    {
    	int next,to,dis;
    }e[N*2];
    
    void add(int from,int to)
    {
    	e[++tot].to=to;
    	e[tot].dis=1;
    	e[tot].next=head[from];
    	head[from]=tot;
    }
    
    void dfs1(int x,int fa,int s)
    {
    	if (s>sum) sum=s,p=x;
    	for (int i=head[x];~i;i=e[i].next)
    		if (e[i].to!=fa)
    			dfs1(e[i].to,x,s+e[i].dis);
    }
    
    void dfs2(int x,int fa,int s)
    {
    	if (s>ans1) ans1=s,q=x;
    	for (int i=head[x];~i;i=e[i].next)
    		if (e[i].to!=fa)
    		{
    			father[e[i].to]=x;
    			dfs2(e[i].to,x,s+e[i].dis);
    		}
    }
    
    void dp(int x,int fa)
    {
    	for (int i=head[x];~i;i=e[i].next)
    	{
    		int y=e[i].to;
    		if (y==fa) continue;
    		dp(y,x);
    		ans2=max(ans2,f[x]+f[y]+e[i].dis);
    		f[x]=max(f[x],f[y]+e[i].dis);
    	}
    }
    
    void change(int x,int y)  //把边权取反
    {
    	if (x==y) return;
    	for (int i=head[x];~i;i=e[i].next)
    		if (e[i].to==father[x])
    		{
    			e[i].dis=-1;
    			e[i^1].dis=-1;
    			change(e[i].to,y);
    		}
    }
    
    int main()
    {
    	memset(head,-1,sizeof(head));
    	scanf("%d%d",&n,&k);
    	for (int i=1;i<n;i++)
    	{
    		scanf("%d%d",&x,&y);
    		add(x,y);
    		add(y,x);
    	}
    	dfs1(1,0,0);
    	dfs2(p,0,0);  //求第一遍树的直径
    	if (k==1) return !printf("%d
    ",2*(n-1)-ans1+1);  //k=1
    	change(q,p);
    	dp(1,0);  //求第二遍树的直径
    	printf("%d
    ",2*(n-1)-ans1-ans2+2);  //k=2
    	return 0;
    }
    
  • 相关阅读:
    读书笔记——吴军《态度》
    JZYZOJ1237 教授的测试 dfs
    NOI1999 JZYZOJ1289 棋盘分割 dp 方差的数学结论
    [JZYZOJ 1288][洛谷 1005] NOIP2007 矩阵取数 dp 高精度
    POJ 3904 JZYZOJ 1202 Sky Code 莫比乌斯反演 组合数
    POJ2157 Check the difficulty of problems 概率DP
    HDU3853 LOOPS 期望DP 简单
    Codeforces 148D. Bag of mice 概率dp
    POJ3071 Football 概率DP 简单
    HDU4405 Aeroplane chess 飞行棋 期望dp 简单
  • 原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998448.html
Copyright © 2011-2022 走看看