hdu 6133 Army Formations

题意很迷,给一棵n个节点的树,每个节点有一个权值是这个节点完成其任务的时间。

比如第一个任务的权值是1,完成它将在第一秒,第二个任务的权值是2,完成前两个任务的时间是3秒。

现在问你完成任务的时间再加上罚时是多少,罚时是完成任务的时间再加上它开始做任务的时间。比如一个任务权值是3,从第四秒开始做,那么完成它的时间加罚时是7秒。

现在问你以每个节点为根的子树完成所有节点的任务的最小时间(加罚时)是多少。

 

可以想最后的时间必定是所有任务的时间加上罚时,那么罚时越少最后的总时间就越小。

所有在一棵子树里,我们把所有节点的任务排序,按从小到大执行任务这样的罚时越少。

 

因为一个根节点下每颗子树间的任务没有交集,所以只能进行启发式合并。

每颗子树的任务不用排序,可以用树状数组维护。

考虑贡献,从小到大开始放,那么小的任务值必定会给比他任务值大的节点都带来他的时间作为罚时的贡献。

那么每加入新的一个节点,我们询问比它大的节点有多少个都乘上它的权值,再询问比它小的节点的总权值是多少。那么总的贡献就算完了。

 

 

 

代码:

 

 

#include <bits/stdc++.h>
using namespace std;
const int M = 1e5+7;
typedef long long ll;
int head[M],cnt,n,_,tot;
int sz[M],son[M],pos[M];
ll csum[M],ans[M],val[M],cnum[M],a[M],tmp;
struct edge{
    int v,next;
}e[M<<1];
void init(){
    cnt=0;memset(head,-1,sizeof(head));
    tmp=0;memset(ans,0,sizeof(ans));
}
void add(int u,int v){
    e[++cnt].v=v;e[cnt].next=head[u];
    head[u]=cnt;
}
void update(int x,ll y,ll *c){
    for(;x<=tot;x+=x&(-x)){
        c[x]+=y;
    }
}
ll query(int x,ll *c){
    ll k=0;
    for(;x;x-=x&(-x)){
        k+=c[x];
    }
    return k;
}
void dfs(int u,int fa){
    csum[u]=cnum[u]=0;
    sz[u]=1;
    son[u]=-1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
    }
    return ;
}
void add_node(int u){
    tmp+=(query(tot,cnum)-query(pos[u],cnum))*val[u]+val[u];//加上把这个点放入的时间再加上给比他大的点带来的罚时
    tmp+=query(pos[u],csum);//加上比这个点小的点给这个点带来的罚时
    update(pos[u],val[u],csum);//这个点带来的贡献加入罚时数组
    update(pos[u],1,cnum);//这个能接受比它小的后面的点带来的罚时
}
void del_node(int u){//删除节点
    update(pos[u],-val[u],csum);//先更新树状数组再更新答案因为多加的答案是还未加入此节点时多算的贡献
    update(pos[u],-1,cnum);
    tmp-=((query(tot,cnum)-query(pos[u],cnum))*val[u]+val[u]);
    tmp-=query(pos[u],csum);
}
void del_tree(int u,int fa){//删除子树
    del_node(u);
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa) continue;
        del_tree(v,u);
    }
}
void add_tree(int u,int fa){//加入子树
    add_node(u);
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa) continue;
        add_tree(v,u);
    }
}
void dfs1(int u,int fa){
    if(sz[u]==1){//如果是根节点
        add_node(u);
        ans[u]=val[u];
        return ;
    }
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa||v==son[u]) continue;
        dfs1(v,u);//寻找子树的答案
        del_tree(v,u);//将子树删除
    }
    dfs1(son[u],u);//找重儿子的答案
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa||v==son[u]) continue;
        add_tree(v,u);//将子树的答案加到根节点中
    }
    add_node(u);//将根节点加入
    ans[u]=tmp;
}
int main(){
    scanf("%d",&_);
    while(_--){
        init();//初始化
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);val[i]=a[i];
        }
        for(int i=1;i<n;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v);add(v,u);
        }
        dfs(1,-1);//找重儿子
        sort(a+1,a+n+1);//排序
        tot=unique(a+1,a+n+1)-a-1;//去重
        for(int i=1;i<=n;i++){
            pos[i]=lower_bound(a+1,a+tot+1,val[i])-a;//找到每个元素在去重数组中的位置
        }
        dfs1(1,-1);
        for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
        puts("");
    }
    return 0;
} 
View Code

 

posted @ 2018-08-26 15:15  LMissher  阅读(171)  评论(0编辑  收藏  举报