题意概要:给定 \(n\) 个点,\(w_i\) 分别有 \(p_{i,1},p_{i,2},p_{i,3}\) 的几率取 \(1,2,3\)。code
在肯定了全部的 \(w_i\) 后再开始游戏:不断抽点,点 \(i\) 被抽中的几率为 \(\frac {w_i}{\sum_{j=1}^nw_j}\),直到全部点都被抽中过。游戏
给定 \(n-1\) 个二元组 \((u,v)\) 表示第一次抽中 \(u\) 的时间须要比第一次抽中 \(v\) 的时间早,且若将这 \(n-1\) 个二元组中的两个元素连无向边,则这张图是一棵树。get
问知足全部二元组限制的几率。io
\(n\leq 1000\)class
(话说考场上看错了题,觉得是肯定好每一个点抽中的几率后再进行游戏,打了正解加仨暴力,跑出来都是同样的就是和大样例不同,致使心态崩了 并且题面里并无提到这个区别)方法
这题的解题突破口在于这个题的限制是一棵树,但边的方向不惟一(无法找到根使得这棵树变成内向树或外向树)nw
为了解题方便,不妨设这棵树是以 \(1\) 号节点为根的外向树(对于每条边而言,方向都是从靠近 \(1\) 的一端指向远离 \(1\) 的一端)di
那么能够发现,\(1\) 号节点必须是最后一个抽到的,几率为 \(\frac {w_1}{\sum_{i=1}^nw_i}\),并且只要知足这个条件后,这个点就没有用了,问题分解成 \(1\) 的每棵子树知足自身拓扑序的几率之积,这又是一个子问题时间
设 \(sz_i\) 表示 \(i\) 的子树中几率系数的和(\(\sum_{v\in subtree(x)}w_v\))
即设 \(f[x]\) 表示 \(x\) 的子树内知足拓扑序的几率,则 \(f[x]=\frac {w_x}{sz_x}\prod_{x\rightarrow v}f[v]\),不可贵到 \(Ans=f[1]=\prod_{x=1}^n\frac {w_x}{sz_x}\)
那么能够用一个Dp解决这个问题:设 \(f[x][i]\) 表示在 \(i\) 的子树中、几率系数和为 \(j\) 且知足拓扑序的几率,\(O(n^2)\)
毒瘤出题人竟然不给这档部分分?
考虑到这棵树不是外向树,存在若干条内向边。
直接处理内向边很差处理,如今存在一种方法,即用 “不考虑这条边”的方案数 减去 “考虑这条边为外向边”的方案数
(用图来表达即:\(o\rightarrow o\leftarrow o\) 等于 \(o\rightarrow o\quad o\) 减去 \(o\rightarrow o\rightarrow o\))
而后在树上Dp时,遇到此类内向边就将权值用上面这种方法计算一下便可。
据说这题还有另外一种作法,代码实现上是同样的,但思想不大同样:
考虑设 \(g[i]\) 表示这若干条内向边中,至少有 \(i\) 条内向边的条件不知足,则容斥可知答案为:\(\sum_i(-1)^ig[i]\)
而 “至少 \(i\) 条内向边不知足”,也即 “\(i\) 条内向边变为外向边,而其余内向边断开”
这样在Dp的时候加入一个容斥系数便可:一开始设全部内向边断开,而后给其加上一个负的 “内向边变外向边” 的权值
//loj-3124 #include <cstdio> typedef long long ll; const int N = 1013, p = 998244353; struct Edge {int v, w, nxt;} a[N<<1]; int head[N], sz[N]; int a1[N], a2[N], a3[N]; int F[N][N*3], arr[N*3]; int inv[N*3], n, _; inline int qpow(int A, int B) { int res = 1; while(B) { if(B&1) res = (ll)res * A%p; A = (ll)A * A%p, B >>= 1; } return res; } void dfs(int x, int las) { int*f = F[x]; f[0] = 1; for(int ii=head[x],v;ii;ii=a[ii].nxt) if((v=a[ii].v) != las) { dfs(v, x); int*g = F[v]; const int sx = sz[x] * 3, sv = sz[v] * 3; for(int i=0;i<=sx+sv;++i) arr[i] = 0; if(!a[ii].w) for(int i=0;i<=sx;++i) for(int j=0;j<=sv;++j) arr[i+j] = (arr[i+j] + (ll)f[i] * g[j])%p; else { ll sm = 0; for(int i=0;i<=sv;++i) sm += g[i]; sm %= p; for(int i=0;i<=sx;++i) arr[i] = sm * f[i]%p; for(int i=0;i<=sx;++i) for(int j=0;j<=sv;++j) arr[i+j] = (arr[i+j] - (ll)f[i] * g[j] + (ll)p*p)%p; } for(int i=0;i<=sx+sv;++i) f[i] = arr[i]; sz[x] += sz[v]; } const int sx = sz[x] * 3; for(int i=0;i<=sx+3;++i) arr[i] = 0; for(int i=0;i<=sx;++i) { arr[i+1] = (arr[i+1] + (ll)f[i] * a1[x]%p * inv[i+1])%p; arr[i+2] = (arr[i+2] + (ll)f[i] * a2[x]%p * inv[i+2] * 2)%p; arr[i+3] = (arr[i+3] + (ll)f[i] * a3[x]%p * inv[i+3] * 3)%p; } for(int i=0;i<=sx+3;++i) f[i] = arr[i]; ++sz[x]; } int main() { scanf("%d",&n); inv[0] = inv[1] = 1; for(int i=2;i<=n*3;++i) inv[i] = (ll)(p-p/i) * inv[p%i]%p; for(int i=1;i<=n;++i) { scanf("%d%d%d",a1+i,a2+i,a3+i); ll v = qpow(a1[i] + a2[i] + a3[i], p-2); a1[i] = v * a1[i]%p; a2[i] = v * a2[i]%p; a3[i] = v * a3[i]%p; } for(int i=1,x,y;i<n;++i) { scanf("%d%d",&x,&y); a[++_].v = y, a[_].w = 0, a[_].nxt = head[x], head[x] = _; a[++_].v = x, a[_].w = 1, a[_].nxt = head[y], head[y] = _; } dfs(1, 0); ll Ans = 0; for(int i=sz[1]*3;~i;--i) Ans += F[1][i]; printf("%lld\n", Ans%p); return 0; }