HDU 6567
首先我们发现每棵树内部的距离已经固定,只有经过新边的路径才会产生贡献。
又因为重心到树上所有节点的距离和最小,所以我们连接两树重心。
然后我们想到一个经典套路:计算距离可以不枚举点,只枚举边。于是我们枚举每条边,计算出它们各自被经过的次数,再求和即为答案。
维护 \(siz_x\) 表示以 \(x\) 为根的子树大小,则由乘法原理,易得过 \(x \to fa_x\) 这条边的次数即为 \(siz_x \times (n-siz_x)\)。\(O(n)\)。
code
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+5;
vector<int> G[N];
int n,ans,sum;
int ctr1,ctr2;
int siz[N],mx[N];
bool tag[N];
void get_ctr(int cur,int fa,int f){
siz[cur]=1;
for(int i:G[cur]){
if(i!=fa){
get_ctr(i,cur,f);
siz[cur]+=siz[i];
mx[cur]=max(mx[cur],siz[i]);
}
}
mx[cur]=max(mx[cur],sum-siz[cur]);
if(mx[cur]*2<=sum){
if(f==1) ctr1=cur;
else ctr2=cur;
}
}
void mark(int cur,int fa){
tag[cur]=1,sum++;
for(int i:G[cur])
if(i!=fa)
mark(i,cur);
}
void dfs(int cur,int fa){
siz[cur]=1;
for(int i:G[cur])
if(i!=fa)
dfs(i,cur),siz[cur]+=siz[i];
}
signed main(){
cin>>n;
for(int i=1,u,v;i<=n-2;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
mark(1,0);
get_ctr(1,0,1);
memset(siz,0,sizeof siz);
memset(mx,0,sizeof mx);
for(int i=1;i<=n;i++){
if(!tag[i]){
sum=n-sum;
get_ctr(i,0,2);
break;
}
}
G[ctr1].push_back(ctr2),G[ctr2].push_back(ctr1);
//cout<<ctr1<<' '<<ctr2<<'\n';
memset(siz,0,sizeof siz),dfs(1,0);
for(int i=1;i<=n;i++)
ans+=siz[i]*(n-siz[i]);
cout<<ans;
return 0;
}
CF708C
考虑 bf 怎么做。
我们可以枚举每个点 \(i\) 作为根,维护最大子树大小 \(mx_i\)。
若 \(mx_i > \frac{n}{2}\),则尝试在 \(i\) 的重儿子 \(son_i\) 中分离一棵不超过 \(\frac{n}{2}\) 的子树 \(part_{son_i}\),分离后剩余的部分 \(x = mx_i-part_{son_i}\) 若满足 \(x \le \frac{n}{2}\),则说明 \(i\) 可为重心。
\(O(n^2)\)。
然后我们发现与其枚举 \(i\) 为重心,不如直接让整棵树的重心作为根,去发掘性质(套路:换根 dp 避免枚举)。
我们效仿求树的中心的换根 dp,维护 \(part1_i,part2_i\) 分别表示点 \(i\) 最大 / 次大的不超过 \(\frac{n}{2}\) 的子树大小,\(up_i\) 表示点 \(i\) 子树外最大的不超过 \(\frac{n}{2}\) 的子树大小,并依然像那样 dp 即可(具体实现见 code)。
最后,因为是以重心为根,所以子树内大小不会超过限制,于是仅需检查子树外切割后是否合法即可。
\(O(n)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int n,rt;
vector<int> G[N];
int mx[N],siz[N];
int part1[N],part2[N],up[N];
void get_ctr(int cur,int fa){
siz[cur]=1;
for(int i:G[cur]){
if(i==fa) continue;
get_ctr(i,cur);
siz[cur]+=siz[i];
mx[cur]=max(mx[cur],siz[i]);
}
mx[cur]=max(mx[cur],n-siz[cur]);
if(mx[cur]<=n/2) rt=cur;
}
void dfsd(int cur,int fa){
siz[cur]=1;
for(int i:G[cur]){
if(i==fa) continue;
dfsd(i,cur);
siz[cur]+=siz[i];
if(siz[i]>n/2) continue;
if(siz[i]>part1[cur])
part2[cur]=part1[cur],
part1[cur]=siz[i];
else if(siz[i]>part2[cur])
part2[cur]=siz[i];
}
}
void dfsu(int cur,int fa,int maxx){
up[cur]=maxx;
for(int i:G[cur]){
if(i==fa) continue;
//if(siz[i]>n/2) continue;
if(n-siz[cur]<=n/2) maxx=max(maxx,n-siz[cur]);
if(siz[i]!=part1[cur])
dfsu(i,cur,max(maxx,part1[cur]));
else
dfsu(i,cur,max(maxx,part2[cur]));
}
}
int main(){
cin>>n;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
get_ctr(1,0);
dfsd(rt,0);
dfsu(rt,0,0);
for(int i=1;i<=n;i++){
if((n-siz[i]-up[i]<=n/2)||rt==i)
cout<<"1 ";
else
cout<<"0 ";
}
return 0;
}
P1522
显然,
(因为若直径两端点不过连新边的两点,则原直径一定更长,否则一定后者更长)
于是我们维护 牧场直径(由下个信息取 max 可得,用并查集维护联通性,存在根下面)、牧场每个点到各自最远点距离(floyd + 取 max)即可。
code
#include<bits/stdc++.h>
using namespace std;
#define pdd pair<double,double>
#define ll long long
const int N=155;
const ll INF=1e9;
int n,fa[N];
double ans;
pdd p[N];
double dis[N][N],l[N],d[N];
void floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][k]+dis[k][j]<dis[i][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
double gdis(pdd a,pdd b){
return sqrt((a.first-b.first)*(a.first-b.first)+(a.second-b.second)*(a.second-b.second));
}
int fnd(int x){ return (x==fa[x]?x:fa[x]=fnd(fa[x])); }
void uni(int x,int y){ x=fnd(x),y=fnd(y); if(x!=y) fa[x]=y; }
int main(){
cin>>n;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) cin>>p[i].first>>p[i].second;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=(i==j?0:INF);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
char c; cin>>c;
if(c=='1')
dis[i][j]=gdis(p[i],p[j]),uni(i,j);
}
}
floyd();
for(int i=1;i<=n;i++){
double mx=-1e9;
for(int j=1;j<=n;j++)
if(dis[i][j]!=INF&&dis[i][j]>mx)
mx=dis[i][j];
d[i]=mx;
int r=fnd(i);
l[r]=max(l[r],d[i]);
}
ans=INF;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][j]==INF)
ans=min(ans,max(d[i]+d[j]+gdis(p[i],p[j]),max(l[fnd(i)],l[fnd(j)])));
cout<<setprecision(6)<<fixed<<ans;
return 0;
}