【TopCoder SRM712】AverageVarianceSubtree

相关链接

题目传送门:https://community.topcoder.com/stat?c=problem_statement&pm=14568&rd=16881

题目大意

给定一棵$n(n \le 50)$个结点的树,每个点上有一个点权$a_i(a_i \le 10^9)$
让你从中随意选出一个连通子图,询问这个连通子图中的每一个点的权值组成的序列的方差的期望是多少
小数输出,最终误差不得大于$\frac{1}{10^9}$

解题报告

将方差的式子化一化,发现我们大概要维护$E(\sum{a_i^2})$和$E((\sum{a_i})^2)$
第一个可以直接维护
第二个那一坨,我们还是使用期望的线性将其拆开,分别维护即可
最后再做一个树$DP$,总的复杂度大概在$O(n^3)$

Code

long double 最后一个点被卡精度了,只能用__float128

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 59;
const int M = N << 1;

class AverageVarianceSubtree {
	int n,E,head[N],nxt[M],to[M];
	__float128 ans,tot,val[N],s1[N][N],s2[N][N],s3[N][N],cnt[N][N];
    public:
    	double average(vector<int> p, vector<int> weight) {
    		E = 1; ans = tot = 0; memset(head,0,sizeof(head));
    		memset(s1,0,sizeof(s1)); memset(s2,0,sizeof(s2));
    		memset(s3,0,sizeof(s3)); memset(cnt,0,sizeof(cnt));
    	     
    	    n = weight.size();
			for (int i=1;i<=n;i++) val[i] = weight[i - 1];
			for (int i=0;i<n-1;i++) AddEdge(i + 2, p[i] + 1);
			DFS(1, 1);
			for (int i=1;i<=n;i++) {
				for (int j=1;j<=n;j++) {
					ans += (s2[i][j] - s3[i][j] / j) / j;
					tot += cnt[i][j];
				}
			} 
			return ans / tot;
   		}
   	private:
   		inline void AddEdge(int u, int v) {
			to[++E] = v; nxt[E] = head[u]; head[u] = E;
			to[++E] = u; nxt[E] = head[v]; head[v] = E;
		}
		void DFS(int w, int f) {
			cnt[w][0] = cnt[w][1] = 1; 
			s1[w][1] = val[w]; 
			s2[w][1] = s3[w][1] = val[w] * val[w];
			for (int i=head[w];i;i=nxt[i]) {
				if (to[i] != f) {
					DFS(to[i], w);
					for (int t=n;t;t--) {
						for (int a=1,b;b=t-a,a<t;a++) {
							s3[w][t] += s3[w][a] * cnt[to[i]][b] + s3[to[i]][b] * cnt[w][a] + 2 * s1[w][a] * s1[to[i]][b];
							s1[w][t] += s1[w][a] * cnt[to[i]][b] + s1[to[i]][b] * cnt[w][a];
							s2[w][t] += s2[w][a] * cnt[to[i]][b] + s2[to[i]][b] * cnt[w][a];
							cnt[w][t] += cnt[w][a] * cnt[to[i]][b];
						}
					}
				}
			}
		}
};

【BZOJ 3451】Tyvj1953 Normal

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3451
神犇题解:http://wyfcyx.is-programmer.com/posts/74735.html

解题报告

考虑一个点$u$,如果点分到点$v$的时候会产生贡献
那么点分$v$的时候,$v \to u$这个路径上还没有其他点被点分
换一句话来讲,点$v$应该是$v \to u$这条路径上第一个被点分的点
因为每一个点被选的概率一样,所以贡献的概率是$\frac{1}{dis_{u \to v} + 1}$
于是最后答案就是$\sum\limits_{u,v \in [1,n]}{\frac{1}{dis_{u \to v}+1}}$

然后这个东西我们可以使用点分治加上FFT来解决
具体来讲就是在点分的时候统计$dis_{u \to v}=x$的方案数,然后计入答案
时间复杂度:$O(n \log^2 n)$

—————————— UPD 2017.4.11 ————————————
找到这题的序列版了:http://www.lydsy.com/JudgeOnline/problem.php?id=2769
在具体的做法方面,用分个块,然后块内暴力,块间FFT即可

【BZOJ 3811】玛里苟斯

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3811
神犇题解Ⅰ:https://blog.sengxian.com/solutions/bzoj-3811
神犇题解Ⅱ:http://yyy.is-programmer.com/posts/200623.html

解题报告

这题这么神,我们来分情况讨论:

1. $k = 1$

这就是一般的期望题。因为期望的线性,所以我们在二进制位下每一位分开考虑:

如果这一位上每一个数都是$0$,那么贡献肯定为$0$
如果有一个数为不为$0$那么我们有贡献的概率为$\frac{1}{2}$

证明的话,可以设$f_{1,0/1}$为考虑到第i个数,异或起来为0/1的概率
写出$DP$式子可以很轻松地发现这俩总是对半分,Q.E.D

于是我们直接把所有数$or$起来,然后除二输出即可
时间复杂度:$O(n)$

2. $k = 2$

这不是一般的期望题了,不是线性的,不能直接加 /(ㄒoㄒ)/~~
但我们发现某一个异或和为$(b_mb_{m-1} \cdots b_0)_{bin}$的话
其中第$i$位与第$j$位的贡献为$b_i \cdot b_j \cdot 2^{i+j}$

因为$b_i$与$b_j$是线性的,所以我们就可以枚举$i,j$然后直接加起来了!
根据$k = 1$时得到的结论,不难发现:

如果这两位独立则贡献的概率为$\frac{1}{4}$
如果这两位不独立,那么贡献的概率为$\frac{1}{2}$
如果这两位中有至少一位从没出现过,那么概率为$0$

