ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

5.1 下饭考试

2021-05-05 12:35:51  阅读:145  来源: 互联网

标签:size 5.1 ll 下饭 儿子 son 节点 考试 mod


5.1考试下饭合集

T2 熟练剖分

题目描述
为什么你们noip的时候shulian(树链)剖分敲得那么shulian(熟练)啊?
你们到底,敲了多少遍树链剖分了啊?
为了考察noip选手们对树链剖分的掌握是否足够熟练,liu_runda决定出个树链剖分题.
可是,SD_le并不认为所有准备联赛的选手都学过树链剖分,因为他自己就不会,liu_runda便决定简述一下这个算法:
本题中使用的树链剖分算法在一棵有根树上进行.根节点编号为1.每个节点有若干个儿子.
每个非叶节点有且仅有一个重儿子,其他儿子称作轻儿子. 重儿子和父亲之间的边称作重边,轻儿子和父亲之间的边称作轻边.
对一个节点进行一次”精彩操作”付出的时间代价是这个节点到根节点路径上的轻边数量.根节点自身进行精彩操作的代价一定是0.我们只关注最坏情况下的时间复杂度,求出每个节点进行一次精彩操作的代价,然后在这些代价中取最大值,就得到了这棵树此时的最坏时间复杂度.
在本题中,liu_runda决定考察选手对树链剖分的灵活应用,因此每一个非叶节点将在它的所有儿子中等概率随机选择一个作为重儿子.
给出一棵树,按上面的方式随机选择重儿子,求最坏时间复杂度的期望.期望一定是一个有理数,可以表示成a/b的形式(a,b均为整数).只需要输出a乘上b的逆元后对10^9+7取模的结果.

------------------------分割线------------------------------

诶妈,期望是甚么东西?亿脸懵13
乍一看这必是树形dp
绝对是!
(是 又 怎 么 样)
(微笑)(裂开)
dp了半天不知道dp了个甚么牛马
现在再来看自己改的代码也半天没看懂

直接上正解吧:
1.设f[i][j],其中i为所有节点,j为最长轻链的长度,f存的是在i点长度为j的轻链的概率。(为方便后面状态转移以及节省时间,f在dp过程中存的是前缀和,求答案的时候再将前缀和解开)答案即为$\sum_{i=1}^n\f[root][i]*i$;
2.状态转移:
(1)遍历i点的所有儿子,递归先搞出儿子们以及自己的大小(size);
(2)再次遍历所有儿子作为重儿子v;
(3)遍历所有儿子son[u][j];
(4)从小到大枚举链长(对于每棵子树,当前节点和该子树构成的最长轻链长度也只有size[son[u][j]]+1,所以遍历到这个数即可)
【1】设两个辅助数组:g,此前遍历过的儿子对当前节点造成贡献后每个链长的概率(也是前缀和);h,当前son[u][j]对f[i][j]所造成的贡献(可以理解为就是f数组)
【2】设gs为g[k]-gk-1;设fs为f[son[u][j]][k]-f[son[u][j]][k-1]解开前缀和,即这个儿子对当前节点链长为k情况下做出的贡献)
【3】 如果该儿子是当前重儿子,重(zhong)边不会对该儿子已 经形成的链产生贡献。转移方程即为:h[k] = gs * f[son[u][j]][k] + fs * g[k] - gs * fs (因为只有最长的链长才会对当前节点造成贡献,所以用f[son[u][j]][k]去乘gs,代表当前儿子贡献出k长的链,此前所有小于等于k的情况全部k的情况造成贡献;用g[k]去乘fs,代表此前贡献出k长的链,当前儿子所有小于等于k的情况全部对k长的情况造成贡献。然而,两次乘法都考虑的是“小于等于”的情况,多算了一遍此前和当前儿子都贡献出k长链的情况,所以为了去重还需减去一个fs * gs);
【4】如果该儿子不是当前重儿子,轻边会使轻链链长+1,所以fs应改为f[son[u][j]][k-1] - f[son[u][j]][k-2],以及后面应该用f[son[u][j]][k-1]乘gs,其余同上;
【5】搞完这个儿子的所有情况后,再把链长循环一遍用h数组更新g数组,顺带的就把h清零了
(5)搞完以v为重儿子的情况后,将g数组解开(解开前缀和需要从后往前遍历,老常识了_)再用解开后的g数组更新f[u][0~size[u]]。这里更新的时候不用搞分数什么的,因为题中说随机选取儿子作为重儿子,所以每个儿子作为重儿子的概率相同,所以可以提前求出当前节点儿子数的逆元,将更新出来的f值乘上这个逆元就可以了;
(6)将所有儿子分别作为重儿子的情况都搞完之后,记得把f数组重新弄成前缀和,因为当前节点回溯到它的老爹的时候,更新这个老爹要用当前节点的f值。至于在遍历重儿子的过程中为什么不用把f更新成前缀和,因为很显然,更新每个节点用的f数组都是它儿子的。况且需要不停地对f数组进行加法以及乘逆元操作,你给它整成前缀和了它拿头去操作去?(滑稽)
3.边界:当找到叶子节点的时候,叶子结点肯定无法形成什么链,所以f[u][0] = 1即可;

