bzoj2817: [ZJOI2012]波浪 计数Dp

bzoj2817: [ZJOI2012]波浪

分析

比较烦的dp题
首先这个相邻两项差的绝对值,这个绝对值很烦对吧,于是根据常规操作,我们肯定从小到大放数
因为如此,我们考虑放一个数对答案的贡献。
这个时候,我们假设原来已经放了若干个数,我们发现,当前这个数对答案的贡献和原来放了什么数已经没有关系了。
考虑放数的方式,假设当前数为 i ,显然有

  1. 放在一个数旁边。
  2. 放在两个数旁边
  3. 没有放在任何一个数的旁边。

第一种情况,原来的数比当前的数小,之后放的数比当前的数大,所以贡献是 i i = 0
第二种情况,旁边的两个数都比当前的数小,所以贡献是 2 i
如果没有放在任何数旁边,之后放在这个数后面的数一定比当前的数更大。所以这个数的贡献就是 2 i

这个时候我们就要考虑这三种方案分别有多少个位置。而这些个位置其实是由之前的连通块个数决定的。就是放了几“团”数

第一种情况,假设原来的连通块个数为 k ,可以放的数的位置个数就是 2 k ,新的状态连通块个数仍然是 k
第二种情况位置个数是 k 1 ,新的状态连通块个数是 k 1 (合并了两个连通块)
第三种情况位置个数是 k + 1 ,新的状态连通块个数是 k + 1 (新建了一个连通块)
注意这里新建指的是相对位置,也就是插入。

这个时候我们发现还有另外一个问题,就是如果连通块放到了边界上,情况会不太一样,但其实问题不大,是类似的。
下面给出两个边界都没有连通块的情况,其余方程可参照代码。

Condition1

f i , j , k = f i 1 , j , k 2 k

Condition2

f i , j + i , k 1 = f i 1 , j , k ( k 1 )

Condition3

f i , j i , k + 1 = f i 1 , j , k ( k + 1 )
至于边上有联通快的情况,方程自己推一推就出来了,详见代码。
还有这道题比较恶心卡了精度,要用float128搞,而float128贼慢,所以按照数据分类用double和float即可。

代码

/**************************************************************
    Problem: 2817
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:15180 ms
    Memory:128800 kb
****************************************************************/

#include<cstdio>
#include<cstring>
#include<algorithm>
#define area [2][9005][101][3]
const int z = 4500, mx = 9000;
__float128 f area; double g area;
int n, m, k, top, st[10001];
template <class T> void print(T ans, int p) {
    long long i = (long long) ans; ans -= i;
    for(top = 0; i ; i /= 10) st[++top] = i % 10;
    for(int i = 1; i <= (top >> 1); ++i) std::swap(st[i], st[top - i + 1]);
    if(!top) st[++top] = 0; int pos = top + 1;
    for(int i = 1; i <= p; ++i, ans -= (st[++top] = (int)ans)) ans *= 10;
    ans *= 10; int c = (int)ans; if(c >= 5) ++st[top]; c = top;
    for(;st[c] == 10 && c; ++st[--c]) st[c] = 0;
    if(!c) putchar('1'); for(int i = 1;i <= top; putchar('0' + st[i++])) if(i == pos) putchar('.');
    putchar('\n');
}
template <class T> void Solve(T f area) {
    f[0][z - 2][1][0] = 1; f[0][z - 1][1][1] = 2; 
    f[0][z][1][2] = 1; int cu = 1;
    for(int i = 2;i <= n; ++i, cu ^= 1) {
        memset(f[cu], 0, sizeof(f[cu])); T tmp;
        for(int j = 0;j <= (z << 1); ++j)
            for(int k = 1;k <= n - 1; ++k) 
                for(int tp = 0;tp <= 2; ++tp)
                if(tmp = f[cu ^ 1][j][k][tp]) {
                    if(j + (i << 1) <= mx) f[cu][j + (i << 1)][k - 1][tp] += tmp * (k - 1); // Merge
                    if(j - (i << 1) >= 0) f[cu][j - (i << 1)][k + 1][tp] += tmp * (k + 1 - tp); // New
                    f[cu][j][k][tp] += tmp * (k * 2 - tp); // two side
                    if(tp <= 1) {
                        if(j - i >= 0) f[cu][j - i][k + 1][tp + 1] += tmp * (2 - tp); // New
                        if(j + i <= mx) f[cu][j + i][k][tp + 1] += tmp * (2 - tp); // one side
                    }
                }
    }
    T ans = 0;
    for(int i = m + z;i <= mx; ++i) ans += f[cu ^ 1][i][1][2];
    for(int i = 1;i <= n; ++i) ans /= (T)i;
    print(ans, k);
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    if(k <= 8) Solve(g);
    else Solve(f);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/79950958