容易想到用\(\text{Tarjan}\)求割点。对于非割点,会损失\(2\times(n-1)\)次访问(注意是互相访问,所以要乘2);对于割点,损失的访问次数即为\(\sum^{k}_{i=1}sz[i]\times(n-sz[i])\)(割点被删后,产生\(k\)个联通块,第\(i\)个联通块大小为\(sz[i]\))。在求割点时顺便安装上述分类讨论即可。
注意:求完\(u\)的子树中所有割点后,还要加上剩下来的大联通块\((n-sum-1)\times(sum+1)\)和自己\(1\times(n-1)\)次访问。
#include <cstdio>
#define MAXN 100010
#define MAXM 500010*2
#define MIN(A,B) ((A)<(B)?(A):(B))
#define MAX(A,B) ((A)>(B)?(A):(B))
#define ll long long
using namespace std;
int head[MAXN],vv[MAXM],nxt[MAXM],tot;
int read(){
int w=1,q=0;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
return w*q;
}
void add_edge(int u, int v){
vv[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
int n,m;
ll ans[MAXN];
int dfn[MAXN],cnt,low[MAXN],sz[MAXN];
void tarjan(int u){
dfn[u]=++cnt;
low[u]=dfn[u];
sz[u]=1;
int son_sz=0,sum=0;
bool cut=0;
for(int i=head[u];i;i=nxt[i]){
int v=vv[i];
++son_sz;
if(dfn[v]==0){
tarjan(v);
low[u]=MIN(low[u], low[v]);
sz[u]+=sz[v];
if(low[v]>=dfn[u]){
sum+=sz[v];
ans[u]+=(ll)sz[v]*(n-sz[v]);
if(u!=1) cut=1;
}
}else low[u]=MIN(low[u], dfn[v]);
}
if(u==1&&son_sz>1) cut=1;
if(!cut) ans[u]=(n-1)*2;
else ans[u]+=(ll)(n-sum-1)*(sum+1)+n-1;
}
int main() {
n=read(),m=read();
while(m--){
int a=read(),b=read();
add_edge(a,b);
add_edge(b,a);
}
tarjan(1);
for(int i=1;i<=n;++i) printf("%lld\n", ans[i]);
return 0;
}
原文:https://www.cnblogs.com/santiego/p/11228783.html