[ZJOI2012]波浪——dp+__float128卡精度

本文深入探讨了ZJOI2012波浪问题,讲解了如何通过动态规划求解特定排列下波浪幅度的累计值大于等于给定阈值的概率,并分享了在不同数据精度要求下采用的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ZJOI2012波浪

题意
1 → N 1\to N 1N的全排列 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=P1P2+P2P3++PN1PN
L ≥ M L\geq M LM的概率有多大,保留k位小数, k ≤ 30 k\leq 30 k30

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 Amid+midB=2×midAB,他的贡献为 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

  1. dp数组其实都是些整数,但是数值很大所以可以直接开long double或者__float128
  2. 为什么我们进行dp的时候都不用判断这种情况是否能做到就直接进行转移呢?比如if (l - i >= -D) dp[nxt][j + 1][l - i + D][p + 1] += nows * (2 - p);为什么就一定能保证当前海浪放在边界的时候就一定能自成一段呢?
  3. 这个很多题解也都说了,dp数组分两类, k ≤ 8 k\leq 8 k8时用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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值