[P2664] 树上游戏

摘要:
=anc)cal_ res;ifnum--,sum+=col[dat[x]];cnt[dat[x]]--;}Void exclude//撤消/还原当前子树结果{sum+=sz[x]*f;col[dat[anc]]+=sz[x]*f;cnt[dat[anc]]++;modify;cnt[dat[anc]-;}Void cal{top=0;cal_sum(x,0);res[x]+=sum;for{ifcontinue;exclude;vcur=sz[x]-sz[e[i].to];cal_res;exclude;}/不能使用me mset,只能清除当前划分和征服树的颜色num=sum=0;强制[st[i]]=col[st[i]]=0;}voidsolve{cal;vis[x]=1;for{ifcontinue;vsum=mxsub[0]=sz[e[i].to];getrt;solve;}}intmain(){scanf;forscanf;forscanf,add_edge(x,y);vsum=mxsub[0]=n;getrt,solve;forprintf;return0;}

Link:

P2664 传送门

Solution:

一道非常不错的计算贡献的题目

此类计算树上所有点对间结果的题目首先考虑点分治,同时一般都是对每种颜色计算贡献

那么对于每层点分治,需要计算其它点对分治中心的贡献和经过分支中心的路径对其它每个点的贡献

考虑颜色$k$,如果一个颜色为$k$的节点$v$为其到根的路径中的第一个$k$,则其对其它子树贡献为$size[v]$

令$col[k]$表示$sum size[v]$,$sum$表示$sum col[k]$

那么分治中心增加的答案就是$sum$,下面考虑每一颗子树中的点

首先要将子树中的点对$col[k]$和$sum$的贡献还原,在计算完该棵子树的贡献后再撤销该操作

接下来考虑每个点$v$,对其产生贡献的颜色$k$分两类:

1、不属于$v$到分治中心路径上的颜色,那么贡献就是$col[k]$

2、属于$v$到分治中心路径上的颜色,贡献显然为除了该子树的点的个数

因此在再次遍历时记录第二类点的个数同时不断用$sum$减去第一次出现颜色的$col[k]$即可

注意,每次解决完一个连通块要将数组还原

此时一定不能用$memset$,要记录经过的颜色并仅将这些还原,否则单层复杂度无法保证$O(n)$

Code:

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+10;
struct edge{int nxt,to;}e[MAXN<<2];
ll cnt[MAXN],col[MAXN],res[MAXN],sum,num;
int n,x,y,vsum,vcur,rt,tot,top;
int head[MAXN],dat[MAXN],mxsub[MAXN],sz[MAXN],vis[MAXN],st[MAXN];

void add_edge(int from,int to)
{
    e[++tot].nxt=head[from];e[tot].to=to;head[from]=tot;
    e[++tot].nxt=head[to];e[tot].to=from;head[to]=tot;
}
void getrt(int x,int anc)
{
    sz[x]=1;mxsub[x]=0;
    for(int i=head[x];i;i=e[i].nxt)
        if(e[i].to!=anc&&!vis[e[i].to])
        {
            getrt(e[i].to,x);
            sz[x]+=sz[e[i].to];
            mxsub[x]=max(mxsub[x],sz[e[i].to]);
        }
    mxsub[x]=max(mxsub[x],vsum-sz[x]);
    if(mxsub[x]<mxsub[rt]) rt=x;
}
//计算总和 
void cal_sum(int x,int anc)
{
    sz[x]=1;cnt[dat[x]]++;
    for(int i=head[x];i;i=e[i].nxt)
        if(!vis[e[i].to]&&e[i].to!=anc)
            cal_sum(e[i].to,x),sz[x]+=sz[e[i].to];
    
    if(cnt[dat[x]]==1)
        col[dat[x]]+=sz[x],sum+=sz[x],st[++top]=dat[x];
    cnt[dat[x]]--;
}
void modify(int x,int anc,int f)
{
    cnt[dat[x]]++;
    for(int i=head[x];i;i=e[i].nxt)
        if(!vis[e[i].to]&&e[i].to!=anc) 
            modify(e[i].to,x,f);
    
    if(cnt[dat[x]]==1)
        sum+=sz[x]*f,col[dat[x]]+=sz[x]*f;
    cnt[dat[x]]--;
}
void cal_res(int x,int anc)//计算当前子树结果 
{
    cnt[dat[x]]++;
    if(cnt[dat[x]]==1)
        num++,sum-=col[dat[x]];
    res[x]+=sum+num*vcur;
    
    for(int i=head[x];i;i=e[i].nxt)
        if(!vis[e[i].to]&&e[i].to!=anc)
            cal_res(e[i].to,x);
    
    if(cnt[dat[x]]==1)
        num--,sum+=col[dat[x]];
    cnt[dat[x]]--;
}

