题目大意:有n个点和n条边的连通图,求每一个点到其他所有点的最短路和。
思路:原图因为是连通图,所以就是一棵树加上了一条边,形成了一个基环外向树(又称环套树),形状上就是一个环,环上每个点向外延伸出一棵树。
对于“外向树”上的点,统计答案较为简单,是一个经典的树形DP模型。f[i]表示i的子树到i这个节点的距离和,sz[i]表示i的子树大小,最后再加上i到根的距离*(n-sz[i])即可。
而对于“基环”的点,就不是那么容易统计了。一整个环,我们会发现从一个点往其他所有点走的时候,有两种路径:顺时针走或者逆时针走。很明显这两种决策的分界线在dis==sumdis/2。我们在树上用two pointers来统计这条路径上包括所有外向树的节点个数和边权和,最后推推式子,求个和乘起来加加减减即可。
#include<bits/stdc++.h>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=200010;
struct edge
{
int next,to,val;
};
edge e[MAXN*2];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
bool in_circle[MAXN],visit[MAXN];
bool Found;
int from[MAXN];
vector <int> cir;
void dfs_cl(int x,int fa)
{
visit[x]=1;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (v==fa) continue;
if (visit[v])
{
int tmp=x;
while (tmp!=v)
{
cir.push_back(tmp);
in_circle[tmp]=1;
tmp=from[tmp];
}
Found=1;
in_circle[v]=1;
cir.push_back(v);
continue;
}
from[v]=x;
dfs_cl(v,x);
if (Found) return ;
}
}
int sz[MAXN],fa[MAXN],root[MAXN];
ll f[MAXN],dis[MAXN];
void dfs(int x,int rt)
{
sz[x]++;
root[x]=rt;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (in_circle[v] || fa[v]) continue;
fa[v]=x;
dis[v]=dis[x]+e[i].val;
dfs(v,rt);
sz[x]+=sz[v];
f[x]+=f[v]+sz[v]*e[i].val;
}
}
ll ans[MAXN];
int n;
void dfs2(int x,int dist,int size)
{
visit[x]=1;
ans[x]+=dist+f[x]+dis[x]*(n-sz[root[x]]);
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (visit[v] || in_circle[v]) continue;
int szv=size+sz[x]-sz[v];
int disv=dist+e[i].val*(szv-sz[v])+f[x]-f[v];
dfs2(v,disv,szv);
}
}
ll cst[MAXN*3],Ans[MAXN];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
for (int i=1;i<=n;i++)
if (!Found)
dfs_cl(i,-1);
int num=cir.size();
for (int i=0;i<num;i++)
dfs(cir[i],cir[i]);
memset(visit,0,sizeof(visit));
for (int i=0;i<num;i++)
dfs2(cir[i],0,0),cir.push_back(cir[i]);
for (int i=0;i<num;i++)
cir.push_back(cir[i]);
for (int i=0;i<num;i++)
{
for (int j=head[cir[i+1]];j;j=e[j].next)
{
int v=e[j].to;
if (v==cir[i])
cst[i+num+num+1]=cst[i+num+1]=cst[i+1]=e[j].val;
}
}
for (int i=1;i<3*num;i++)
cst[i]+=cst[i-1];
ll anss=0;
for (int i=1;i<num;i++)
anss+=(cst[num]-cst[i])*sz[cir[i]];
int j=num+1,ncnt=sz[cir[num]];
ll tot=cst[num];
ll all=0;
for (int i=0;i<num;i++)
all+=ans[cir[i]];
for (int i=num;i<2*num;i++)
{
while (cst[j]-cst[i]<=tot/2)
{
ncnt+=sz[cir[j]];
anss+=(2*(cst[j]-cst[i])-tot)*sz[cir[j]];
j++;
}
Ans[cir[i]]=anss;
ncnt-=sz[cir[i]];
anss+=(n-ncnt*2)*(cst[i+1]-cst[i]);
}
for (int i=1;i<=n;i++)
printf("%I64d ",Ans[root[i]]+ans[i]+all-ans[root[i]]);
return 0;
}
个人的几个坑点:1.DFS找环竟然跪了一发。。
2.基环上的答案统计不要推错式子。。
3.long long又一次成为了众矢之的