zoukankan      html  css  js  c++  java
  • HZOI2019熟练剖分(tree)

    题目大意:https://www.cnblogs.com/Juve/articles/11186805.html

    题解:

    先给出官方题解:

    其实这题跟期望没什么关系,因为E=$sum_limits{x=0}^{+infty}$p(x)*x,所以我们只要求出轻链最多为 i 的概率就行了。
    以下把题面所求的精彩操作称为最长轻链。而这个东西显然是可以由子节点转移到父亲节点的。
    F[i][j]表示在点 i 为根的子树中,向下最长轻链长度为 j 的概率。
    对于一个点,先枚举它选择的重儿子是谁,然后扫一遍它的所有儿子,让 G[i][j]=$sum_limits{k}^{k<=j}$F[i][k],假如当前扫的儿子是 x(x 是重儿子)。
    F[i][j]=F[x][j]*G[i][j]+G[x][j]*F[i][j]-F[x][j]*F[i][j]-----(1)
    不是重儿子的需要相应的改一下,还有要注意 F 数组更新的顺序,标程是先把 F 暂存到了一个别的数组里。
    转移的时候如果(1)式子的 j 循环到了 size[i],那么复杂度可以被卡到 N3,我们发现当 j>size[x]+1 的时候 F[x][j]=0,G[x][j]=1,F[i]相当于没有变,所以只要 j 循环到 size[x]+1 就行了。
    每个节点只有在 dp 它父亲时会被枚举成为重儿子,然后最多把整棵树的大小扫一遍,所以复杂度为N2.

    这看起来极其难以理解,为了便于理解,我粘了Al_Ca大佬的题解,楷体字是我的一些理解

    f[i][j]表示在点 i 为根的子树中,向下最长轻链长度小于等于 j 的概率。g[x][k]表示x节点之前的儿子中最长轻链长度(包括x)小于等于k的概率,但当前节点的g与其他节点没有关系,第一维可以清空,所以只有g[k]

    首先递归下去并求出子树大小,然后枚举重儿子,枚举该点最长轻链长度,再次枚举儿子节点并逐个考虑,

    假设当前枚举的重儿子是v(i),枚举到儿子节点v(j),x最长轻链长度为k,设gs为v(j)之前考虑的儿子中最长轻链长度为k的概率(因为是前缀和,所以代码中有减这个操作,f同理),如果v(j)=v(i)即v(j)为重儿子,则设fs为以v(j)为根的子树最长轻链长度为k的概率,f[x][k]=gs*f[v(j)][k](v(j)之前考虑的儿子为长度k*以v(j)为根字数长度<=k(此条边为重链所以可以等于))+fs*g[k]-gs*fs(去重),

    stop,我们看一下这个方程,当v(j)为重儿子时,有:

    f[x][k]=gs*f[v(j)][k]+fs*g[k]-gs*fs,解释一下

    在点 x 为根的子树中,向下最长轻链长度小于等于 k 的概率可以有这样几种转移:

    1:在v(j)之前考虑的儿子中最长轻链长度为k 且同时 以v(j)为根的子树向下最长轻链长度小于等于k。

      若v(j)为x的重儿子,则v(j)到x的路径没有贡献,所以是小于等于k。

    2:以v(j)为根的子树最长轻链长度为k 且同时 v(j)父节点之前的儿子中最长轻链长度(包括父节点)小于等于k

    3:我们发现有情况算重了,那就是同时满足 在v(j)之前考虑的儿子中最长轻链长度为k 和 以v(j)为根的子树最长轻链长度为k,所以应减去

    如果v(j)是轻儿子,则设fs为以v(j)为根的子树最长轻链长度为k-1的概率,f[x][k]=gs*f[v(j)][k-1]+fs*g[k]-gs*fs,大致同上,

    stop,再解释一下gs*f[v(j)][k-1]含义

    和上一个转移方程一样,只不过这时的v(j)不是重儿子,所以v(j)和它的父节点之间的边会有贡献,所以这时转移的应是以v(j)为根的子树向下最长轻链长度小于等于k-1。

    剩下的就都一样了

    只是x与v(j)相连的这条边为轻链所以有减1,值得提醒的一点是这里的f[x][k]并不是最终的f[x][k],只是考虑到当前几个儿子时的值,一个儿子一个儿子地向里加。考虑到f数组直接改的话会错,所以用h数组保存,最后加到g数组中清空h,当v(i)为重儿子这个情况考虑玩后将g数组加到f中去,清空g。当前节点x求完后,此时的f数组并不是前缀和,所以需要再次转化。

    最后求答案时再次将前缀和转化为单个的值。

     代码实现还是有一定难度的,具体细节看代码吧:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define MAXN 3005
    #define ll long long
    using namespace std;
    const int mod=1e9+7;
    ll n,in_deg[MAXN],root,son_num[MAXN],ans=0;
    ll to[MAXN],nxt[MAXN],pre[MAXN],cnt;
    void add(ll u,ll v){
    	cnt++,to[cnt]=v,nxt[cnt]=pre[u],pre[u]=cnt;
    }
    ll q_pow(ll a,ll b,ll p){
    	ll ans=1;
    	for(;b;b>>=1){
    		if(b&1) ans=ans*a%p;
    		a=a*a%p;
    	}
    	return ans;
    }
    ll size[MAXN],g[MAXN],h[MAXN],f[MAXN][MAXN];//f[i][j]表示在点 i 为根的子树中,向下最长轻链长度小于等于 j 的概率,h表示临时的f数组,g[x][i]表示x节点之前的儿子中最长轻链长度(包括x)小于等于k的概率,但第一维可以随时清空,所以只有g[i]
    void dfs(ll x){
    	size[x]=1;
    	for(ll i=pre[x];i;i=nxt[i]){
    		ll y=to[i];
    		dfs(y),size[x]+=size[y];
    	}
    	ll q=q_pow(son_num[x],mod-2,mod);
    	for(ll i=pre[x];i;i=nxt[i]){//枚举重儿子
    		for(ll j=0;j<=n;j++) g[j]=1;
    		ll h_son=to[i];//heavy son
    		for(ll j=pre[x];j;j=nxt[j]){//枚举其他儿子
    			ll a_son=to[j];//another son
    			for(ll k=0;k<=size[a_son]+1;k++){
    				ll gs=g[k],fs=f[a_son][k];//gs为a_son之前考虑的儿子中最长轻链长度为k的概率
    				if(k) gs-=g[k-1],fs-=f[a_son][k-1];
    				if(a_son==h_son) h[k]=(gs*f[a_son][k]%mod+fs*g[k]%mod-fs*gs%mod+mod)%mod;//若a_son为重儿子,fs为以a_son为根的子树最长轻链长度为k的概率
    				else{
    					fs=f[a_son][k-1];if(k>1) fs-=f[a_son][k-2];//若a_son为轻儿子,则设fs为以a_son为根的子树最长轻链长度为k-1的概率
    					h[k]=(gs*f[a_son][k-1]%mod+fs*g[k]%mod-gs*fs%mod+mod)%mod;
    				}
    			}
    			g[0]=h[0],h[0]=0;
    			for(ll k=1;k<=size[a_son]+1;k++) g[k]=(g[k-1]+h[k])%mod,h[k]=0;
    		}
    		for(ll j=size[x];j>=1;j--) g[j]=(g[j]-g[j-1]+mod)%mod;
    		for(ll j=0;j<=size[x];j++) f[x][j]=(f[x][j]+g[j]*q%mod)%mod;
    	}
    	if(!pre[x]) f[x][0]=1;
    	for(ll i=1;i<=size[x]+1;i++)
    		f[x][i]=(f[x][i]+f[x][i-1])%mod;
    	return ;
    }
    int main(){
    	scanf("%lld",&n);
    	for(ll i=1;i<=n;i++){
    		scanf("%lld",&son_num[i]);
    		for(ll j=1,son;j<=son_num[i];j++){
    			scanf("%lld",&son);
    			add(i,son);
    			in_deg[son]++;
    		}
    	}
    	for(ll i=1;i<=n;i++){
    		if(!in_deg[i]){
    			root=i;
    			break;
    		}
    	}
    	dfs(root);
    	for(ll i=1;i<=n;i++)
    		ans=(ans+i*(f[root][i]-f[root][i-1]+mod)%mod)%mod;
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    g4e基础篇#1 为什么要使用版本控制系统
    软件开发的自然属性
    定时器实现延时处理
    二分查找法
    php实现循环链表
    redis实现分布式锁
    RabbitMq初探——用队列实现RPC
    RabbitMq初探——发布与订阅
    RabbitMq初探——消息均发
    RabbitMq初探——消息持久化
  • 原文地址:https://www.cnblogs.com/Juve/p/11190340.html
Copyright © 2011-2022 走看看