题目大意:
题目链接:https://www.luogu.org/problemnew/show/P4381
给出一个基环树森林,求每个基环树的最长路径长度之和。
思路:
神题!无限 o r z orz orz
首先,这是一个基环树森林,所以把每一棵基环树拆开来做。
对于任意一棵基环树,它的最长路径要么是以任意环上节点为根的子树的直径之和
,要么是环上任意两个点之间的距离加上它们到达以它们为根的子树任意一点的最大路径和
。
说人话,设
l
e
n
[
x
]
len[x]
len[x]表示以
x
x
x为根的子树(
x
x
x为环上的点)的直径,
d
i
s
[
x
]
dis[x]
dis[x]表示点
x
x
x到达最近的环上的点的距离,
m
a
x
d
i
s
[
x
]
maxdis[x]
maxdis[x](
x
x
x为环上的点)表示对于任意
y
y
y在以
x
x
x为根的子树的
m
a
x
{
d
i
s
[
y
]
}
max\{dis[y]\}
max{dis[y]},
d
i
s
t
[
x
]
[
y
]
dist[x][y]
dist[x][y]表示环上的第
x
x
x个点和第
y
y
y个点的距离,对于任意一棵基环树(环上的集合为
Q
Q
Q),它的最长路径就是
- m a x { l e n [ Q [ i ] ] } max\{ len[Q[i]] \} max{len[Q[i]]}
- m a x { d i s t [ Q [ x ] ] [ Q [ y ] ] + m a x d i s [ Q [ x ] ] + m a x d i s [ Q [ y ] ] } max\{ dist[Q[x]][Q[y]]+maxdis[Q[x]]+maxdis[Q[y]]\} max{dist[Q[x]][Q[y]]+maxdis[Q[x]]+maxdis[Q[y]]}
树的直径是可以
O
(
n
)
O(n)
O(n)求的。找环在
O
(
n
)
O(n)
O(n)中也没有问题。于是如何求出
m
a
x
{
d
i
s
t
[
Q
[
x
]
]
[
Q
[
y
]
]
+
m
a
x
d
i
s
[
Q
[
x
]
]
+
m
a
x
d
i
s
[
Q
[
y
]
]
}
max\{ dist[Q[x]][Q[y]]+maxdis[Q[x]]+maxdis[Q[y]]\}
max{dist[Q[x]][Q[y]]+maxdis[Q[x]]+maxdis[Q[y]]}就是当务之急。
所以现在的问题有两个:
- 如何 O ( n ) O(n) O(n)求出该式子
- d i s t [ x ] [ y ] dist[x][y] dist[x][y]的空间复杂度是 O ( n 2 ) O(n^2) O(n2),如何把空间压成 O ( n ) O(n) O(n)。
首先考虑第二个问题。我们发现,若我们记录一个
d
i
s
t
_
1
[
i
]
dist\_1[i]
dist_1[i]表示点
i
i
i到点1顺时针的距离,那么原先的
d
i
s
t
[
x
]
[
y
]
dist[x][y]
dist[x][y]就可以表示成
d
i
s
t
_
1
[
y
]
−
d
i
s
t
_
1
[
x
]
dist\_1[y]-dist\_1[x]
dist_1[y]−dist_1[x]。这样就可以把空间压缩成
O
(
n
)
O(n)
O(n)。
对于第一个问题。我们需要让
m
a
x
d
i
s
[
x
]
+
d
i
s
t
[
i
]
−
d
i
s
t
[
x
]
maxdis[x]+dist[i]-dist[x]
maxdis[x]+dist[i]−dist[x]尽量大,所以可以破环为链,复制一段到最后面,然后就是单调队列的模板了。每次维护
i
−
q
.
f
r
o
n
t
(
)
<
i-q.front()<
i−q.front()<环上的点的个数即可。
时间复杂度:
O
(
n
)
O(n)
O(n),非常
n
b
nb
nb的神题
o
r
z
o
r
z
o
r
z
orzorzorz
orzorzorz
代码:
#include <cstdio>
#include <queue>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const ll N=1000010;
ll in[N],head[N],father[N],len[N],f[N],dis[N],dist[N],maxdis[N],Q[N];
ll n,x,y,tot,cnt,ans,sum,maxlen,lastlen;
bool vis[N];
struct edge
{
ll next,to,dis;
}e[N*2];
void add(ll from,ll to,ll dis)
{
e[++tot].to=to;
e[tot].dis=dis;
e[tot].next=head[from];
head[from]=tot;
}
void topsort()
{
queue<int> q;
for (ll i=1;i<=n;i++)
if (in[i]==1) q.push(i);
while (q.size())
{
ll u=q.front(),v;
q.pop();
for (ll i=head[u];~i;i=e[i].next)
{
v=e[i].to;
if (in[v]>1)
{
in[v]--;
if (in[v]==1) q.push(v);
}
}
}
}
void dp(ll x,ll fa,ll root) //求树的直径
{
father[x]=root;
for (ll i=head[x];~i;i=e[i].next)
{
ll y=e[i].to;
if (y==fa||in[y]>1) continue;
dis[y]=dis[x]+e[i].dis;
maxdis[root]=max(maxdis[root],dis[y]);
dp(y,x,root);
len[root]=max(len[root],f[x]+f[y]+e[i].dis);
f[x]=max(f[x],f[y]+e[i].dis);
}
}
void find() //找环
{
bool ok;
do
{
ok=0;
vis[Q[cnt]]=1;
for (ll i=head[Q[cnt]];~i;i=e[i].next)
{
ll v=e[i].to;
if (in[v]>1&&!vis[v])
{
ok=1;
Q[++cnt]=v;
dist[cnt]=dist[cnt-1]+e[i].dis;
break;
}
else if (in[v]>1) lastlen=e[i].dis;
}
}
while (ok);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%lld",&n);
for (ll i=1;i<=n;i++)
{
scanf("%lld%lld",&x,&y);
add(i,x,y);
add(x,i,y);
in[i]++;
in[x]++;
}
topsort();
memset(vis,0,sizeof(vis));
for (ll i=1;i<=n;i++)
if (in[i]>1) dp(i,0,i); //求树的直径
for (ll j=1;j<=n;j++)
if (!vis[j]&&in[j]>1)
{
memset(Q,0,sizeof(Q));
memset(dist,0,sizeof(dist));
Q[1]=j; //记录换上的点
cnt=1;
find();
for (ll i=1;i<=cnt;i++) //复制一份
{
Q[i+cnt]=Q[i];
dist[i+cnt]=dist[i]+dist[cnt]+lastlen;
}
deque<int> q;
sum=0;
for (ll i=1;i<=2*cnt;i++)
{
while (q.size()&&i-q.front()>=cnt) q.pop_front(); //长度不能超过cnt
if (q.size())
sum=max(sum,maxdis[Q[i]]+maxdis[Q[q.front()]]+dist[i]-dist[q.front()]); //单调
while (q.size()&&maxdis[Q[i]]>maxdis[Q[q.back()]]+dist[i]-dist[q.back()])
q.pop_back();
q.push_back(i);
}
for (ll i=1;i<=cnt;i++)
sum=max(sum,len[Q[i]]);
ans+=sum;
}
printf("%lld\n",ans);
return 0;
}