• 9.26


    以后可能会在每次考试后(当然极大概率咕咕咕)找一道题讲讲其实是三道题太多了

    那么既然量减了,质量就会提上去了也不一定,一次可能会根据某一道题说些思路

    反正也没人看,随便写一些留着自己用好了

      于是本期节目为您带来:换根dp

      这东西好像考了两三次了,每次都稍恶心,关键是转移难写

      判断它是个换根dp:

        1.时间复杂度$Theta(n)$的(或许$Theta(nlogn)$也可以?),暴力一般是$Theta(n^2)$的

        2.考虑dp状态与子树有关系的,一般来说是求$ans[i]$需要在以$i$为根的树上统计答案

        3.带补充...

      我知道这是个换根dp了,然后呢?:

        通常来说换根dp是需要$2+$次dfs的,并且这几次dfs求的都不一样,最后一次dfs通过前一次或几次的dfs求出的各种变量,让答案从父节点向儿子节点转移


    上例题:

      randomwalking form 模拟测试52(b)

      首先从数据范围推测复杂度,应该是个$Theta(n)$,最多加个$log$的对吧?

      我们可以认为一个点的答案应该是(所有它能到的点的贡献和+它的点权)/它的度

      即为(它父亲的贡献+所有它儿子的贡献和+它的点权)/它的度

      于是我们会发现这很换根dp

      那么考虑我们应该怎么切掉这道题

      我们先定义$son[i]$为一个点的子树以及它本身对$ans[i]$的贡献

      这显然是可以通过一次dfs求出来的

      同时我们也就有了$ans[1]$

      考虑如何由父节点将答案转移至儿子节点

      对于一个父亲节点,它的答案组成是:

      $它父亲的贡献+它所有儿子的贡献/它的度+它的点权$

      对于一个儿子节点,它的答案组成是:

      $它父亲的贡献+它所有儿子的贡献/它的度+它的点权$

      那么我们把它的父亲的贡献减去父亲的点权*父亲的度即为:

        $父亲的父亲的贡献+它所有兄弟的贡献+它的贡献$

      将这个值减去它的子树,即为:

        在以它为根的树中,它以前的父亲的子树对它以前的父亲的贡献

      将这个值除它以前的父亲现在的儿子数,即为:

        它以前的父亲现在的子树的贡献

      设这个值为res,我们再考虑其他贡献

      于是之前的$son[i]$可以被理解为:

        除去它以前的父亲的子树的贡献后,其他儿子对它的贡献+它的点权

      将son[i]*(度数-1)再减去它的点权

      加上res即为它所有能到的所有点的贡献

      再除以它的度,加上它的点权即为它的$ans$

      于是就有了转移,上代码:

      

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define int long long
    #define cin(k)	scanf("%lld",&k)
    const int maxn=1e6+5;
    double ans[maxn],son[maxn];
    struct road{
    	int e,nt;
    }r[maxn<<1];
    int nt[maxn],tot;
    int a[maxn],n;
    double minn;int bj;
    int v[maxn],du[maxn];
    int sondu[maxn];
    void dfs(int k)
    {
    	double tmp=0;
    	v[k]=1;
    	for(int q=nt[k];q;q=r[q].nt)
    		if(!v[r[q].e])
    		{sondu[k]++;dfs(r[q].e);tmp+=son[r[q].e];}
    	if(sondu[k])tmp/=sondu[k];
    	tmp+=a[k]*1.0;
    	son[k]=tmp;
    	v[k]=0;
    }
    void getans(int k)
    {
    	v[k]=1;
    	for(int q=nt[k];q;q=r[q].nt)
    		if(!v[r[q].e])
    		{
    			double res=ans[k];
    			res-=a[k];res*=du[k];res-=son[r[q].e];
    			if(du[k]!=1)	res/=(du[k]-1);
    			res+=a[k];
    			ans[r[q].e]=res;
    			ans[r[q].e]+=(son[r[q].e]-a[r[q].e])*sondu[r[q].e];
    			ans[r[q].e]/=du[r[q].e];
    			ans[r[q].e]+=a[r[q].e];
    			getans(r[q].e);
    		}
    	v[k]=0;
    }
    void add(int s,int e)
    {
    	r[++tot].e=e;
    	r[tot].nt=nt[s];
    	nt[s]=tot;
    }
    signed main()
    {
    	cin(n);
    	for(int q=1;q<=n;q++)	cin(a[q]),minn+=1.0*a[q];
    	for(int q=1,x,y;q<n;q++)
    	{
    		cin(x),cin(y);
    		add(x,y),add(y,x);
    		du[x]++,du[y]++;
    	}
    	dfs(1);
    	ans[1]=son[1];
    	getans(1);
    	for(int q=1;q<=n;q++)
    		if(ans[q]<minn)
    			minn=ans[q],bj=q;
    	cout<<bj<<endl;
    }
    

      大喘几口气,我们切掉了这道题

      回顾一下过程,我们先求出了$ans[1]$和子树贡献

      然后通过父亲节点与儿子节点的关系

      将父亲的ans转移到了儿子身上

      这也就是换根dp的思路


     于是,全文完

    下次不咕的时候再见

  • 相关阅读:
    golng切片实现分页
    go mgo包 简单封装 mongodb 数据库驱动
    docker 制作自己的镜像
    mongodb 数据库操作——备份 还原 导出 导入
    Override
    Parallel.For循环与普通的for循环的比较
    C#死锁案例代码
    C#的构造函数在基类和父类中执行顺序
    C#构造函数
    C# 多线程的死锁
  • 原文地址:https://www.cnblogs.com/ooovooo/p/11589034.html
Copyright © 2020-2023  润新知