有 n n n个点 n n n条双向边
对于 x x x到 y y y
假设有边则你可以选择走路加经验或开挂没经验
假设每边则你可以选呢开挂没经验
问在每个点都不重复经过的情况下的最高经验
依题得
这是一个基环树森林,开挂其实就是从这个基环树到另一棵基环树,所以答案就是所有基环树的直径和。
怎么求基环树的直径呢?我们假设它只是一棵树,很明显可以直接 d p dp dp,首先我们强行求出原来树的直径,由于这是一棵基环树,所以我还有 n n n种在环上的走法,对于这 n n n中走法,可表示为
可以发现这就是维护区间长度小于 m m m的最大和,用单调队列维护即可
#include
#include
#include
#include
#define ri register int
using namespace std;int n,m,block,l[1000010],tot,unb[1000010],rd[1000010];
//l为邻接表数组
//unb为每个节点隶属的联通块
//rd为每个节点的入度
struct node{int next,to,w;}e[2000010];//边
inline void add(ri u,ri v,ri w){e[++tot]=(node){l[u],v,w};l[u]=tot;return;}
long long ans,f[1000010],dis[1000010],d[1000010],a[1000010];
//f为树的直径的f数组
//dis为树上第i个节点到第1个节点的距离
//di表示第i棵基环树的答案,当然不卡数组也行
//a为环中每个节点的f用于单调队列
bool vis[1000010];//标记每个联通块是否走过
inline char Getchar()
{
static char buf[100000],*p1=buf+100000,*pend=buf+100000;
if(p1==pend)
{
p1=buf; pend=buf+fread(buf,1,100000,stdin);
if (pend==p1) return -1;
}
return *p1++;
}
inline long long read()
{
char c;int d=1;long long f=0;
while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline void write(register long long x)
{
if(x<0)write(45),x=-x;
if(x>9)write(x/10);
putchar(x%10+48);
return;
}//以上为读写优化,卡到了洛谷最优解Page 1
inline void dfs(ri x)
{
unb[x]=block;//标记联通块
for(ri i=l[x];i;i=e[i].next)
{
int y=e[i].to;
if(!unb[y]) dfs(y);//搜索
}
return;
}
inline void topsort()//拓扑排序
{
queue<int>q;
int y,w;
for(ri i=1;i<=n;i++) if(!(--rd[i])) q.push(i);
//因为是无向联通图,每个节点都有>=1的入度,同时-1下面的判断就会方便很多
while(q.size())
{
int x=q.front();q.pop();
for(ri i=l[x];i;i=e[i].next)
if(rd[y=e[i].to])
{
w=e[i].w;
if(!--rd[y]) q.push(y);
d[unb[x]]=max(d[unb[x]],f[x]+f[y]+w);
f[y]=max(f[y],f[x]+w);
//因为topsort相当于一个宽搜,所以我们可以“顺便”求直径
}
}
return;
}
inline void dp(ri t,ri x)
{
int m=0,y=x,i;//m表示环上节点个数,y表示当前环上点
do
{
a[++m]=f[y];rd[y]=0;//放入a数组,并标记已删除防止重复计算
for(i=l[y];i;i=e[i].next)
if(rd[e[i].to])//若拓扑排序结束后入度不为0则其为环上的点
{
dis[m+1]=dis[m]+e[i].w;y=e[i].to;//接上
break;
}
}while(i);
if(m==2)//这里是重边情况,要特判
{
int w=0;
for(ri i=l[y];i;i=e[i].next)
if(e[i].to==x) w=max(w,e[i].w);//找到那两条边,取个最大值
d[t]=max(d[t],f[x]+f[y]+w);//保存
return;//退出
}
for(ri i=l[y];i;i=e[i].next)//否则就接上
if(e[i].to==x)
{
dis[m+1]=dis[m]+e[i].w;
break;
}
for(ri i=1;i<=m;i++) a[i+m]=a[i],dis[i+m]=dis[m+1]+dis[i];//破环为链
deque<int>q;q.push_back(1);
for(ri i=2;i<(m<<1);i++)
{
while(q.size()&&i-q.front()>=m) q.pop_front();//长度不小于m就弹出,因为我们不可重复走,所以不能连续m个
if(q.size()) d[t]=max(d[t],dis[i]-dis[q.front()]+a[i]+a[q.front()]);//保存
while(q.size()&&a[q.back()]-dis[q.back()]<=a[i]-dis[i]) q.pop_back();//保持队列单调性
q.push_back(i);//放入
}
return;
}
signed main()
{
n=read();
for(ri i=1,x,y;i<=n;i++)
{
rd[i]++;rd[x=read()]++;//入度++
y=read();add(i,x,y);add(x,i,y);//连边
}
for(ri i=1;i<=n;i++) if(!unb[i]) block++,dfs(i);//标记联通块
topsort();//拓扑排序
for(ri i=1;i<=n;i++)
if(rd[i]&&!vis[unb[i]])//是环中点且该联通块没被处理过
{
vis[unb[i]]=true;//标记
dp(unb[i],i);//dp
ans+=d[unb[i]];//加上答案
}
write(ans);//输出
}