题目大意:
题目链接:https://www.luogu.org/problemnew/show/P4381
给出一个基环树森林,求每个基环树的最长路径长度之和。
思路:
神题!无限
首先,这是一个基环树森林,所以把每一棵基环树拆开来做。
对于任意一棵基环树,它的最长路径要么是以任意环上节点为根的子树的直径之和
,要么是环上任意两个点之间的距离加上它们到达以它们为根的子树任意一点的最大路径和
。
说人话,设表示以为根的子树(为环上的点)的直径,表示点到达最近的环上的点的距离,(为环上的点)表示对于任意在以为根的子树的,表示环上的第个点和第个点的距离,对于任意一棵基环树(环上的集合为),它的最长路径就是
树的直径是可以求的。找环在中也没有问题。于是如何求出就是当务之急。
所以现在的问题有两个:
- 如何求出该式子
- 的空间复杂度是,如何把空间压成。
首先考虑第二个问题。我们发现,若我们记录一个表示点到点1顺时针的距离,那么原先的就可以表示成。这样就可以把空间压缩成。
对于第一个问题。我们需要让尽量大,所以可以破环为链,复制一段到最后面,然后就是单调队列的模板了。每次维护环上的点的个数即可。
时间复杂度:,非常的神题
代码:
#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
",ans);
return 0;
}