2020牛客暑期多校训练营(第一场) B Infinite Tree
思路:
1、首先假设这棵树的结果已经确定,我们统计每颗子树的 w[i] 的和 val,显然 val[1] = \(\sum_{i=1}^n w[i]\) , 假设我们当前选定点 u ,处于点 u 时的结果为 f[u], 点 u 的深度为 depeth[u],
接下来遍历 u 的子节点 v,由于已知 f[u], 所以 f[v] = f[u] + (val[1] - 2 * val[v]) * (depeth[v] - depeth[u])。若 f[v] < f[u], 则向 v 转移, 直到不能再转移就是正确结果了。
2、但我们不能直接这么做,因为 m = 1e5, 我们存不下来这么节点, 但是可以发现,有用的点只有 1e5 个给定点和他们的公共祖先 LCA, 所以用虚树来减少节点的数量。
要建立虚树我们需要知道每个关键点的 dfs 序 dfn[i],depeth[i], 和 dfs序相邻两点的lca, 但在这题中,点 dfn[i * i] 一定小于 dfn[(i + 1)* (i + 1)]
原因:假设一个数唯一分解后的结果为 \(x_1^{p_1}x_2^{p_2}x_3^{p_3}x_n^{p_n}...\),那么该节点的深度为 \((\sum_{i = 1}^n p_i) + 1\), 根节点 1 的深度为 1。
接着看 x! 和 (x + 1)! LCA 的深度, 若 (x + 1) 的最大素因子大于 x 的最大素因子,则 x! 和 (x + 1)! 的 LCA 为 1, 否则,
假设 x! 唯一分解后的素因子集合为 {5, 5, 5, 3, 3, 2}, (x + 1)! 唯一分解后的素因子集合为{5, 5, 5, 3, 3, 2} + {3, 3, 2},
根据这颗树的性质,我们很容易得出 x! 和 (x + 1)! LCA 的深度为 6 (3个5, 两个3, 再加上一个根节点), 可以手画一颗树来,很容易发现这个性质。
3、然后建立虚树再dp就好了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 1e9 + 7;
typedef long long LL;
int mindiv[maxn], vis[maxn];
int n;
LL w[maxn];
void init(){ // 求mindiv[i]
for(int i = 2; i < maxn; i++){
mindiv[i] = INF;
}
for(int i = 2; i < maxn; i++){
if(vis[i]) continue;
for(int j = 1; 1LL * j * i < maxn; j++){
mindiv[1LL * i * j] = min(mindiv[i * j], i);
vis[1LL * i * j] = 1;
}
}
}
int tree[maxn];
int lowbit(int x){ // 通过树状数组来对因子的个数计数
return x & (-x);
}
void Update(int i, int k){
while(i <= n){
tree[i] += k;
i += lowbit(i);
}
}
int getSum(int i){
int res = 0;
while(i > 0){
res += tree[i];
i -= lowbit(i);
}
return res;
}
struct Edge
{
int to, next;
} edge[maxn * 4];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int top, stk[maxn], depeth[maxn], lcadepth[maxn], tot;
void BuildTree(){ //建立虚树
top = 1;
tot = n;
stk[1] = depeth[1] = 1;
for(int i = 2; i <= n; i++){
int cnt = 0;
int j;
for(j = i; j != mindiv[j]; j /= mindiv[j]) cnt++; // 计算点 i 深度
depeth[i] = depeth[i - 1] + cnt + 1;
lcadepth[i] = getSum(n) - getSum(j - 1) + 1;
for(j = i; j != 1; j /= mindiv[j]) Update(mindiv[j], 1);
}
for(int i = 2; i <= n; i++){
if(top == 1 || lcadepth[i] == depeth[stk[top]]){
stk[++top] = i;
continue;
}
while(top > 1 && lcadepth[i] <= depeth[stk[top - 1]]){
add(stk[top - 1], stk[top]);
top--;
}
if(lcadepth[i] != stk[top]){
depeth[++tot] = lcadepth[i]; // 要给定非 1 - n 的lca的编号
head[tot] = -1;
w[tot] = 0;
add(tot, stk[top]);
stk[top] = tot;
}
stk[++top] = i;
}
while(top > 1){
add(stk[top - 1], stk[top]);
top--;
}
}
LL val[maxn];
void dfs(int u){ // 计算所有节点的val[u]
val[u] = 0;
val[u] = w[u];
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
dfs(to);
val[u] += val[to];
}
}
LL ans = 0;
void dp(int u, LL res){
ans = min(ans, res);
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
LL res2 = res + 1LL * (val[1] - 1LL * 2 * val[to]) * (depeth[to] - depeth[u]);
if(res2 < res){
dp(to, res2);
}
}
}
int main(int argc, char const *argv[])
{
init();
while(~scanf("%d", &n)){
for(int i = 0; i <= n; i++){
head[i] = -1;
}
ans = 0;
k = 0;
for(int i = 1; i <= tot * 2; i++) {
tree[i] = depeth[i] = lcadepth[i] = 0;
}
BuildTree();
for(int i = 1; i <= n; i++){
scanf("%lld", &w[i]);
}
ans = 0;
dfs(1);
for(int i = 1; i <= n; i++) {
ans += 1LL * (depeth[i] - 1) * w[i];
}
dp(1, ans);
printf("%lld\n", ans);
}
return 0;
}