ECNU XCPC 2021 Selection Round 3
E. Drinking
观察一下不难发现,任意一端区间$g(l,r)=\sum\limits_{i=1}^nx_{(i)}\frac{1}{2^i},x(i)$表示区间内第$i$大的值,由于误差要求$<10^{-6}$,所以估一下只要取区间最大的T=50个数
自然想到单独计算每个数对答案的贡献。设第$i$个数左边比它大的数从右到左为$l_1,l_2,\cdots,l_T$,右边比它大的数从左往右为$r_1,r_2,\cdots,r_T$,那么每个数的贡献为
然后发现可以把u、v分离出来
从小到大用链表维护一下,可以找到每个数左边和右边比它大的、最近的T个数,计算完之后从链表里删掉,复杂度$O(n)$,常数<=100
#include <bits/stdc++.h>
#define debug(x) cerr << #x << " = " << x << '\n'
using namespace std;
const int N = 1e6 + 5;
int n, w[N], pre[N], nxt[N], ord[N];
double ans;
void Remove(int x) {
nxt[pre[x]] = nxt[x];
pre[nxt[x]] = pre[x];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
for (int i = 1; i <= n; i++) ord[i] = i;
sort(ord + 1, ord + 1 + n, [&](int u, int v) {
return w[u] < w[v];
});
for (int i = 1; i <= n; i++) pre[i] = i - 1, nxt[i] = i + 1;
nxt[0] = 1, pre[n + 1] = n;
pre[0] = nxt[n + 1] = -1;
for (int i = 1; i <= n; i++) {
double fct1, fct2, rat;
fct1 = fct2 = 0;
rat = 0.5;
for (int j = 1, now = pre[ord[i]]; j <= 50 && now != -1; j++, now = pre[now]) {
fct1 += (nxt[now] - now) * rat;
rat /= 2;
}
rat = 0.5;
for (int j = 1, now = nxt[ord[i]]; j <= 50 && now != -1; j++, now = nxt[now]) {
fct2 += (now - pre[now]) * rat;
rat /= 2;
}
ans += w[ord[i]] * 2 * fct1 * fct2;
Remove(ord[i]);
}
printf("%.6f\n", ans / n / n);
return 0;
}
F.Mathematics
递推式$f_n=1-nf_{n-1}$
正着递推误差会不断放大,然后很快就爆炸了
但是反着推$f_{n-1}=\frac{1-f_n}{n}$误差会不断缩小,所以可以假设$f_{20000}=0$(这个误差本身就非常小),然后反着推出$f_n$
#include <bits/stdc++.h>
using namespace std;
double ans;
int n;
int main() {
cin >> n;
for (int i = 20000; i >= n + 1; i--) {
ans = (1 - ans) / i;
}
printf("%.4f\n", ans);
return 0;
}