Great Cow Gathering G|P2986
https://www.luogu.com.cn/problem/P2986
题目描述
Bessie 正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。
每个奶牛居住在 N 个农场中的一个,这些农场由 N-1 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 i连接农场 Ai 和 Bi,长度为 Li。集会可以在 N 个农场中的任意一个举行。另外,每个牛棚中居住着 Ci 只奶牛。
在选择集会的地点的时候,Bessie 希望最大化方便的程度(也就是最小化不方便程度)。比如选择第 X 个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和(比如,农场 i 到达农场 X 的距离是 20,那么总路程就是 Ci×20)。帮助 Bessie 找出最方便的地点来举行大集会。
输入格式
第一行一个整数 N 。
第二到 N+1 行:第 i+1 行有一个整数 Ci。
第 N+2 行到 2N 行:第 i+N+1 行为 3 个整数:Ai,Bi 和 Li。
输出格式
一行一个整数,表示最小的不方便值。
输入输出样例
输入 #1
5
1
1
0
0
2
1 3 1
2 3 2
3 4 3
4 5 3
输出 #1
15
说明/提示
1≤N≤100000,1≤Ai≤Bi≤N,0≤Ci,Li≤1000。
板子题,但我不会
确信
题意很简单,在有边权有点权的一棵无根树上选一点为根,使得所有节点到根的路径长乘点权之和最小。
首先,既然是无根树,这个树就不能太规整,要长得洒脱一些,便于我们观测:
首先,最朴素的方法,每个点都作为根算一遍
O(n^2)=O(TLE)
所以
不可以哦~
那咋做呢
我们看看哪里有严重浪费。
比如,我们在算相邻的节点时,会发现,除了这两点的子树和sz有变化,其他的sz都没有变化,而我们做那么多遍dp显然浪费了。
嗯——
那我们就可以先随便找一个点当作根,先算出一种情况,O(n)很快的
然后从这个基根向四周dfs,由于dfs回溯的特性,每次只用改一小部分,也就是两个sz
新的dp也能很简单的算出来:假设从u换根到v,u到v的这条边权为w,那v的子树的贡献就减小了sz[v]*w,而换到v后,u作为v的子树,对答案的贡献增加了sz[u]*w, dp就转移成功了。
但显然发现,第二个sz不能直接用,因为这时的sz[u]还是u为根时的子树和.
那——怎么办呢?
稍加计算,我们发现,根处的sz永远是整棵树的和,不变,那把sz[u]赋给sz[v]即可;
嗯?
u作为v的子树,它的大小应当是整棵树的大小减去u还是根时v子树的大小,剩下的便是u作为新子树时的sz
记得回溯回来就好啦
显然,换完所有根都试一遍复杂度也只有O(n),过了。
#include<iostream>
#include<cstring>
#define int long long
using namespace std;
int n;
const int MAXN=1e5+10;
//快读读,读快快~
inline int read(){
int s=0,f=1;
char c=getchar();
while(c<48 || c>57){if(c=='-')f=-f;c=getchar();}
while(c>=48 && c<=57)s=s*10+c-48,c=getchar();
return s*f;
}
int w[MAXN];//点权
struct tree{
int to;
int w;
int next;
}edge[MAXN*2];
int head[MAXN];
int _cnt;
void add(int u,int v,int w){
edge[++_cnt].to=v;
edge[_cnt].w=w;
edge[_cnt].next=head[u];
head[u]=_cnt;
}
//把1dfs掉!
int sz[MAXN];
int dp[MAXN];
bool vis[MAXN];
int dfs(int x,int dep){
vis[x]=1;
sz[x]=w[x];
int ret=dep*w[x];
//生娃娃
for(int i=head[x];i!=0;i=edge[i].next){
int nxt=edge[i].to;
if(vis[nxt])continue;
ret+=dfs(nxt,dep+edge[i].w);
sz[x]+=sz[nxt];
}
return ret;
}
//换根咯~
int minn=0x7fffffffffffffff;
void chg_root(int r){
vis[r]=1;
minn=min(minn,dp[r]);
//sons
for(int i=head[r];i!=0;i=edge[i].next){
int nxt=edge[i].to;
if(vis[nxt])continue;
dp[nxt]=dp[r];
dp[nxt]-=edge[i].w*sz[nxt];
int diff=sz[r]-sz[nxt];
sz[nxt]=sz[r];
sz[r]=diff;
dp[nxt]+=edge[i].w*sz[r];
chg_root(nxt);
sz[r]=sz[nxt];
sz[nxt]-=diff;
}
return;
}
signed main(){
n=read();
int u,v,k;
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1;i<n;i++){
u=read(),v=read(),k=read();
add(u,v,k);
add(v,u,k);
}
//先是1
int pre=dfs(1,0);
memset(vis,0,sizeof(vis));
dp[1]=pre;
chg_root(1);
cout<<minn;
//要相信光!!!!!
//相信个鬼哟。。
//相信汝社
return 0;
}