Luogu2612 ZJOI2012 波浪 DP

传送门


花掉了自己用来搞学科的时间做了这道题……

一道类似的题:Here

考虑拆开绝对值计算贡献。那么我们对于\(1\)\(N\)的排列,从小到大地将插入它们插入排列中。

假设我们现在计算到了数\(i\),这意味着前\(i-1\)个数已经被插入到了排列中。考虑当前如何计算\(i\)的贡献。

不难发现:在最终的排列中,\(i\)的贡献与它和前\(i-1\)个数和边界的相邻情况有关。如果\(i\)某一边与边界相邻,会产生\(0\)的贡献;某一边与小于\(i\)的数相邻,会产生\(i\)的贡献;某一边与大于\(i\)的数相邻,会产生\(-i\)的贡献。

但是在这里大于\(i\)的数还没有被插入,所以这里必须要强制\(i\)与前\(i-1\)个数和边界的相邻情况才能够在当前阶段计算出\(i\)对序列价值的贡献。

故设\(f_{i,j,k,l}\)表示放完了前\(i\)个数、数列中存在\(j\)个连通块(定义连通块为一段极长区间,满足这一段区间任意的相邻的两个数都被强制定为相邻,也就是说在之后的转移中,这一段区间内不能插入数)、数列总价值为\(k-5000\)、有\(l\)个边界已经与某个数强制相邻的方案数。

转移:

\(a.\)一边与边界相邻,一边不与当前产生的连通块相邻,产生\(-i\)的价值,方案数为\(2-l\)

\(b.\)一边与边界相邻,一边与当前产生的连通块相邻,产生\(i\)的价值,方案数为\(2-l\),要求\(j \neq 0\)

\(c.\)两边均与当前产生的连通块相邻,产生\(2i\)的价值,方案数为\(j-1\),要求\(j \geq 2\)

\(d.\)两边均不与当前产生的连通块相邻,产生\(-2i\)的价值,方案数为\(j+1-l\)

\(e.\)一边与当前产生的连通块相邻,另一边不与当前产生的连通块相邻,产生\(0\)的价值,方案数为\(j*2-l\),要求\(j \neq 0\)

注意到\(de\)两种转移方案数都减去了\(l\),因为对于两端都不与边界相邻的连通块,可以选择左右之一与当前的数相邻,但是有一段与边界相邻的连通块只有一端可以。

最后的答案就是\(\frac{\sum\limits_{i=5000+M}^{10000} f_{N,1,i,2}}{n!}\)

然后这鬼题还要数据类型分治……\(K \leq 8\)使用long double,\(K \leq 30\)使用__float128

// luogu-judger-enable-o2
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c)){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    while(isdigit(c)){
        a = (a << 3) + (a << 1) + (c ^ '0');
        c = getchar();
    }
    return f ? -a : a;
}

namespace db{long double dp[2][110][10010][3];}
namespace flt{__float128 dp[2][110][10010][3];}
int N , M , K;

template < class T >

void out(T ans){
    cout << "0.";
    ans *= 10;
    for(int i = 1 ; i <= K ; ++i){
        cout << (int)(ans + (K == i) * 0.5);
        ans = (ans - (int)ans) * 10;
    }
}

template < class T >

inline void solve(T dp[][110][10010][3]){
    T ans = 0;
    dp[0][0][5000][0] = 1;
    int now = 0;
    for(int i = 1 ; i <= N ; ++i){
        now ^= 1;
        memset(dp[now] , 0 , sizeof(dp[now]));
        for(int j = 0 ; j <= min(i - 1 , M) ; ++j)
            for(int k = 0 ; k <= 10000 ; ++k)
                for(int p = 0 ; p <= 2 ; ++p)
                    if(dp[now ^ 1][j][k][p]){
                        if(k - 2 * i >= 0)
                            dp[now][j + 1][k - 2 * i][p] += dp[now ^ 1][j][k][p] * (j + 1 - p);
                        if(j)
                            dp[now][j][k][p] += dp[now ^ 1][j][k][p] * (j * 2 - p);
                        if(j >= 2 && k + 2 * i <= 10000)
                            dp[now][j - 1][k + 2 * i][p] += dp[now ^ 1][j][k][p] * (j - 1);
                        if(p != 2){
                            if(k - i >= 0)
                                dp[now][j + 1][k - i][p + 1] += dp[now ^ 1][j][k][p] * (2 - p);
                            if(j && k + i <= 10000)
                                dp[now][j][k + i][p + 1] += dp[now ^ 1][j][k][p] * (2 - p);
                        }
                    }
    }
    for(int i = M ; i <= 5000 ; ++i)
        ans += dp[now][1][5000 + i][2];
    for(int i = 1 ; i <= N ; ++i)
        ans /= i;
    out(ans);
}

int main(){
    #ifndef ONLINE_JUDGE
    //freopen("in" , "r" , stdin);
    //freopen("out" , "w" , stdout);
    #endif
    N = read();
    M = read();
    K = read();
    if(K <= 8)
        solve(db::dp);
    else
        solve(flt::dp);
    return 0;
}
posted @ 2019-01-27 21:34  cjoier_Itst  阅读(273)  评论(0编辑  收藏  举报