蒟蒻第二次写题解,不清楚的地方见谅qwq
题目描述
共两个问题,第一问求树的直径长度,第二问求直径的必须边
思路
第一问很好求,lyd书里有,就不再赘述。
这里建议使用两次bfs的方法,因为关系到第二问的路径,这么做比较方便。
然后考虑第二问。
(注:必须边就是每条直径都要经过的一部分边)
容易知道,所有必须边构成一条链(有待证明)(其实是懒得想2333)
假设现在已经求得了一条直径的具体路径。设其两端分别为x和y。
从一端枚举到另外一端,并用l和r维护相交的部分。
先看x到y的。
设当前的节点为u。如果(u和x的距离)==(u不过直径能达到的最大长度),
说明u这里是直径的分叉口,l=u
再看y到x的。
同样的道理,若(u和y的距离)==(u不过直径的最大长度),r=u
这样就确定了l和r,完成!
(ps:记得开long long哦)
C++ 代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int n,tot=0,head[N],rt1,rt2,q[N],h,t,fro[N],hh[N];
bool vis[N];ll dis[N];
struct edge
{
int ver,nxt;
ll w;
}e[N*2];
void mmax( ll &x,ll y )
{
if ( y>x ) x=y;
}
void bfs( int st )
{
memset( vis,0,sizeof(vis) );
dis[st]=0; vis[st]=1; h=1; t=0; q[++t]=st;
while ( h<=t )
{
int x=q[h++];
for ( int i=head[x]; i; i=e[i].nxt )
{
int to=e[i].ver;
if ( vis[to] ) continue;
dis[to]=dis[x]+e[i].w; vis[to]=1; fro[to]=x;
q[++t]=to;
}
}
}
ll dfs( int x,int fa )
{
if ( fa!=0 && vis[x] ) return 0;
ll s=0;
for ( int i=head[x]; i; i=e[i].nxt )
{
int to=e[i].ver;
if ( to==fa || vis[to] ) continue;
mmax( s,dfs( to,x )+e[i].w );
}
return s;
}
void add( int u,int v,ll w )
{
tot++; e[tot].nxt=head[u]; e[tot].w=w; e[tot].ver=v; head[u]=tot;
}
int main()
{
scanf( "%d",&n );
for ( int i=1,x,y,l; i<n; i++ )
{
scanf( "%d%d%d",&x,&y,&l );
add( x,y,l ); add( y,x,l );
}
ll s=0; int x,l,r;
bfs( 1 );
for ( int i=1; i<=n; i++ )
if ( s<dis[i] ) s=dis[i],rt1=i;
bfs( rt1 ); s=0;
for ( int i=1; i<=n; i++ )
if ( s<dis[i] ) s=dis[i],rt2=i;
memset( vis,0,sizeof(vis) );
x=rt2; vis[rt1]=1;
while ( x!=rt1 )
{
vis[x]=1; hh[fro[x]]=hh[x]+1; x=fro[x];
}
l=rt2; r=rt1; x=rt2;
while ( x!=rt1 )
{
s=dfs( x,0 );
if ( s==dis[rt2]-dis[x] ) l=x;
if ( s==dis[x] )
{
r=x; break;
}
x=fro[x];
}
printf( "%lld\n%d",dis[rt2],hh[r]-hh[l] );
}
运行总时间:455ms 空间:15456 KB
完结撒花qwq
update
我找到证明了qwq
夏日大佬的一句话完结。
“显然直径的必须边一定连续,不然就成环了”
夏日大佬tql orz
推了94分钟推崩溃了,后来才知道这是一道省选题。。。
我也是这么想的,只是不知道这么写【捂脸】,真是太感谢了
你这跟夏日大佬的思路差不多啊, 当 n = 2e5, 且全在一条直径上, 你这就是 n^2,
求l, 和r 应该是一次bfs, https://www.acwing.com/solution/content/19389/
全在一条直径上是指啥qwq
$a_1 -> a_2 ->..->a_n $ 所有点成一条直线
但是这样找的时候不是会return掉吗,就是如果找到的点vis为1的话说明是直径上的点,就直接return了,复杂度应该还是线性的啊
抱歉, 没看dfs里面,这样返回的是线性的
为啥没 TLE 啊,直径上每个点都会遍历一次图,应该是 $O(N^2)$ 啊
如果你说的是第一问的话,书上比较详细可以看看,就是先从任意一个点出发,bfs一次得到最远的节点记为p,然后从p出发bfs一次得到最远的q,p到q就是一条直径,这样的复杂度就是O(2N)而不是N^2
如果是第二问,dfs的作用是找某一个节点到树上另一个节点的最远距离,之前标记直径路径的时候vis[x]=1就是为了保证dfs出的路径不过直径。(记录hh是为了输出的时候可以直接得到必须链的长度),dfs最多是一条直径上的点,不会很多;就算是退化成链了,一旦找到端点就会break掉,不会T(程序里的实现是两个端点一起找,两个都找到退出)(以上仅仅是我的理解,错了请指正)
先找一条直径,把直径上的边的权值减去1,再找一遍直径,差便是答案。
这是另一篇题解的解释,因为当前的直径肯定是最长的一条(之一),都减一以后,再找一条,如果必须边有x条,这x条是肯定会经过的,那么找出来的直径长度就会少了x,我觉得更加巧妙。如果你喜欢这样做的话,希望做完可以发一篇题解哦~
(引诱同学写题解.jpg)
谢谢 : ) /qq
nb ,orz
ORZ
撒花
%%%
orz
前排资瓷
qwq