【YBTOJ】【Luogu P6089】[JSOI2015]非诚勿扰
链接:
题目大意:
有 \(n\) 个甲类点和 \(n\) 个乙类点。每个甲点可以和若干个乙点连接,对于每一个乙点,它会以 \(p\) 的概率选择此点,不选择就跳到下一个点,若最后一点不选择,则跳到第一点。求出期望逆序对个数。
正文:
假设一个甲点可以和 \(k\) 个乙点连接。
因为像一个环一样,可以转几圈。第一个乙点在第一圈被选中的概率是 \(p\),第二圈被选中的概率就是 \((1-p)^kp\)(即不选它 \(k\) 次也就转了一圈,转完后再选),第三圈被选中的概率是 \((1-p)^{2k}p\),以此类推第一个乙点被选中的概率是:
\[E_1=\sum_{i=0}^{\infty}(1-p)^{ik}p
\]
通过等比数列求和公式得到:
\[E_1=\frac{1-(1-p)^{\infty}}{1-(1-p)^{k}p}p=\frac{p}{1-(1-p)^{k}p}
\]
式子中 \((1-p)^{\infty}\) 无限大所以 \(1-(1-p)^{\infty}\) 无限小,所以可以把它化掉。
那么 \(E_1\) 求好了,剩下的呢?很容易得到 \(E_i=E_{i-1}\cdot(1-p)\),因为 \(i\) 相当于是先拒绝了 \(i-1\) 后得来的。
剩下的就和题目大意的一样,求一个逆序对就搞定了。
代码:
int n, m;
long double p, t[N], ans;
vector <int> v[N];
void add (int x, long double k) {for (; x < n; x += x & -x) t[x] += k; }
long double query (int x) {long double ans = 0;for (; x; x -= x & -x) ans += t[x]; return ans;}
long double qpow(long double a, int b)
{
long double ans = 1;
for (; b; b >>= 1) {if(b & 1)ans *= a; a *= a;}
return ans;
}
int main()
{
scanf ("%d%d", &n, &m);
scanf ("%Lf", &p);
for (int i = 1; i <= m; i++)
{
int x, y;
scanf ("%d%d", &x, &y);
v[x].push_back(y);
}
for (int i = 1; i <= n; i++) sort (v[i].begin(), v[i].end());
for (int i = 1; i <= n; i++)
{
if (v[i].empty()) continue;
int k = v[i].size();
long double P = p / (1 - qpow(1 - p, k));
for (int j = 0; j < k; j++)
ans += P * query(n - v[i][j]),
P *= 1 - p;
P = p / (1 - qpow(1 - p, k));
for (int j = 0; j < k; j++)
add(n + 1 - v[i][j], P),
P *= 1 - p;
}
printf ("%.2Lf", ans);
return 0;
}