CF960E Alternating Tree 题解

本文详细解析了CF960E Alternating Tree问题的解决方案,通过树形DP计算每个点在树中不同位置的贡献。文章首先介绍了预处理步骤,然后分别讨论了树中链的三种情况,并提供了相应的计算公式。最后,给出了完整代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

分析:

单独计算每条链的贡献很耗时间,且不好计算,可以考虑分别计算每个点的贡献。

一些处理

因为一个点的贡献与这个点在链中的(奇偶)位置有关,考虑树形DP:
f u , 0 , f u , 1 f_{u,0},f_{u,1} fu,0,fu,1表示分别表示在 u u u的子树中(包括 u u u)与 u u u的距离为偶数,奇数的点的数量。记 u u u的儿子的集合为 s o n u son_u sonu,转移方程很明显为:
f u , 0 = ( ∑ v ∈ s o n u f v , 1 ) + 1 , f u , 1 = ∑ v ∈ s o n u f v , 0 f_{u,0} = (\sum_{v \in son_u}f_{v,1}) + 1,\qquad f_{u,1} = \sum_{v \in son_u}f_{v,0}\qquad\qquad fu,0=(vsonufv,1)+1,fu,1=vsonufv,0

开始计算

再考虑如何计算贡献,发现过任意一点 u u u的链在树中只有三种情况:

  1. u u u的子树 — 到 u u u — 到 u u u的子树外
  2. u u u的子树 — 到 u u u — 再回到 u u u的子树
  3. u u u的子树外 — 到 u u u — 到 u u u的子树

(要注意题目中的链显然是有向的,所以1、3两种情况能合并)

我们可以对三种情况分开讨论,其中要注意几种从自己开始或到自己结束的特殊情况。下面记最终答案为 a n s ans ans,点 u u u的子树大小为 s i z e u size_u sizeu,我们有:


对于情况1:

这里计算了三个特殊情况:自己到自己、子树到自己以及自己到子树外,有:
a n s = a n s + ( f u , 0 − f u , 1 ) × ( n − s i z e u + 1 ) × v u ans = ans + (f_{u,0} - f_{u,1})\times(n - size_u + 1) \times v_u\qquad\qquad ans=ans+(fu,0fu,1)×(nsizeu+1)×vu

对于情况2:

略微复杂一点,没有计算任何特殊情况,注意子树到自己这个特殊情况在情况1已经算过了,所以计算最后一个节点时应当减一,为:
a n s = a n s + ∑ v ∈ s o n u ( ( f v , 1 − f v , 0 ) × ( s i z e u − s i z e v − 1 ) × v u ) ans = ans + \sum_{v \in son_u} ((f_{v,1} - f_{v,0})\times(size_u - size_v - 1)\times v_u)\qquad ans=ans+vsonu((fv,1fv,0)×(sizeusizev1)×vu)

对于情况3:

当前维护的信息好像无法计算?
考虑再维护一个数组 g u , 0 , g u , 1 g_{u,0},g_{u,1} gu,0,gu,1分别表示在点 u u u的子树外(不包括 u u u)与点 u u u的距离为偶数,奇数的点的数量,那么这个数组显然要从父节点转移,可以考虑搜索时记录每个点的深度,搜索完后按深度排序进行转移,这样就可以保证每个点被更新前父一定是被更新过的。令 f a u fa_u fau表示点 u u u的父节点,有转移方程:
g u , 0 = g f a u , 1 + f f a u , 1 − f u , 0 , g u , 1 = g f a u , 0 + f f a u , 0 − f u , 1 g_{u,0} = g_{fa_u,1} + f_{fa_u,1} - f_{u,0},\qquad g_{u,1} = g_{fa_u,0} + f_{fa_u,0} - f_{u,1}\qquad gu,0=gfau,1+ffau,1fu,0,gu,1=gfau,0+ffau,0fu,1

有了 g g g数组之后贡献就很好统计了,这里计算了一种特殊情况:子树外到自己,为:
a n s = a n s + ( g u , 0 − g u , 1 ) × s i z e u × v u ans = ans + (g_{u,0} - g_{u,1}) \times size_u \times v_u ans=ans+(gu,0gu,1)×sizeu×vu

这样情况3就算出来了
 
 
但你会发现上面还少了一种特殊情况:自己到子树。这个需要单独统计:
a n s = a n s + ( s i z e u − 1 ) × v u ans = ans + (size_u - 1) \times v_u ans=ans+(sizeu1)×vu

到这里三种情况就都统计完了,计算的时候注意取模即可
 
 

Code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 50,mod = 1e9 + 7;
long long n,x,y,cnt,ans,a[maxn],v[maxn],fa[maxn],last[maxn],d[maxn],size[maxn],f[maxn][2],g[maxn][2];
//d为深度,a为按深度排序后的顺序,这样就不用写结构体
struct Edge{
    int v,nxt;
}e[2 * maxn];
int read(){
    int x = 0,f = 1;
    char c = getchar();
    while(c < '0' || c > '9'){
        if(c == '-') f *= -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + (c ^ 48),c = getchar();
    return x * f;
}
inline void insert(int x,int y){
    cnt ++,e[cnt].v = y,e[cnt].nxt = last[x],last[x] = cnt;
}
bool cmp(int x,int y){
    return d[x] < d[y];//按深度排序
}
void dfs(int u,int father){
    fa[u] = father,d[u] = d[fa[u]] + 1,size[u] = 1,f[u][0] = 1;
    for(int i = last[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if(v == father) continue;
        dfs(v,u);
        f[u][0] += f[v][1],f[u][1] += f[v][0],size[u] += size[v];
    }
    //把信息统计完后再计算贡献
    for(int i = last[u]; i; i = e[i].nxt){
        int t = e[i].v;
        if(t == father) continue;
        ans = (ans + (f[t][1] - f[t][0]) * (size[u] - size[t] - 1) % mod * v[u] % mod + mod) % mod;//情况2
    }
    ans = (ans + (size[u] - 1) * v[u] % mod + mod) % mod;//特殊情况,单独统计
    ans = (ans + (f[u][0] - f[u][1]) * (n - size[u] + 1) % mod * v[u] % mod + mod) % mod;//情况1
}
int main(){
    n = read();
    for(int i = 1; i <= n; i ++) v[i] = read(),a[i] = i;
    for(int i = 1; i < n; i ++){
        x = read(),y = read();
        insert(x,y),insert(y,x);
    }
    dfs(1,0);
    sort(a + 1,a + 1 + n,cmp);
    for(int i = 2; i <= n; i ++){
        int u = a[i];
        g[u][0] = g[fa[u]][1] + f[fa[u]][1] - f[u][0];
        g[u][1] = g[fa[u]][0] + f[fa[u]][0] - f[u][1];
        ans = (ans + (g[u][0] - g[u][1]) * size[u] % mod * v[u] % mod + mod) % mod;//情况3
    }
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值