zoukankan      html  css  js  c++  java
  • 题解-CTS2019氪金手游

    Problem

    (mathtt {loj-3124})

    题意概要:给定 (n) 个点,(w_i) 分别有 (p_{i,1},p_{i,2},p_{i,3}) 的概率取 (1,2,3)

    在确定了所有的 (w_i) 后再开始游戏:不断抽点,点 (i) 被抽中的概率为 (frac {w_i}{sum_{j=1}^nw_j}),直到所有点都被抽中过。

    给定 (n-1) 个二元组 ((u,v)) 表示第一次抽中 (u) 的时间需要比第一次抽中 (v) 的时间早,且若将这 (n-1) 个二元组中的两个元素连无向边,则这张图是一棵树。

    问满足所有二元组限制的概率。

    (nleq 1000)

    Solution

    (话说考场上看错了题,以为是确定好每个点抽中的概率后再进行游戏,打了正解加仨暴力,跑出来都是一样的就是和大样例不一样,导致心态崩了 而且题面里并没有提到这个区别

    这题的解题突破口在于这个题的限制是一棵树,但边的方向不唯一(没法找到根使得这棵树变成内向树或外向树)

    为了解题方便,不妨设这棵树是以 (1) 号节点为根的外向树(对于每条边而言,方向都是从靠近 (1) 的一端指向远离 (1) 的一端)

    那么可以发现,(1) 号节点必须是最后一个抽到的,概率为 (frac {w_1}{sum_{i=1}^nw_i}),而且只要满足这个条件后,这个点就没有用了,问题分解成 (1) 的每棵子树满足自身拓扑序的概率之积,这又是一个子问题

    (sz_i) 表示 (i) 的子树中概率系数的和((sum_{vin subtree(x)}w_v)

    即设 (f[x]) 表示 (x) 的子树内满足拓扑序的概率,则 (f[x]=frac {w_x}{sz_x}prod_{x ightarrow v}f[v]),不难得到 (Ans=f[1]=prod_{x=1}^nfrac {w_x}{sz_x})

    那么可以用一个Dp解决这个问题:设 (f[x][i]) 表示在 (i) 的子树中、概率系数和为 (j) 且满足拓扑序的概率,(O(n^2))

    毒瘤出题人居然不给这档部分分?


    考虑到这棵树不是外向树,存在若干条内向边。

    直接处理内向边不好处理,现在存在一种方法,即用 “不考虑这条边”的方案数 减去 “考虑这条边为外向边”的方案数

    (用图来表达即:(o ightarrow oleftarrow o) 等于 (o ightarrow oquad o) 减去 (o ightarrow o ightarrow o)

    然后在树上Dp时,遇到此类内向边就将权值用上面这种方法计算一下即可。


    听说这题还有另一种做法,代码实现上是一样的,但思想不大一样:

    考虑设 (g[i]) 表示这若干条内向边中,至少有 (i) 条内向边的条件不满足,则容斥可知答案为:(sum_i(-1)^ig[i])

    而 “至少 (i) 条内向边不满足”,也即 “(i) 条内向边变为外向边,而其他内向边断开”

    这样在Dp的时候加入一个容斥系数即可:一开始设所有内向边断开,然后给其加上一个负的 “内向边变外向边” 的权值

    Code

    //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
    ", Ans%p);
    	return 0;
    }
    
  • 相关阅读:
    基于模糊Choquet积分的目标检测算法
    Android开发5:布局管理器2(表格布局TableLayout)
    JAVA WEB开发环境搭建教程
    linux下自助获取帮助
    dsp下基于双循环缓冲队列的视频采集和显示记录
    找工作笔试面试那些事儿(11)---数据库知识总结(2)范式
    【Todo】Zookeeper系列文章
    VC2010对Excel的操作
    hdu2647解题报告
    premake 在64位Ubuntu系统下编译32位GCC程序
  • 原文地址:https://www.cnblogs.com/penth/p/11398655.html
Copyright © 2011-2022 走看看