HDU6035 Colorful Tree

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=6035


【题意】给定一棵有n个节点的树,ci表示节点i的颜色。任意两个节点间存在一条路径,该路径的权值为路径上不同颜色节点的数量,求解所有路径权值的和。


【分析】比赛期间想要通过树分治对答案进行求解,无奈发现始终无法完成对路径具体数值的快速统计和避免同色记重。赛后看了题解才发现自己还是太弱了。分析可以发现每个颜色对答案的贡献是经过该颜色的路径数。求解经过一个颜色的路径数还是不好求解,所以再转个方向去求解不经过该颜色的路径数。求解不经过对应颜色的路径数等于删除所有该颜色的节点后求解剩下的所有的联通块路径数的和。简单的删点重构树显然会T,所以采用虚树的概念进行求解,即不对树进行重构。此时需要利用dfs序,从而快速获取子树对应的区间,而区间大小就是节点数,可以快速求解路径数。具体方法看代码。代码中通过In保存对应节点dfs先序遍历值,Out保存对应先序遍历值的dfs序后续遍历值,合起来对应了以该节点为根的子树区间。

【代码】

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<fstream>
using namespace std;
#define Uint unsigned int
#define LL long long
vector<int> color[200200];
int In[200200];
int Out[200200];
int c[200200];
vector<int> edge[200200];
int cnt;
void dfs(int u,int f){
    In[u]=++cnt;
    for(int i=0;i<edge[u].size();++i){
        int v=edge[u][i];
        if(v==f)
            continue;
        dfs(v,u);
    }
    Out[In[u]]=cnt;
}
bool cmp(int x,int y){
    return In[x]<In[y];
}
LL Count(int idx,int s,int t,int l,int r){
    LL tot=r-l+1;
    LL ans=0;
    while(s<=t){
        int head=In[color[idx][s]]+1,tail=Out[In[color[idx][s]]];
        tot-=tail-head+2;
        s++;
        while(head<=tail){
            int he=head;
            int ta=Out[he];
            int i=s;
            while(i<=t && ta>=In[color[idx][i]])
                i++;
            ans+=Count(idx,s,i-1,he,ta);
            s=i;
            head=ta+1;
        }
    }
    ans+=tot*(tot-1)/2;
    return ans;
}

int main(){
    int n,u,v,cas=1;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;++i){
            In[i]=Out[i]=0;
            color[i].clear();
            edge[i].clear();
        }
        for(int i=1;i<=n;++i){
            scanf("%d",&c[i]);
            color[c[i]].push_back(i);
        }
        for(int i=1;i<n;++i){
            scanf("%d %d",&u,&v);
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        cnt=0;
        dfs(1,0);
        LL ans=0;
        for(int i=1;i<=n;++i){
            sort(color[i].begin(),color[i].end(),cmp);
            ans+=n*1ll*(n-1ll)/2-Count(i,0,color[i].size()-1,1,n);
        }
        printf("Case #%d: %I64d\n",cas++,ans);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值