分治:处理问题的重要手段(2)
分治不仅可以处理序列问题,在处理动态图问题关于时间分治,或者在处理树上问题时关于重心分治,都是常用的。
在处理动态图的联通性问题时,若允许询问离线,可考虑利用线段树对时间进行分治,从而将并查集不易进行的删除操作,转化为易进行的撤销操作。
\(CF1217F\)
https://www.luogu.com.cn/problem/CF1217F
题意:\(n\)个点无向图,两种操作:
1:给定\(x,y\),若边\((x,y)\)存在则删除,否则加入。
2:给定\(x,y\),询问\(x,y\)间的联通性。
本题要求在线,即\(x,y\)真实值与上一次询问操作答案有关。
发现询问的答案只有\(0,1\),这说明每次操作一的可能性也只有两种,考虑将所有可能边都考虑,设可能出现\((x,y)\)的时间点为\(t_1,t_2,...,t_k\),则对应线段树上的线段应该为\([t_1,t_2-1],[t_2,t_3-1],...,[t_k,m]\),先将其全部加入到线段树中,与离线做法相比,我们在加入\([t_i,t_{i+1}-1]\)时,需要知道时刻\(t_i\)的答案,但这样会进入一个死循环,加入\([t_i,t_{i+1}-1]\)要知道\(t_i\)处答案,但在遍历至\(t_i\)前,一定要对\([t_i,t_{i+1}-1]\)做出抉择,否则会影响其余点的答案。一个解决方法是把\([t_i,t_{i+1}-1]\)变为\([t_i+1,t_{i+1}-1]\),这样做的正确性是由于\(t_i\)时刻一定不是操作二,也就是\(t_i\)的抉择是事先确定的,不会被\([t_i,t_{i+1}-1]\)的抉择所影响。
于是有以下算法流程:
1:对时间建立线段树,并将所有可能区间存入
2:遍历线段树,并动态维护一个标记数组\(f(x,y)\),代表是否要加入\(f(x,y)\)这条边
3:遍历到叶子时,得到真实操作对\((x,y)\),若为询问,直接询问即可,若为修改,令\(f(x,y)=f(x,y)\bigoplus 1\)即可
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int n,m,now;
int ans[N];
map<PII,int> f,pre;
struct Operator{
int tp,x,y;
}op[N];
struct DisjointSetUnion{
int top;
int p[N],sz[N];
PII stk[N];
void init(){
for(int i=1; i<=n; i++) p[i]=i;
for(int i=1; i<=n; i++) sz[i]=1;
}
int find(int x){
if(x==p[x]) return x;
return find(p[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y) return;
if(sz[x]>sz[y]) swap(x,y);
p[x]=y,sz[y]+=sz[x];
stk[++top]={x,y};
}
void del(){
PII t=stk[top--];
int x=t.first,y=t.second;
p[x]=x,sz[y]-=sz[x];
}
}dsu;
struct SegmentTree{
struct Node{
int l,r;
vector<PII> op;
}tr[4*N];
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int x,int y){
if(l>r) return;
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].op.push_back({x,y});
return;
}
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,x,y);
if(r>mid) modify(u<<1|1,l,r,x,y);
}
void solve(int u){
int t=dsu.top;
for(int i=0; i<tr[u].op.size(); i++){
int x=tr[u].op[i].first,y=tr[u].op[i].second;
if(f[{x,y}]) dsu.merge(x,y);
}
if(tr[u].l==tr[u].r){
int tp=op[tr[u].l].tp,x=op[tr[u].l].x,y=op[tr[u].l].y;
x=(x+now-1)%n+1,y=(y+now-1)%n+1;
if(x>y) swap(x,y);
if(tp==1) f[{x,y}]^=1;
else now=ans[tr[u].l]=dsu.find(x)==dsu.find(y);
while(dsu.top>t) dsu.del();
return;
}
solve(u<<1),solve(u<<1|1);
while(dsu.top>t) dsu.del();
}
}tree;
int main(){
cin >> n >> m;
tree.build(1,1,m);
for(int i=1; i<=m; i++){
int tp,x,y;
cin >> tp >> x >> y;
op[i]={tp,x,y};
if(tp==2) continue;
int x1=(x-1)%n+1,y1=(y-1)%n+1;
if(x1>y1) swap(x1,y1);
if(pre.find({x1,y1})!=pre.end())
tree.modify(1,pre[{x1,y1}]+1,i-1,x1,y1);
int x2=x%n+1,y2=y%n+1;
if(x2>y2) swap(x2,y2);
if(pre.find({x2,y2})!=pre.end())
tree.modify(1,pre[{x2,y2}]+1,i-1,x2,y2);
pre[{x1,y1}]=pre[{x2,y2}]=i;
}
for(auto it:pre)
tree.modify(1,it.second+1,m,it.first.first,it.first.second);
dsu.init();
tree.solve(1);
for(int i=1; i<=m; i++)
if(op[i].tp==2) cout << ans[i];
cout << endl;
return 0;
}
\(CF576E\)
https://www.luogu.com.cn/problem/CF576E
题意:给定\(n\)点,\(m\)条边的无向图,有\(k\)种颜色,一开始每条边都没颜色,定义合法状态为仅保留染成\(k\)种颜色中的任何一种颜色的边,图都是一张二分图。给\(q\)次操作,第\(i\)次操作将第\(e_i\)条边的颜色染成\(c_i\),每次操作只有合法才会被执行,判断每次操作是否会被执行。
题解:若\(k=1\),即为线段树分治模板,https://www.luogu.com.cn/problem/solution/CF576E,此题可考虑用\(k\)个扩展域并查集维护。对于相邻的对同一边染色操作\(i,j\),操作\(i\)的影响范围为\([i,j-1]\),而\([i,j-1]\)所染颜色有两种情况,原先颜色或者新颜色,这取决于第\(i\)次操作是否合法。于是在分治到叶子节点\(i\)时,判断能否执行,如果不能执行则令\([i,j-1]\)颜色保留,反之改为\(c_i\)。同样为保证不出现,\([i,j-1]\)取决于\(i\),而\([i,j-1]\)又必须立马抉择的尴尬境地,并依靠\(i\)仅为判断操作,将\([i,j-1]\)替换为\([i+1,j-1]\)即可。
// LUOGU_RID: 167786784
// LUOGU_RID: 167721679
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,k,q,top;
int p[52][2*N],sz[52][2*N];
int u[N],v[N],color[N];
int op[N],col[N],now[N];
struct Node {
int l,r;
vector<int> q;
} tr[4*N];
struct Edge {
int c,x,y;
};
Edge stk[4*N];
void build(int u,int l,int r) {
tr[u]= {l,r};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int x) {
if(l>r) return;
if(tr[u].l>=l&&tr[u].r<=r) {
tr[u].q.push_back(x);
return;
}
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,x);
if(r>mid) modify(u<<1|1,l,r,x);
}
int find(int c,int x) {
while(x!=p[c][x]) x=p[c][x];
return x;
}
void merge(int c,int x,int y) {
x=find(c,x),y=find(c,y);
if(x==y) return;
if(sz[c][x]>sz[c][y]) swap(x,y);
p[c][x]=p[c][y],sz[c][y]+=sz[c][x];
stk[++top]= {c,x,y};
}
void del() {
int c=stk[top].c,x=stk[top].x,y=stk[top].y;
p[c][x]=x,sz[c][y]-=sz[c][x],top--;
}
void solve(int now) {
int t=top;
for(int i=0; i<tr[now].q.size(); i++) {
int id=tr[now].q[i];
int x=u[id],y=v[id],c=color[id];
if(!c) continue;
merge(c,x,y+n),merge(c,x+n,y);
}
if(tr[now].l==tr[now].r) {
int id=tr[now].l;
int x=u[op[id]],y=v[op[id]],c=col[id];
if(find(c,x)==find(c,y)) cout << "NO" << endl;
else cout << "YES" << endl,color[op[id]]=c;
} else solve(now<<1),solve(now<<1|1);
while(top>t) del();
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k >> q;
build(1,1,q);
for(int i=1; i<=m; i++) cin >> u[i] >> v[i];
for(int i=1; i<=q; i++) cin >> op[i] >> col[i];
for(int i=1; i<=m; i++) now[i]=q+1;
for(int i=q; i; i--) {
modify(1,i+1,now[op[i]]-1,op[i]);
now[op[i]]=i;
}
for(int i=1; i<=50; i++)
for(int j=1; j<=2*n; j++) p[i][j]=j;
for(int i=1; i<=50; i++)
for(int j=1; j<=2*n; j++) sz[i][j]=1;
solve(1);
return 0;
}
动态图问题,可以通过分治的方法去降低有用边数
\(P3206\)
https://www.luogu.com.cn/problem/P3206
题意:待补
题解:待补
\(P5163\)
https://www.luogu.com.cn/problem/P5163
题意:待补
题解:待补
树分治,通常分为点分治和边分治,二者本质上没有区别。树分治的核心是利用树上一些特殊点的性质,比如重心,不断以这些特殊点为分治中心递归处理问题。
\(CF833D\)
https://www.luogu.com.cn/problem/CF833D
题意:给定一棵树,边有颜色和权值,颜色分为黑色和白色,求满足黑白边比例在\([\frac{1}{2},2]\)的路径边权乘积的乘积。
题解:树上路径统计问题考虑点分治,枚举分治中心,考虑计算经过分治中心的路径贡献。设两条路径分别有黑白边\((a_1,b_1),(a_2,b_2)\)条,则两路径合并后合法,应满足
\(2*(a_1+a_2)>=b_1+b_2\),即\(2*a_1-b_1>=b_2-2*a_2\)
\(2*(b_1+b_2)>=a_1+a_2\),即\(2*b_1-a_1>=a_2-2*b_2\)
注意一个重要性质,若不满足等式\(1\),则一定满足等式\(2\),反之亦然。
于是考虑容斥,用满足等式\(1\)的路径贡献除不满足等式\(2\)的路径贡献即为经过分治中心的路径贡献。
具体满足相关等式的路径贡献的计算方法,对\(2*a_1-b_1,2*b_1-a_1\)建立树状数组并维护前缀积和前缀个数即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10,P=1e9+7;
int n,T,ans=1;
int h[N],ne[2*N],idx;
int e[2*N],w[2*N],col[2*N];
bool st[N];
struct Node{
int s1,s2,s3;
};
vector<Node> q;
struct BitTree{
int v[4*N],sz[4*N],flag[4*N];
inline int lowbit(int x){
return x&-x;
}
inline void modify(int k,int x){
k+=2*n;
for(int i=k; i<=4*n; i+=lowbit(i)){
if(flag[i]!=T) v[i]=1,sz[i]=0,flag[i]=T;
v[i]=(LL)v[i]*x%P,sz[i]++;
}
}
inline void query(int k,int &v1,int &v2){
k+=2*n,v1=1,v2=0;
for(int i=k; i; i-=lowbit(i)){
if(flag[i]!=T) continue;
v1=(LL)v1*v[i]%P,v2+=sz[i];
}
}
}tree1,tree2;
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=(LL)res*a%P;
a=(LL)a*a%P;
b>>=1;
}
return res;
}
inline int inv(int x){
return qpow(x,P-2);
}
inline void add(int a,int b,int c,int d){
e[idx]=b,w[idx]=c,col[idx]=d;
ne[idx]=h[a],h[a]=idx++;
}
inline int get_size(int u,int fa){
if(st[u]) return 0;
int sz=1;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
sz+=get_size(j,u);
}
return sz;
}
inline int get_center(int u,int fa,int tot,int ¢er){
if(st[u]) return 0;
int sz=1,mx=0;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
int v=get_center(j,u,tot,center);
sz+=v,mx=max(mx,v);
}
mx=max(mx,tot-sz);
if(mx<=tot/2) center=u;
return sz;
}
inline void get_val(int u,int fa,int s1,int s2,int s3){
if(st[u]) return;
q.push_back({s1,s2,s3});
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
if(col[i]) get_val(j,u,s1+2,s2-1,(LL)s3*w[i]%P);
else get_val(j,u,s1-1,s2+2,(LL)s3*w[i]%P);
}
}
inline void calc(vector<Node> q){
for(int i=0; i<q.size(); i++){
int s1=q[i].s1,s2=q[i].s2,s3=q[i].s3,v,sz;
tree1.query(s1,v,sz);
ans=(LL)ans*v%P*qpow(s3,sz)%P;
tree2.query(-s2-1,v,sz);
ans=(LL)ans*inv((LL)v*qpow(s3,sz)%P)%P;
}
for(int i=0; i<q.size(); i++){
int s1=q[i].s1,s2=q[i].s2,s3=q[i].s3;
tree1.modify(-s1,s3);
tree2.modify(s2,s3);
}
}
inline void solve(int u){
if(st[u]) return;
get_center(u,-1,get_size(u,-1),u);
st[u]=1;
T++,tree1.modify(0,1),tree2.modify(0,1);
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
q.clear();
if(col[i]) get_val(j,u,2,-1,w[i]);
else get_val(j,u,-1,2,w[i]);
calc(q);
}
for(int i=h[u]; i!=-1; i=ne[i]) solve(e[i]);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
memset(h,-1,sizeof h);
cin >> n;
for(int i=1; i<=n-1; i++){
int a,b,c,d;
cin >> a >> b >> c >> d;
add(a,b,c,d),add(b,a,c,d);
}
solve(1);
cout << (ans%P+P)%P << endl;
return 0;
}
点分治还可以处理树上其余相关问题
\(P6326\)
https://www.luogu.com.cn/problem/P6326
题意:树上背包,要求买了物品的点构成一个连通块
题解:考虑点分治,每次统计包含当前分治中心的选择方案的最优解。
在考虑\(dp\)时候,进行如下流程:
1:进入某个节点时,将当前背包存下
2:强制选择一个当前节点的物品
3:对当前节点其余物品做多重背包
4:向当前节点儿子递归
5:离开当前节点时与进入时的背包值取\(max\)
理性理解下,\(dp\)数组存储的信息是仅考虑\(dfn\)小于当前点的点和以当前点为根子树中的点,组成的最优方案。进入当前点时,\(dp\)数组为从根到父亲路径上点必选,\(dfn\)小于当前点的点任意,子树中点不选的最优方案。离开时,\(dp\)数组存储的信息是从根到父亲路径上所有点必选,\(dfn\)小于当前点的点任意,子树的根必选,子树其余点任意的最优方案。二者取\(max\)即为根到父亲路径上点必选,\(dfn\)小于当前点的点任意,当前子树任意的最优方案。那么就求出了所定义的\(dp\)数组,并且这个数组正是进入其兄弟子树时需要的信息。
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=4010;
int n,m,ans;
int w[N],c[N],d[N],f[M];
int h[N],e[2*N],ne[2*N],idx;
bool st[N];
void add(int a,int b) {
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int get_size(int u,int fa) {
if(st[u]) return 0;
int sz=1;
for(int i=h[u]; i!=-1; i=ne[i])
if(e[i]!=fa) sz+=get_size(e[i],u);
return sz;
}
int get_center(int u,int fa,int tot,int ¢er) {
if(st[u]) return 0;
int sz=1,mx=0;
for(int i=h[u]; i!=-1; i=ne[i]) {
int j=e[i];
if(j==fa) continue;
int v=get_center(j,u,tot,center);
sz+=v,mx=max(mx,v);
}
mx=max(mx,tot-sz);
if(mx<=tot/2) center=u;
return sz;
}
void dp(int u,int fa) {
if(st[u]) return;
int g[M];
memcpy(g,f,sizeof f);
for(int i=m; i>=c[u]; i--) f[i]=f[i-c[u]]+w[u];
for(int i=c[u]-1; i>=0; i--) f[i]=-2e7;
int t=1,s=d[u]-1;
vector<int> q;
while(t<=s) {
q.push_back(t);
s-=t,t*=2;
}
if(s) q.push_back(s);
for(int i=0; i<q.size(); i++)
for(int j=m; j>=c[u]*q[i]; j--)
f[j]=max(f[j],f[j-c[u]*q[i]]+w[u]*q[i]);
for(int i=h[u]; i!=-1; i=ne[i]) if(e[i]!=fa) dp(e[i],u);
for(int i=0; i<=m; i++) f[i]=max(f[i],g[i]);
}
void solve(int u) {
if(st[u]) return;
get_center(u,-1,get_size(u,-1),u);
memset(f,0,sizeof f);
dp(u,-1);
ans=max(ans,f[m]);
st[u]=1;
for(int i=h[u]; i!=-1; i=ne[i]) solve(e[i]);
}
int main() {
int T;
cin >> T;
while(T--) {
idx=0;
memset(h,-1,sizeof h);
memset(st,0,sizeof st);
cin >> n >> m;
for(int i=1; i<=n; i++) cin >> w[i];
for(int i=1; i<=n; i++) cin >> c[i];
for(int i=1; i<=n; i++) cin >> d[i];
for(int i=1; i<=n-1; i++) {
int a,b;
cin >> a >> b;
add(a,b),add(b,a);
}
ans=0;
solve(1);
cout << ans << endl;
}
return 0;
}
点分治还经常用于寻找关键点
\(P4886\)
https://www.luogu.com.cn/problem/P4886
题意:给定\(n\)给点的树,有\(m\)条路径,点到路径的距离为点到路径两端点的距离和,试求出一点,使得这点到\(m\)条路径距离的最大值最小。
题解:先考虑任意选取一点作为关键点,求出这点到\(m\)条路径的距离,设最大值为\(mx\),现考虑所有距离为\(mx\)的路径。若存在一组距离为\(mx\)的路径,且当前关键点在这条路径上,则答案不会变小。如果存在两组距离为\(mx\)的路径,他们的\(lca\)不位于关键点相同的子树,则答案不会变小。所以答案可能变小,仅当所有路径位于关键点的一个子树中,选取这棵子树的重心当成关键点即可。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,lg,mx,ans=1e9;
int x[N],y[N],q[N];
int d[N],dep[N],f[N][22];
int h[N],e[2*N],w[2*N],ne[2*N],idx;
bool st[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int get_size(int u,int fa){
if(st[u]) return 0;
int sz=1;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
sz+=get_size(j,u);
}
return sz;
}
int get_center(int u,int fa,int tot,int ¢er){
if(st[u]) return 0;
int sz=1,mx=0;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
int v=get_center(j,u,tot,center);
sz+=v,mx=max(mx,v);
}
mx=max(mx,tot-sz);
if(mx<=tot/2) center=u;
return sz;
}
void dfs(int u,int fa,int dist,int depth){
d[u]=dist,dep[u]=depth;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
f[j][0]=u;
for(int k=1; k<=lg; k++)
f[j][k]=f[f[j][k-1]][k-1];
dfs(j,u,dist+w[i],depth+1);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=lg; i>=0; i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=lg; i>=0; i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int dist(int x,int y){
return d[x]+d[y]-2*d[lca(x,y)];
}
int solve(int u){
if(st[u]) return ans;
get_center(u,-1,get_size(u,-1),u);
st[u]=1;
for(int i=0; i<=lg; i++) f[u][i]=0;
dfs(u,-1,0,1);
int mx=0;
for(int i=1; i<=m; i++)
mx=max(mx,d[x[i]]+d[y[i]]);
ans=min(ans,mx);
int cnt=0;
for(int i=1; i<=m; i++)
if(d[x[i]]+d[y[i]]==mx) q[++cnt]=i;
for(int i=1; i<=cnt; i++)
if(lca(x[q[i]],y[q[i]])==u) return mx;
for(int i=2; i<=cnt; i++)
if(lca(lca(x[q[i-1]],y[q[i-1]]),lca(x[q[i]],y[q[i]]))==u) return mx;
for(int i=h[u]; i!=-1; i=ne[i])
if(lca(e[i],lca(x[q[1]],y[q[1]]))==e[i]) return solve(e[i]);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
memset(h,-1,sizeof h);
cin >> n >> m;
lg=(int)(log(n)/log(2))+1;
for(int i=1; i<=n-1; i++){
int a,b,c;
cin >> a >> b >> c;
add(a,b,c),add(b,a,c);
}
for(int i=1; i<=m; i++) cin >> x[i] >> y[i];
cout << solve(1) << endl;
return 0;
}
\(CF566C\)
https://www.luogu.com.cn/problem/CF566C
题意:\(n\)个点的树,有点权和边权,两点间距离定义为两点间边权的\(\frac{2}{3}\)次方,求带权重心。
题解:令\(f(u)=\sum_{i=1}^{n} dist(i,u)^\frac{2}{3}*a_i\),所求即为最小的\(f(u)\)。
设当前选取的重心为\(u\),考虑向子节点\(v\)移动是否会变优秀,设新重心在\((u,v)\)边上,离\(u\)的距离为\(x\),则权值为\(\sum_{i\notin tree_v}(dist(i,u)+x)^\frac{3}{2}*a_i+\sum_{i\in tree_v}(dist(i,u)-x)^\frac{3}{2}*a_i\),求导得\(\sum_{i\notin tree_v}\frac{3}{2}(dist(i,u)+x)^\frac{1}{2}*a_i-\sum_{i\in tree_v}\frac{3}{2}(dist(i,u)-x)^\frac{1}{2}*a_i\),往导数小于\(0\)的\(v\)走即可,在计算中由于\(x\)取极小值,可忽略。
这样做的复杂度为\(O(n^2)\),可考虑每次移动至\(v\)子树的重心,复杂度变为\(O(nlogn)\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int n,ans;
int a[N],d[N];
int h[N],e[2*N],w[2*N],ne[2*N],idx;
bool st[N];
double f[N],g[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int get_size(int u,int fa){
if(st[u]) return 0;
int sz=1;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
sz+=get_size(j,u);
}
return sz;
}
int get_center(int u,int fa,int tot,int ¢er){
if(st[u]) return 0;
int sz=1,mx=0;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
int v=get_center(j,u,tot,center);
sz+=v,mx=max(mx,v);
}
mx=max(mx,tot-sz);
if(mx<=tot/2) center=u;
return sz;
}
void dfs(int u,int fa,int id,int dist,double &res){
f[id]+=3.0/2*a[u]*sqrt(dist);
res+=a[u]*dist*sqrt(dist);
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u,id,dist+w[i],res);
}
}
int solve(int u){
if(st[u]) return ans;
get_center(u,-1,get_size(u,-1),u);
st[u]=1;
double sum=0;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
f[j]=0;
dfs(j,u,j,w[i],g[u]);
sum+=f[j];
}
if(g[u]<g[ans]) ans=u;
for(int i=h[u]; i!=-1; i=ne[i])
if(2*f[e[i]]>sum) return solve(e[i]);
return ans;
}
signed main(){
memset(h,-1,sizeof h);
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i];
for(int i=1; i<=n-1; i++){
int a,b,c;
cin >> a >> b >> c;
add(a,b,c),add(b,a,c);
}
g[0]=1e20,solve(1);
printf("%d %.10lf\n",ans,g[ans]);
return 0;
}
浙公网安备 33010602011771号