题意
在
1
→
N
1\to N
1→N的全排列
P
P
P中
L
=
∣
P
1
–
P
2
∣
+
∣
P
2
–
P
3
∣
+
…
+
∣
P
N
−
1
–
P
N
∣
L=|P_1–P_2|+|P_2–P_3|+…+|P_{N-1}–P_N|
L=∣P1–P2∣+∣P2–P3∣+…+∣PN−1–PN∣
问
L
≥
M
L\geq M
L≥M的概率有多大,保留k位小数,
k
≤
30
k\leq 30
k≤30
Solution
(这个
s
o
l
u
t
i
o
n
solution
solution不是正文)
我们考虑一个数如果比左右两侧的数都大那么
∣
A
−
m
i
d
∣
+
∣
m
i
d
−
B
∣
=
2
×
m
i
d
−
A
−
B
|A-mid|+|mid-B|=2\times mid-A-B
∣A−mid∣+∣mid−B∣=2×mid−A−B,他的贡献为
2
×
m
i
d
2\times mid
2×mid
同理,如果一个数比一侧的数大,比另一侧的数小,贡献为
0
0
0;比两侧都小,贡献为
−
2
×
m
i
d
-2\times mid
−2×mid
先从小到大排序,每次考虑插入时的状态,因为后插入的一定大,所以考虑和那些块相邻
d
p
[
i
]
[
j
]
[
l
]
[
p
]
dp[i][j][l][p]
dp[i][j][l][p]表示前
i
i
i个数,
j
j
j个联通块,答案
L
L
L为
l
l
l,边界占了
p
p
p个的方案数(暂时是可以这么讲的)
i
i
i这一维可以仿佛滚掉
∵
l
∈
[
−
4500
,
4500
]
\because l\in [-4500,4500]
∵l∈[−4500,4500],加一个常数
D
D
D即可
Attention
- dp数组其实都是些整数,但是数值很大所以可以直接开
long double
或者__float128
- 为什么我们进行dp的时候都不用判断这种情况是否能做到就直接进行转移呢?比如
if (l - i >= -D) dp[nxt][j + 1][l - i + D][p + 1] += nows * (2 - p);
为什么就一定能保证当前海浪放在边界的时候就一定能自成一段呢? - 这个很多题解也都说了,dp数组分两类,
k
≤
8
k\leq 8
k≤8时用
long double
,否则用__float128
,全用__float128
会T。。。
Tricks
这里才是正文(相信大佬们一定直接跳过了上面的dp部分)。
本文重点就是如何卡精度,方法有很多(比如还能猜数据开大小的?),但是局部动态开是会某些OJ上是会RE的。我采取了开两个namespace
的方法,用template
开不同的数组类型,这样的内存是两倍的,可能比较大(雾
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 110
#define D 4500
namespace db{long double dp[2][N][(D << 1) + 10][3];}
namespace flt{__float128 dp[2][N][(D << 1) + 10][3];}
int n, m, k;
template < class T >
inline void doit(T dp[][N][(D << 1) + 10][3]) {
int now = 1, nxt = 0;
dp[0][0][D][0] = 1;
for (int i = 1; i <= n; ++i) {
std :: swap(now, nxt);
memset(dp[nxt], 0, sizeof dp[nxt]);
for (int j = 0; j <= std :: min(i - 1, m); ++j) {
for (int l = -D; l <= D; ++l) {
for (int p = 0; p <= 2; ++p) {
T nows = dp[now][j][l + D][p];
if (!nows) continue;
if (p <= 2) {
if (j && l + i <= D) dp[nxt][j][l + i + D][p + 1] += nows * (2 - p);
if (l - i >= -D) dp[nxt][j + 1][l - i + D][p + 1] += nows * (2 - p);
}
if (l - (i << 1) >= -D) dp[nxt][j + 1][l - (i << 1) + D][p] += nows * (j + 1 - p);
if (j) dp[nxt][j][l + D][p] += nows * ((j << 1) - p);
if (j > 1 && l + (i << 1) <= D) dp[nxt][j - 1][l + (i << 1) + D][p] += nows * (j - 1);
}
}
}
}
T prt = 0;
for (int i = m; i <= D; ++i) {
prt += dp[nxt][1][i + D][2];
}
for (int i = 1; i <= n; ++i) {
prt /= i;
}
int tot = prt;
printf("%d.", tot);
while (k--) {
prt = (prt - tot * 1.0) * 10.0;
if (!k) prt = prt + 0.5;
tot = prt;
printf("%d", tot);
} printf("\n");
}
int main() {
scanf("%d%d%d", &n, &m, &k);
if (k <= 8) doit(db :: dp);
else doit(flt :: dp);
return 0;
}