• 【BZOJ3124】[Sdoi2013]直径 树形DP(不用结论)


    【BZOJ3124】[Sdoi2013]直径

    Description

    小Q最近学习了一些图论知识。根据课本,有如下定义。树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度。如果一棵树有N个节点,可以证明其有且仅有N-1 条边。 路径:一棵树上,任意两个节点之间最多有一条简单路径。我们用 dis(a,b)
    表示点a和点b的路径上各边长度之和。称dis(a,b)为a、b两个节点间的距离。  
     直径:一棵树上,最长的路径为树的直径。树的直径可能不是唯一的。 
    现在小Q想知道,对于给定的一棵树,其直径的长度是多少,以及有多少条边满足所有的直径都经过该边。 

    Input

    第一行包含一个整数N,表示节点数。 
    接下来N-1行,每行三个整数a, b, c ,表示点 a和点b之间有一条长度为c
    的无向边。 

    Output

    共两行。第一行一个整数,表示直径的长度。第二行一个整数,表示被所有
    直径经过的边的数量。 

    Sample Input

    6
    3 1 1000
    1 4 10
    4 2 100
    4 5 50
    4 6 100

    Sample Output

    1110
    2
    【样例说明】
    直径共有两条,3 到2的路径和3到6的路径。这两条直径都经过边(3, 1)和边(1, 4)。

    HINT

    对于100%的测试数据:2≤N≤200000,所有点的编号都在1..N的范围内,
    边的权值≤10^9。

    题解:网上都说这题是结论题,吓傻了~

    第一问随便求,直接说第二问。

    首先,正难则反,统计被所有直径都经过的边有点困难,我们可以统计那些边被至少一条直径错过。我们在求直径的时候可以维护每个点子树中深度的最大值和次大值(二者不再同一个儿子的子树中),如果最大值和次大值组合起来刚好是一条直径,那么不再这两个子树中的所有边就都一定错过了,我们在DFS序上打一个标记即可(用差分)。但是有一个问题,最大值和次大值组合起来不一定是唯一的直径,可能最大值和次次大值也能组合起来成为直径,还有次次次。。。。不过我们有必要把这些都记录下来吗?显然没有,我们只需要记录最大,次大,次次大即可,这3个儿子的组合已经足以“淘汰”掉剩余的所有儿子。(这里需要自己思考一下)

    但是这还不够,我们刚才讨论只是直径上的最高点,还要继续考虑下面的边。这里需要记录h[x]代表x子树外的点到x的最远距离,如果x子树内的深度最大值和h[x]能组成一条直径,那么我们就可以将x的其他儿子全都淘汰掉。但是同上,我们还需要看一下最大值也是否应该被淘汰掉,所以用h[x]和次大值组合一下就行了。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    typedef long long ll;
    const int maxn=200010;
    int n,cnt,ans;
    int to[maxn<<1],next[maxn<<1],head[maxn],fa[maxn],p[maxn],q[maxn],Q[maxn];
    ll len,val[maxn<<1],dep[maxn],lf[maxn],g[maxn][3],h[maxn],s[maxn];
    inline int rd()
    {
    	int ret=0,f=1;	char gc=getchar();
    	while(gc<'0'||gc>'9')	{if(gc=='-')f=-f;	gc=getchar();}
    	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
    	return ret*f;
    }
    inline void add(int a,int b,int c)
    {
    	to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++;
    }
    inline void pushup(int x,int y)
    {
    	if(lf[y]>lf[g[x][0]])	g[x][2]=g[x][1],g[x][1]=g[x][0],g[x][0]=y;
    	else	if(lf[y]>lf[g[x][1]])	g[x][2]=g[x][1],g[x][1]=y;
    	else	if(lf[y]>lf[g[x][2]])	g[x][2]=y;
    }
    void dfs(int x)
    {
    	p[x]=++Q[0],Q[Q[0]]=x;
    	lf[x]=dep[x];
    	for(int i=head[x],y;i!=-1;i=next[i])	if(to[i]!=fa[x])
    	{
    		y=to[i],fa[y]=x,dep[y]=dep[x]+val[i],dfs(y),lf[x]=max(lf[x],lf[y]),pushup(x,y);
    	}
    	len=max(len,lf[g[x][0]]+lf[g[x][1]]-2*dep[x]);
    	q[x]=Q[0];
    }
    inline void sumup(int a,int b)
    {
    	if(a<=b)	s[a]++,s[b+1]--;
    }
    inline void updata(int x,int a,int b)
    {
    	sumup(1,p[x]),sumup(q[x]+1,n);
    	if(p[a]>p[b])	swap(a,b);
    	sumup(p[x]+1,p[a]-1),sumup(q[a]+1,p[b]-1),sumup(q[b]+1,q[x]);
    }
    int main()
    {
    	n=rd();
    	int i,x,y,a,b,c;
    	memset(head,-1,sizeof(head));
    	for(i=1;i<n;i++)	a=rd(),b=rd(),c=rd(),add(a,b,c),add(b,a,c);
    	dfs(1);
    	for(i=1;i<=n;i++)
    	{
    		x=Q[i],y=fa[x];
    		if(lf[g[x][0]]+lf[g[x][1]]-2*dep[x]==len)	updata(x,g[x][0],g[x][1]);
    		if(lf[g[x][0]]+lf[g[x][2]]-2*dep[x]==len)	updata(x,g[x][0],g[x][2]);
    		if(lf[g[x][1]]+lf[g[x][2]]-2*dep[x]==len)	updata(x,g[x][1],g[x][2]);
    		if(x==g[y][0])	h[x]=max(h[y],lf[g[y][1]]-dep[y]*2);
    		else	h[x]=max(h[y],lf[g[y][0]]-dep[y]*2);
    		if(h[x]+lf[x]!=len)	s[p[x]]++,s[p[x]+1]--;
    		if(h[x]+lf[g[x][0]]==len)	sumup(p[x]+1,p[g[x][0]]-1),sumup(q[g[x][0]]+1,q[x]);
    		if(h[x]+lf[g[x][1]]==len)	sumup(p[x]+1,p[g[x][1]]-1),sumup(q[g[x][1]]+1,q[x]);
    	}
    	for(i=1;i<=n;i++)	s[i]+=s[i-1],ans+=(i!=1)&&(!s[i]);
    	printf("%lld
    %d",len,ans);
    	return 0;
    }
  • 相关阅读:
    C#对SQLite、Access数据库操作的封装,很好用的~
    如何使用MFC连接Access数据库
    字节、十六进制字符串相互转换(asc2hex、hex2asc)
    Oracle数据库模式关系和数据备份导出导入
    Oracle数据库sql常用
    Oracle数据库的函数,存储过程,程序包,游标,触发器
    收集一下
    JS 获取随机颜色值
    IDEA webapp文件夹不识别解决方案
    使用postman请求响应Invalid CORS request
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7536611.html
Copyright © 2020-2023  润新知