于是我们暴力枚举$i,j$直接算贡献就可以了
时间复杂度:$O(62n + 62^2)$

3. $k \ge 3$

我们先来看一个结论:若$E(x^k) < 2^{63}$,初始集合中的每个数小于$2^{22}$
证明的话,sengxian教我的:

不妨用反证法,考虑答案为:$\sum\limits_{s \in \{1,2,\cdots,n\}}{\frac{v^3}{2^n}}$
假如有一个数的二进制下第$22$位出现了$1$,有$2^{n-1}$个集合异或起来后这一位为$1$
所以这一位的贡献就已经为$2^{63}$了,又因为答案小于$2^{63}$,矛盾,故不可能,Q.E.D

所以我们可以求出这些数的线性基,然后暴力枚举线性基的子集
根据$k = 1$时的人生经验,我们又可以得到每一种情况出现的概率相等
于是我们暴力枚举,然后暴力算贡献就可以了
时间复杂度:$O(21n + 2^{21})$

【BZOJ 3566】[SHOI2014] 概率充电器

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3566
神犇题解:https://blog.sengxian.com/solutions/bzoj-3566

解题报告

首先根据期望的线性,我们只需要求出每个元件有电的概率,之后相加就是答案
那么怎么求每个元件有电的概率呢?我们可以无脑点分
显然在$O(n)$的时间复杂度内搞一发树形$DP$就可以了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 500009;
const int M = N << 1;

int n,head[N],nxt[M],to[M]; 
double up[N],down[N],cost[M],q[N],vout;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline void AddEdge(int u, int v, int f) {
	static int E = 1; 
	cost[E+1] = cost[E+2] = f / 100.0;
	to[++E] = v; nxt[E] = head[u]; head[u] = E;
	to[++E] = u; nxt[E] = head[v]; head[v] = E;
}

void DFS1(int w, int f) {
	down[w] = 1 - q[w];
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != f) {
			DFS1(to[i], w);
			down[w] *= down[to[i]] + (1 - down[to[i]]) * (1 - cost[i]);
		}
	}
}

void DFS2(int w, int f, double p) {
	vout += 1 - (up[w] = down[w] * p);
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != f) {
			double tmp = p * down[w] / (down[to[i]] + (1 - down[to[i]]) * (1 - cost[i]));
			DFS2(to[i], w, tmp + (1 - tmp) * (1 - cost[i]));
		}
	}
}

int main() {
	n = read();
	for (int i=1,u,v;i<n;i++) {
		u = read(); v = read();
		AddEdge(u, v, read());
	}
	for (int i=1;i<=n;i++) q[i] = read() / 100.0;
	DFS1(1, 1);
	DFS2(1, 1, 1);
	printf("%.6lf\n",vout);
	return 0;
}

【BZOJ 4008】[HNOI2015] 亚瑟王

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4008
神犇题解:https://blog.sengxian.com/solutions/bzoj-4008

解题报告

根据期望地线性,我们可以单独计算每一个技能的期望然后相加
于是问题转化为求每个技能在这$r$轮中被发动的概率

但我们发现如果直接模拟题目中的过程,我们似乎需要记录哪些点已经被使用过
这个问题似乎只能使用状压DP来解决?但复杂度是不允许这么干的
于是我们需要一点Trick:

我们将这$r$轮整体转移,记录$f_{i,j}$表示到第$i$个技能,还有$j$轮没有触发任何技能的概率
那么这个状态的时候第$i$个技能被触发的概率就是$1-(1-p_i)^j$

于是这个问题就做完啦!时间复杂度:$O(Tnr)$
至于为什么是对的?
因为每一轮的游戏过程没有区别,所以我们可以将将其一视同仁

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int n,r,d[250];
double f[250][150],p[250][150],ans[250];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	for (int T=read();T;T--) { 
		memset(f,0,sizeof(f)); memset(ans,0,sizeof(ans));
		n = read(); r = read();
		for (int i=1;i<=n;i++) scanf("%lf%d",&p[i][0],&d[i]);
		for (int i=1;i<=n;i++) {
			p[i][1] = 1 - p[i][0];
			for (int j=2;j<=r;j++) p[i][j] = p[i][j-1] * p[i][1];
		}
		f[1][r] = 1;
		for (int i=1;i<=n;i++) {
			for (int j=1;j<=r;j++) {
				ans[i] += f[i][j] * (1 - p[i][j]);
				f[i+1][j] += f[i][j] * p[i][j];
				f[i+1][j-1] += f[i][j] * (1 - p[i][j]);
			}
		}
		double vout = 0;
		for (int i=1;i<=n;i++) vout += ans[i] * d[i];
		printf("%.10lf\n",vout);
	} 
	return 0;
}

【HDU 5194】DZY Loves Balls

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5194
中文题面:http://blog.csdn.net/u014086857/article/details/44724335

解题报告

这题主要就是用到一个“期望的可加性”吧!
什么是可加性呢?

期望的和,等于和的期望

这里不需要期望之间相互独立
证明的话,你可以参见这里:http://blog.csdn.net/grooowing/article/details/45000205

于是这题我们分开考虑每一个位置出现01的概率就好了
于是推一推式子发现答案至于n,m相关,于是写一个gcd就好了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

int GCD(int x, int y) {
	return y? GCD(y, x % y): x;
} 

int main() {
	for (int n,m,gcd;~scanf("%d%d",&n,&m);) {
		gcd = GCD(n*m, n+m);
		printf("%d/%d\n", n*m/gcd, (n+m)/gcd);
	}
	return 0;
}