• 【BZOJ3727】Zadanie(PA2014 Final)-思维


    测试地址:Zadanie
    题目大意: 一棵树,第ii个点有aia_i个人,现在求出了bib_i,为所有人走到点ii的总路程,要求还原aia_i
    做法: 本题需要用到思维。
    在我们求bib_i的时候,我们可以使用换根法,那么我们能不能用换根法,找到a,ba,b之间的关系呢?
    在换根法中,先随便选一个点作为根(这个根不是指换根法中的根,而是为了算法方便而求出的根),把根从点ii换到父亲点jjbb会增加sumi(totalsumi)=2sumitotalsum_i-(total-sum_i)=2sum_i-total,其中sumisum_i为以ii为根的子树内所有点的aia_i之和,totaltotal为所有点的aia_i之和。因此得到等式bjbi=2sumitotalb_j-b_i=2sum_i-total
    这样类推下去,我们可以得到n1n-1个等式。但之中有nn个未知数,所以我们还需要找到一个条件。我们注意到上面的等式中bb都是差的形式,如果我们再多用一个点的bb来连接和所有未知数之间的关系,应该就可以解了,这个点就是算法的根rootroot
    我们发现,brootb_{root}就等于irootsumisum_{i e root}sum_i。这可以通过一个简单的贡献变换得出。这样一来我们就有了nn个等式,意味着这个方程组可以解了。
    怎么解呢?首先当然是算出totaltotal。一开始的n1n-1个等式都可以化成下面的形式:sumi=total+bf(i)bi2sum_i=frac{total+b_{f(i)}-b_i}{2}(其中f(i)f(i)表示ii的父亲),于是有:
    2irootsumi=(n1)total+iroot(bf(i)bi)2sum_{i e root}sum_i=(n-1)total+sum_{i e root}(b_{f(i)}-b_i)
    又因为irootsumi=brootsum_{i e root}sum_i=b_{root},所以totaltotal为:
    total=2broot+iroot(bibf(i))n1total=frac{2b_{root}+sum_{i e root}(b_i-b_{f(i)})}{n-1}
    这就可以很轻松地算出来了。进一步地,有了totaltotal后就可以通过前n1n-1个等式逐一算出每个点的sumsum了,实际上totaltotal就是根的sumsum。而从子树和sumsum恢复aa也非常容易了,对于每个点ii,对sumisum_i减去它所有儿子的sumsum即可得到aia_i。这样我们就解决了这一题,时间复杂度为O(n)O(n)
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,first[300010]={0},tot=0,fa[300010]={0};
    ll a[300010],b[300010],total;
    struct edge
    {
    	int v,next;
    }e[600010];
    
    void insert(int a,int b)
    {
    	e[++tot].v=b;
    	e[tot].next=first[a];
    	first[a]=tot;
    }
    
    void solve(int v)
    {
    	total+=b[v]-b[fa[v]];
    	for(int i=first[v];i;i=e[i].next)
    		if (e[i].v!=fa[v])
    		{
    			fa[e[i].v]=v;
    			solve(e[i].v);
    		}
    }
    
    void finalsolve(int v)
    {
    	for(int i=first[v];i;i=e[i].next)
    		if (e[i].v!=fa[v])
    		{
    			a[v]-=a[e[i].v];
    			finalsolve(e[i].v);
    		}
    }
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<n;i++)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		insert(u,v),insert(v,u);
    	}
    	for(int i=1;i<=n;i++)
    		scanf("%lld",&b[i]);
    	
    	total=b[1];
    	solve(1);
    	total/=(ll)(n-1);
    	a[1]=total;
    	for(int i=2;i<=n;i++)
    		a[i]=(total-b[i]+b[fa[i]])>>1;
    	finalsolve(1);
    	for(int i=1;i<=n;i++)
    		printf("%lld ",a[i]);
    	
    	return 0;
    }
    
  • 相关阅读:
    Vue插件之导出EXCEl
    vue.js--加载JSON文件的两种方式
    vue项目中axios的封装
    雪碧图布局
    开始学习算法
    Java中有关Null的9件事
    一个抓取知乎页面图片的简单爬虫
    浅析Java中的final关键字
    Java中String、StringBuilder以及StringBuffer
    把一个数组向右循环移动k位要求时间复杂度为O(n)
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793253.html
Copyright © 2020-2023  润新知