CF960 E. Alternating Tree(点分治)

Posted on 2022-03-11 17:47  Capterlliar  阅读(38)  评论(0编辑  收藏  举报

题意:

给定一棵树,按一正一负一正这样求所有奇数长度链(链有方向)的权值和,(偶数显然会抵消)。

解:

去学了一波点分治,专治求树上各种链的问题,简单口胡一下(

学习博客

学习视频

经典引入:求树上所有长度为k的链的权值和。

从根看起,链有两种,跨过根的和不跨过根的,除了叶子节点,不跨过根的它肯定也要跨过些什么,因此所有链都可以劈成两半来加,然后递归处理子树内的情况,此为分治。

要想得到长为k的链,可以把它分成两段,一段长为n,考虑到还有本结点,另一段长k-n-1。于是递归处理子树内长为k的链有多少,权值和是多少,然后排列组合一下加起来。

考虑这种情况:选取的两条链有共同结点时,显然这条链不能选,要把多算的次数减去。于是遍历每一条出边时,计算当前节点会被多算几次,也就是子树内符合要求的数量,减掉它。

再考虑树退化成一条链的情况:从头开始依次遍历,复杂度还是n2,不妙。所以还要每次找子树的重心,然后以子树重心为根,处理剩余的情况。

找子树重心的方法:该结点最大子树的结点数量最少即为重心。

再看这道题,统计奇偶就可以了。也就是说,点分治每次重写cal函数就行,很快乐。

如果权值在点上,根节点单独计算;权值在边上直接算就行。

几个WA的点:

1.找到重心还从1开始遍历;

2.找重心的时候由于剩下部分也是一棵子树,所以应该和 *当前树大小减去包括当前点在内的子树大小* 比较;

3.经典问题  一种基于错误的寻找重心方法的点分治的复杂度分析(问题详见该页评论区

实测错误方法和正确的时间差得真不多(

几乎一模一样

4.由经典问题引发的惨案:在分治程序里要记录当前子树大小,但是如果用正确的方法(totsize=son[to]>son[now]?tot-son[now]:son[to];)算totsize,这个值是要应用到子树里的,因此还要开一个变量保存当前的tot。(其实怪弱智的

5.如果TLE了,多半是重心求错了,(摆烂就好

代码完全参考此博客,但在求重心处改了改:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 200005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 1000000007
#define base 2333
//#define int long long
int n;
ll a[maxx];
struct edge{
    int u,v,next;
}v[maxx*2];int head[maxx]={0},cnt=0;
void add(int x,int y){
    v[++cnt].u=x,v[cnt].v=y,v[cnt].next=head[x],head[x]=cnt;
}
ll ans=0;
int rt=0;
int vis[maxx]={0},son[maxx]={0},mx[maxx]={0};
void dfs_rt(int now,int fa,int tot){
    mx[now]=0,son[now]=1;
    for(int i=head[now];i;i=v[i].next){
        int to=v[i].v;
        if(vis[to]||to==fa)
            continue;
        dfs_rt(to,now,tot);
        son[now]+=son[to];
        mx[now]=max(mx[now],son[to]);
    }
    mx[now]=max(mx[now],tot-son[now]);
    if(!rt||mx[rt]>mx[now]) rt=now;
}
ll d[2]={0},num[2]={0};
void dfs_dis(int now,int fa,int n,ll sum){
    d[n]=(d[n]+sum)%mod;
    num[n]++;
    for(int i=head[now];i;i=v[i].next){
        int to=v[i].v;
        if(vis[to]||to==fa)
            continue;
        dfs_dis(to,now,!n,sum+a[to]*(n?1:-1));
    }
}
ll cal1(int now){
    d[0]=d[1]=0;
    num[0]=num[1]=0;
    dfs_dis(now,now,0,0);
    d[1]=-d[1];
    return (d[0] * num[0] * 2 + d[1] * num[1] * 2 +
            a[now] * (( num[0] * num[0] - num[1] * num[1])%mod)%mod)%mod;
}
ll cal2(int now,ll w){
    d[0]=d[1]=0;
    num[0]=num[1]=0;
    dfs_dis(now,now,1,-a[now]);
    d[1]=-d[1];
    return (d[0] * num[0] * 2 + d[1] * num[1] * 2 +
            w * (( num[0] * num[0] - num[1] * num[1])%mod)%mod)%mod;
}
void dfs_work(int now,int tot){
    vis[now]=1;
    ans=(ans+cal1(now))%mod;
    for(int i=head[now];i;i=v[i].next){
        int to=v[i].v;
        if(vis[to])
            continue;
        ans=(ans-cal2(to,a[now]))%mod;
        int totsize=son[to]>son[now]?tot-son[now]:son[to];
        rt=0;dfs_rt(to,now,totsize);
        dfs_work(rt,totsize);
    }
}
signed main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=1;i<=n-1;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    rt=0;dfs_rt(1,0,n);
    dfs_work(rt,n);
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}
View Code