void exclude(int x,int anc,int f)//将当前子树结果撤销/还原 
{
    sum+=sz[x]*f;col[dat[anc]]+=sz[x]*f;
    cnt[dat[anc]]++;modify(x,anc,f);cnt[dat[anc]]--;
}
void cal(int x)
{
    top=0;cal_sum(x,0);res[x]+=sum;
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(vis[e[i].to]) continue;
        
        exclude(e[i].to,x,-1);
        vcur=sz[x]-sz[e[i].to];
        cal_res(e[i].to,x);
        exclude(e[i].to,x,1);
    }
    //一定不能用memset,仅对当前分治树的颜色清零 
    num=sum=0;
    for(int i=1;i<=top;i++) 
        cnt[st[i]]=col[st[i]]=0;
}
void solve(int x)
{
    cal(x);vis[x]=1;
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(vis[e[i].to]) continue;
        vsum=mxsub[0]=sz[e[i].to];
        getrt(e[i].to,rt=0);solve(rt);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]);
    for(int i=1;i<n;i++)
        scanf("%d%d",&x,&y),add_edge(x,y);
    
    vsum=mxsub[0]=n;getrt(1,rt=0);
    solve(rt);
    for(int i=1;i<=n;i++)
        printf("%lld
",res[i]);
    return 0;
}

免责声明:文章转载自《[P2664] 树上游戏》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇[网络流24题] 最小路径覆盖问题[ZROI #316] ZYB玩字符串下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

牛客练习赛51 E-数列

E-数列 题目链接:https://ac.nowcoder.com/acm/contest/1083/E 题目描述:小乔有一个长度为n的整数数列,最开始里面所有的值都为0,小乔需要将在1…n的每一个位置填入一个大于0的正整数,得到一个新的数列,并且这个数列所有数的和不超过m,小乔对这个数列会有一个喜爱度,小乔对这个数列的喜爱度为满足2<=i<=n...

Java中Scanner的用法

转载自:http://blog.csdn.net/pkbilly/article/details/3068912 Scanner是SDK1.5新增的一个类,可是使用该类创建一个对象.Scanner reader=new Scanner(System.in);然后reader对象调用下列方法(函数),读取用户在命令行输入的各种数据类型:next.Byte(),...

循环结构

应用场景 如果在程序中我们需要重复的执行某条或某些指令,例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向奔跑的指令。当然你可能已经注意到了,刚才的描述中不仅仅有需要重复的动作,还需要用到上一章讲的分支结构。再举一个简单的例子,我们要实现一个每隔1秒中在屏幕上打印一次"hello, world"并持续打印...

退役前的做题记录1

学习一波鸽子的更博方式 Codeforces 623E:Transforming Sequence (f_{i,j}) 表示前 (i) 个,选了 (j) 个二进制位(f_{i,j}=sum f_{i-1,k}2^kinom{j}{k})倍增+(MTT) 即可 Codeforces 553E:Kyoya and Train (f_{i,j}) 表示 (i) 到...

BZOJ 1005: [HNOI2008]明明的烦恼

传送门 prufer序列 因为每个prufer序列唯一对应一颗树,所以如果能求出prufer序列的方案数就能求出树的方案数 一颗 n 个节点的树的prufer序列有 n-2 个数字,每个数字在 [1,n] 的范围内,表示节点编号 对于一个度数为 $d_i$ 的点,它会在prufer序列中出现 $d_i-1$ 次 设 m 为度数确定的节点数量,$d_i$ 为某...

【转】【最小树形图】有向图的最小生成树【朱刘算法】

这篇文章挺好的。每行还有注释QAQ,kuangbin的模板里并没有万能节点; 万能节点好像是在不定根的时候的拓展。 要点: 1.求所有边权和sum; 2.以0点为万能节点向所有点建一条权值为sum的边; 3.记得sum++;保证比所有边权值总和大一点; 4.判断条件为(ans==-1||ans-sum>=sum) //ans-sum是除去虚根的最小树形...