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

版权声明:蒟蒻的博文,dalao转载标明出处就好吖 https://blog.csdn.net/jokingcoder/article/details/89252535

ZJOI2012波浪

题意
1 N 1\to N 的全排列 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 M L\geq M 的概率有多大,保留k位小数, k 30 k\leq 30

Solution
(这个 s o l u t i o n solution 不是正文)
我们考虑一个数如果比左右两侧的数都大那么 A m i d + m i d B = 2 × m i d A B |A-mid|+|mid-B|=2\times mid-A-B ,他的贡献为 2 × m i d 2\times mid
同理,如果一个数比一侧的数大,比另一侧的数小,贡献为 0 0 ;比两侧都小,贡献为 2 × m i d -2\times mid

先从小到大排序,每次考虑插入时的状态,因为后插入的一定大,所以考虑和那些块相邻
d p [ i ] [ j ] [ l ] [ p ] dp[i][j][l][p] 表示前 i i 个数, j j 个联通块,答案 L L l l ,边界占了 p p 个的方案数(暂时是可以这么讲的)

i i 这一维可以仿佛滚掉
l [ 4500 , 4500 ] \because l\in [-4500,4500] ,加一个常数 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 时用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;
}

猜你喜欢

转载自blog.csdn.net/jokingcoder/article/details/89252535