zoukankan      html  css  js  c++  java
  • BZOJ5212: [Zjoi2018]历史

    BZOJ5212: [Zjoi2018]历史

    Description

    九条可怜是一个热爱阅读的女孩子。
     
    这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣。
     
    这个世界有n个城市,这n个城市被恰好n?1条双向道路联通,即任意两个城市都可以互相到达。同时城市1坐落在世
    界的中心,占领了这个城市就称霸了这个世界。
     
    在最开始,这n个城市都不在任何国家的控制之下,但是随着社会的发展,一些城市会崛起形成国家并夺取世界的
    霸权。为了方便,我们标记第i个城市崛起产生的国家为第i个国家。在第i个城市崛起的过程中,第i个国家会取得
    城市i到城市1路径上所有城市的控制权。
     
    新的城市的崛起往往意味着战争与死亡,若第i个国家在崛起中,需要取得一个原本被国家j(j!=i)控制的城市的控
    制权,那么国家i就必须向国家j宣战并进行战争。
     
    现在,可怜知道了,在历史上,第i个城市一共崛起了ai次。但是这些事件发生的相对顺序已经无从考究了,唯一
    的信息是,在一个城市崛起称霸世界之前,新的城市是不会崛起的。战争对人民来说是灾难性的。可怜定义一次崛
    起的灾难度为崛起的过程中会和多少不同的国家进行战争(和同一个国家进行多次战争只会被计入一次)。可怜想
    要知道,在所有可能的崛起顺序中,灾难度之和最大是多少。
     
    同时,在考古学家的努力下,越来越多的历史资料被发掘了出来,根据这些新的资料,可怜会对ai进行一些修正。
    具体来说,可怜会对ai进行一些操作,每次会将ax加上w。她希望在每次修改之后,都能计算得到最大的灾难度。
     
    然而可怜对复杂的计算并不感兴趣,因此她想让你来帮她计算一下这些数值。
    对题面的一些补充:
    1:同一个城市多次崛起形成的国家是同一个国家,这意味着同一个城市连续崛起两次是不会和任何国家开战的:因
    为这些城市原来就在它的控制之下。
    2:在历史的演变过程中,第i个国家可能会有一段时间没有任何城市的控制权。但是这并不意味着第i个国家灭亡了
    ,在城市i崛起的时候,第i个国家仍然会取得1到i路径上的城市的控制权

    Input

    第一行输入两个整数n,m表示城市个数和操作个数。
    第二行输入n个整数表示ai的初始值。
    接下来n-1行,每行输入两个整数ui,vi(1<=ui,vi<=n)描述了一条道路。
    接下来m行每行输入两个整数xi,wi表示将axi加上wi
    N,M<=4*10^5
    1 <= ai, wi <= 10^7, 1 <= xi <= n

    Output

    输出共m+1行,第一行表示初始的ai的答案,接下来m行每行表示这次修正后的答案

    Sample Input

    5 3
    1 1 1 1 1
    1 2
    1 3
    2 4
    2 5
    2 1
    3 1
    4 1

    Sample Output

    6
    7
    9
    10
    在修正开始之前,如果按照所在城市4,1,5,3,2的顺序崛起,那么依次会和0,1,2,1,2个
    国家进行战争。这时一共会产生6对敌对关系。可以证明这是所有崛起顺序中的最大值。

    题解Here!
    暑假的最后一道题。。。
    也是暑假最后一道黑题。。。
    看着题目名称莫名地感觉有点心酸,也许,这就是历史吧。。。

    正题:

    首先化简题意
    给出一棵树,给定每一个点的$access$次数,计算轻重链切换次数的最大值,带修改。

    先看没有修改怎么做。
    我们发现点$i$会开战当且仅当$i$的子树里有城市崛起,并且和上次崛起的城市不同。
    设点$u$有$m$个儿子,$u$自己$access$次数为$A_0$,第$i$个儿子的子树的$access$次数为$A_i$。
    问题转化为有$m+1$个不同颜色的小球,每种有$A_i$个,求一种排列使得相邻颜色不同的个数最多。
    设$$sum=sumlimits_{i=0}^{m}A_i,mx=maxlimits_{i=0}^{m}A_i$$
    则点$u$最大切换次数就是$min(sum-1,2 imes(sum-mx))$。
    可以看出每一个点都只和自己的子树有关,也就是说两点之间的贡献都是相互独立的。
    那么我们就可以用树形$DP$求一次静态的答案了。
    这样就有$30$分。
    然后考虑带上修改
    我们只需要动态地维护$DP$值即可对吧。
    很自然的想到了树形数据结构:树链剖分/$LCT$。
    这里我们用$LCT$来维护。
    设$sum_u$表示$u$子树里的$A_i$之和。
    如果$exists v in son[u],sum_u+1 leq 2sum_v$,则$u,v$之间连实边,否则为虚边。
    显然这样的$v$只有一个。
    修改时在$LCT$上把虚边全部修改一次即可。
    可以证明虚边的数量是$log_2sum A_i$的,和树链剖分的证明方法类似。
    所以复杂度是$O(nlog_2n)$的。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #define MAXN 500010
    using namespace std;
    int n,m,c=1;
    long long ans=0;
    int head[MAXN];
    struct Edge{
        int next,to;
    }edge[MAXN<<1];
    inline int read(){
        int date=0,w=1;char c=0;
        while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
        while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
        return date*w;
    }
    inline long long min(const long long x,const long long y){return x<y?x:y;}
    namespace LCT{
        struct Link_Cut_Tree{
            int f,son[2];
            long long v,w,sum;
        }a[MAXN];
        inline bool isroot(int rt){
            return a[a[rt].f].son[0]!=rt&&a[a[rt].f].son[1]!=rt;
        }
        inline void pushup(int rt){
            if(!rt)return;
            a[rt].sum=a[a[rt].son[0]].sum+a[a[rt].son[1]].sum+a[rt].v+a[rt].w;
        }
        inline void turn(int rt){
            int x=a[rt].f,y=a[x].f,k=a[x].son[0]==rt?1:0;
            if(!isroot(x)){
                if(a[y].son[0]==x)a[y].son[0]=rt;
                else a[y].son[1]=rt;
            }
            a[rt].f=y;a[x].f=rt;a[a[rt].son[k]].f=x;
            a[x].son[k^1]=a[rt].son[k];a[rt].son[k]=x;
            pushup(x);pushup(rt);
        }
        void splay(int rt){
            while(!isroot(rt)){
                int x=a[rt].f,y=a[x].f;
                if(!isroot(x)){
                    if((a[y].son[0]==x)^(a[x].son[0]==rt))turn(rt);
                    else turn(x);
                }
                turn(rt);
            }
        }
        void access(int rt,int k,int after){
            for(;rt;after=rt,rt=a[rt].f){
                splay(rt);
                long long sum=a[a[rt].son[1]].sum+a[rt].v+a[rt].w;
                 
                if(a[rt].son[1])ans-=2LL*(sum-a[a[rt].son[1]].sum);
                else if(sum+1<=2LL*a[rt].v)ans-=2LL*(sum-a[rt].v);
                else ans-=sum-1;
                 
                a[rt].sum+=k;a[rt].w+=k;sum+=k;
                if(sum+1>2LL*a[a[rt].son[1]].sum){
                    a[rt].w+=a[a[rt].son[1]].sum;
                    a[rt].son[1]=0;
                }
                if(sum+1<=2LL*a[after].sum){
                    a[rt].son[1]=after;
                    a[rt].w-=a[a[rt].son[1]].sum;
                }
                 
                if(a[rt].son[1])ans+=2LL*(sum-a[a[rt].son[1]].sum);
                else if(sum+1<=2LL*a[rt].v)ans+=2LL*(sum-a[rt].v);
                else ans+=sum-1;
            }
        }
        void update(int rt,long long k){
            splay(rt);
            long long sum=a[a[rt].son[1]].sum+a[rt].v+a[rt].w;
             
            if(a[rt].son[1])ans-=2LL*(sum-a[a[rt].son[1]].sum);
            else if(sum+1<=2LL*a[rt].v)ans-=2LL*(sum-a[rt].v);
            else ans-=sum-1;
             
            a[rt].v+=k;a[rt].sum+=k;sum+=k;
            if(sum+1>2LL*a[a[rt].son[1]].sum){
                a[rt].w+=a[a[rt].son[1]].sum;
                a[rt].son[1]=0;
            }
             
            if(a[rt].son[1])ans+=2LL*(sum-a[a[rt].son[1]].sum);
            else if(sum+1<=2LL*a[rt].v)ans+=2LL*(sum-a[rt].v);
            else ans+=sum-1;
             
            access(a[rt].f,k,rt);
        }
        void dfs(int rt,int fa){
            int maxx=rt;
            long long maxn=a[rt].v;
            a[rt].f=fa;
            a[rt].sum=a[rt].v;
            for(int i=head[rt],will;i;i=edge[i].next){
                will=edge[i].to;
                if(will!=fa){
                    dfs(will,rt);
                    a[rt].sum+=a[will].sum;
                    if(a[will].sum>maxn){
                        maxn=a[will].sum;
                        maxx=will;
                    }
                }
            }
            ans+=min(a[rt].sum-1,2LL*(a[rt].sum-maxn));
            if(maxx!=rt&&a[rt].sum+1<=2LL*maxn)a[rt].son[1]=maxx;
            a[rt].w=a[rt].sum-a[rt].v-a[a[rt].son[1]].sum;
        }
    }
    inline void add(int x,int y){
        edge[c].to=y;edge[c].next=head[x];head[x]=c++;
        edge[c].to=x;edge[c].next=head[y];head[y]=c++;
    }
    void work(){
        int x,y;
        while(m--){
            x=read();y=read();
            LCT::update(x,y);
            printf("%lld
    ",ans);
        }
    }
    void init(){
        int x,y;
        n=read();m=read();
        for(int i=1;i<=n;i++)LCT::a[i].v=read();
        for(int i=1;i<n;i++){
            x=read();y=read();
            add(x,y);
        }
        LCT::dfs(1,0);
        printf("%lld
    ",ans);
    }
    int main(){
        init();
        work();
        return 0;
    }

    $UPDATE$:这个程序在各大$OJ$上均能通过,只有洛谷可能不行。
    洛谷的评测姬要求好严啊。。。
    我们发现,我们在$dfs$递归的时候,爆栈了。
    并且任何手动开栈操作均会被洛谷的第四代评测姬忽略!
    这就是为什么洛谷至今很少被卡。。。
    于是,我们需要把自动递归,改为手动递归。。。
    他长这个样子:
    stack<int> one,two;
    void dfs(){
    	int rt=1;
    	a[rt].f=0;
    	one.push(rt);two.push(rt);
    	while(!one.empty()){
    		rt=one.top();
    		one.pop();
    		a[rt].sum=a[rt].v;
    		for(int i=head[rt],will;i;i=edge[i].next){
    			will=edge[i].to;
    			if(will==a[rt].f)continue;
    			a[will].f=rt;
    			one.push(will);two.push(will);
    		}
    	}
    	while(!two.empty()){
    		rt=two.top();
    		two.pop();
    		long long maxn=a[rt].v,maxx=rt;
    		for(int i=head[rt],will;i;i=edge[i].next){
    			will=edge[i].to;
    			if(will==a[rt].f)continue;
    			a[rt].sum+=a[will].sum;
    			if(a[will].sum>maxn){
    				maxn=a[will].sum;
    				maxx=will;
    			}
    		}
    		ans+=min(a[rt].sum-1,2LL*(a[rt].sum-maxn));
    		if(maxx!=rt&&a[rt].sum+1<=2LL*maxn)a[rt].son[1]=maxx;
    		a[rt].w=a[rt].sum-a[rt].v-a[a[rt].son[1]].sum;
    	}
    }
    

    用这个把上面那个旧的$dfs$替换掉就好辣!

  • 相关阅读:
    网站设计中常见的几个错误
    C#的一个小函数来计算一个运算使用的时间和内存
    移动位置社交服务全球热潮日益高涨
    Opera CEO 专访:开源真的那么重要吗?
    N73和蓝牙GPS接收器LD3W----Route66 Mobile7中国版
    激励员工的首席执行官以及他们的秘诀
    2008年智能手机五大发展趋势
    J2EE初学者从这里入门
    Windows Mobile 5.0 开发学习
    手机开发平台指南、教程和资料介绍
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9564578.html
Copyright © 2011-2022 走看看