bzoj2817: [ZJOI2012]波浪 计数Dp

bzoj2817: [ZJOI2012]波浪

分析

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

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

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

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

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

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

Condition1

fi,j,k=fi1,j,k2k f i , j , k = f i − 1 , j , k ⋅ 2 k

Condition2

fi,j+i,k1=fi1,j,k(k1) f i , j + i , k − 1 = f i − 1 , j , k ⋅ ( k − 1 )

Condition3

fi,ji,k+1=fi1,j,k(k+1) 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值