废话不多说(好像已经很多废话了)......算了上代码吧:

#define inf 0x3f3f3f3f
#define ll long long
#define ri register long long
#define ld long double
using namespace std;

const int Maxn = 5e3 + 5;
const int mod = 1e9 + 7;

ll n;
ll root;
vector<int> son[Maxn];
bool b[Maxn], bb[Maxn]; 
ll ans;

ll f[Maxn][Maxn];
ll g[Maxn], h[Maxn];
ll size[Maxn];

ll qp(ll x, ll idx){
   ll res = 1;
   while(idx){
   	if(idx & 1) res = (res * x) % mod;
   	x = (x * x) % mod;
   	idx >>= 1;
   }
   return res;
}

void dfs(int u){
   ll jud = son[u].size();
   ll q = qp(jud, mod - 2);
   size[u] = 1;
   for(ri i = 0; i < son[u].size(); i++){
   	ll v = son[u][i];
   	dfs(v);
   	size[u] += size[v];
   }
   for(ri i = 0; i < son[u].size(); i++){
   	ll v = son[u][i];
   	for(ri j = 0; j <= n; j++) g[j] = 1;
   	for(ri j = 0; j < son[u].size(); j++){
   		for(ri k = 0; k <= size[son[u][j]] + 1; k++){
   			ll gs = g[k]; if(k) gs -= g[k-1];
   			ll fs = f[son[u][j]][k]; if(k) fs -= f[son[u][j]][k-1];
   			if(v == son[u][j]){
   				h[k] = ((gs * f[son[u][j]][k] % mod + fs * g[k] % mod - gs * fs % mod) % mod + mod) % mod;
   			}
   			else if(k){
   				fs = f[son[u][j]][k-1]; if(k > 1) fs -= f[son[u][j]][k-2];
   				h[k] = ((fs * g[k] % mod + gs * f[son[u][j]][k-1] % mod - gs * fs % mod) % mod + mod) % mod;
   			}
   		}
   		g[0] = h[0], h[0] = 0;
   		for(ri k = 1; k <= size[son[u][j]] + 1; k++){
   			g[k] = (g[k-1] + h[k]) % mod;
   			h[k] = 0;
   		}
   	}
   	for(ri j = size[u]; j >= 1; --j){
   		g[j] = (g[j] - g[j-1] + mod) % mod;
   	}
   	for(ri j = 0; j <= size[u]; j++){
   		f[u][j] = (f[u][j] + g[j] * q % mod) % mod;
   	}
   }
   if(!son[u].size()){
   	f[u][0] = 1;
   }
   for(ri i = 1; i <= n; i++){
   	f[u][i] = (f[u][i-1] + f[u][i]) % mod;
   }
}

int main(){
   scanf("%lld", &n);
   for(ri i = 1, k; i <= n; i++){
   	scanf("%lld", &k);
   	for(ri j = 1, v; j <= k; j++){
   		scanf("%lld", &v);
   		son[i].push_back(v);
   		b[v] = true;
   		bb[i] = true;
   	}
   }
   int cnt = 0;
   for(ri i = 1; i <= n; i++){
   	if(!b[i]) root = i;
   	if(bb[i]) cnt++;
   }
   if(cnt == 1){
   	printf("1");
   	return 0;
   }
   dfs(root);
   for(ll i = 1; i <= n; i++){
   	ans = ((ans + (f[root][i] - f[root][i-1] + mod) * i % mod) % mod) % mod;
   }
   printf("%lld", ans);
   return 0;
} 

标签:size,5.1,ll,下饭,儿子,son,节点,考试,mod
来源: https://www.cnblogs.com/mtr20050125/p/14731410.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有