选书问题 / 非诚勿扰
题目链接:ybt金牌导航1-1-7 / luogu P6089
题目大意
就是有两种点,第一种的点会与一些第二种点连接,然后对于每一个连着的第二种点,这个第一种的点会有 p 的概率选择这个点,否则就跳到下一个和它连着的点。然后如果一直到最后一个都不选,就跳到第一个继续。
问你期望逆序对的概率。
思路
我们看第一种点连着 k k k 个第二种点。
然后我们来看选中第
i
i
i 个第二种点的概率。
由于有无限,而且很麻烦,我们只看第一个点。
它第一轮被选中的概率是
p
p
p,第二轮是
(
1
−
p
)
k
p
(1-p)^kp
(1−p)kp,第三轮是
(
1
−
p
)
2
k
p
(1-p)^{2k}p
(1−p)2kp,以此类推。
我们可以发现总概率就是
∑
i
=
0
∞
(
1
−
p
)
i
k
p
\sum\limits_{i=0}^{\infty }(1-p)^{ik}p
i=0∑∞(1−p)ikp。
那我们看怎么化简,可以发现是一个等比数列的和。
那我们根据和等于
a
1
(
1
−
q
n
)
1
−
q
\dfrac{a_1(1-q^n)}{1-q}
1−qa1(1−qn),可以把式子变成这个:
p
(
1
−
(
1
−
p
)
k
×
∞
)
1
−
(
1
−
p
)
k
\dfrac{p(1-(1-p)^{k\times\infty})}{1-(1-p)^k}
1−(1−p)kp(1−(1−p)k×∞),即
p
(
1
−
(
1
−
p
)
∞
)
1
−
(
1
−
p
)
k
\dfrac{p(1-(1-p)^{\infty})}{1-(1-p)^k}
1−(1−p)kp(1−(1−p)∞)
因为
1
−
p
1-p
1−p 是一个大于零小于一的数,所以它的无限次方就无限趋近与零,那一减它就是无限趋近于一咯。
那这个式子就变成了这个:
p
1
−
(
1
−
p
)
k
\dfrac{p}{1-(1-p)^k}
1−(1−p)kp
那我们就得出了第一个点被选中的概率,那怎么求第二个、第三个这些点的概率呢?
很明显,可以知道这些点被选中的概率就是上一个点被选中的概率
×
(
1
−
p
)
\times (1-p)
×(1−p)。(就是上一个不选,然后这个选,就要不多一次不选,就是多乘一次
×
(
1
−
p
)
\times (1-p)
×(1−p))
第 i i i 个被选到的概率就是 p × ( 1 − p ) i − 1 1 − ( 1 − p ) k \dfrac{p\times(1-p)^{i-1}}{1-(1-p)^k} 1−(1−p)kp×(1−p)i−1。
求次方很明显用快速幂,那我们就可以求出概率了。
接下来,我们来看期望怎么求。
看到题目要求逆序对,我们会很自然地想到求逆序对的树状数组。
但是它要的是期望诶。
那普通求逆序对个数是每次加一,那我们就加出现这个点的概率。而且找前面的逆序对的时候还要乘上这个点被选到的概率。
这样子,我们就可以得出这两个点都被选到的概率相乘,也就是这一个逆序对的期望了。
然后有一点要注意的就是题目会卡精度,用 long double 来解决。
代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n, m, x, y, size;
vector <int> a[500001];
long double p, re, ans, tree[500001], thi;
void build(int x, long double y) {//树状数组
for (int now = x; now <= 500000; now += now & (-now))
tree[now] += y;
}
long double ask(int x) {
re = 0;
for (int now = x; now; now -= now & (-now))
re = re + tree[now];
return re;
}
long double ksm(long double x, int y) {//快速幂
re = 1.0;
while (y) {
if (y & 1) re = re * x;
x = x * x;
y >>= 1;
}
return re;
}
int main() {
scanf("%d %d", &n, &m);
scanf("%Lf", &p);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
a[x].push_back(y);
}
for (int i = 1; i <= n; i++)
sort(a[i].begin(), a[i].end());
ans = 0.0;
for (int i = 1; i <= n; i++) {
size = a[i].size();
for (int j = 0; j < size; j++) {
thi = p * ksm(1.0 - p, j) / (1.0 - ksm(1.0 - p, size));//算出来的概率
ans += (ask(m) - ask(a[i][j])) * thi;
build(a[i][j], thi);
}
}
printf("%.2Lf", ans);
return 0;
}