jzoj5342 赤壁情 (序列插入型dp,笛卡尔树新奇姿势dp)

题面

这里写图片描述

很玄幻的一题dp.
现在有一块一块的子串,他们的相对顺序是已知的但最终一定不相邻 (也就是说中间至少有一个数)

考虑从小到大插入数字,每一个数字有3种基本转移
1. 在某个空隙新建一块,
2. 并入某块(放最左或最右)。
3. 合并相邻两块

贡献系数好算,比如说并入一块那么系数就是0,假如新开系数就是-2 (因为可以知道旁边是比他大的还是比他小的)

但是有一个问题,边界贡献比较特殊,所以要开两个特殊块:边界 L,R
顾名思义,规定边界的左右不能再放。
那么要加几条转移
1. 在某个空隙新建一块普通块
3. 新建L或R
4. 并入某普通块(放最左或最右)
5. 并入目前的最左或最右,并将其升级为L或R
(因为最终的最左或最右不一定小,要支持在他旁边插数)
6. 并入L或R
7. 合并相邻两块

这样我们状态就变成了
f[][][][L][R] 表示这样的方案数
N(N2)N4 的复杂度,转移虽然是O(1)的,但实际上有7条。所以比较慢了

最终的答案就是sum f[n][k][1][1][1],最后再除上一个排列就得到概率了. (强行套上概率的,明明可以直接加mod,还是比较慢的缘故?)

这样为什么不会重呢? 考虑到一块中的元素是从小到大被加入的,每一块就相当于一颗笛卡尔树。转移的意义分别是
1. 在某个空隙新建一块, (以当前为根新建一棵)
2. 并入某块(放最左或最右)。 (以当前为根,将原有笛卡尔树视为左或右子树)
3. 合并相邻两块 (以当前为根,左右分别为两颗子树)

因为一个序列对应着的笛卡尔树只有一棵,在从小到大加入前提下,显然一棵笛卡尔树有且仅有一种构造方法。 因此是不重不漏的。

Demo

//#pragma GCC optimize(2)
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
// typedef __float128 fff;
typedef double fff;

const int N=101;
fff f[2][N][N*N][2][2];
int n,m,k,e,o;

int main() {
    freopen("river.in","r",stdin);
    freopen("river.out","w",stdout);
    cin>>n>>m>>k;
    e=n*n/2; 
    f[o][0][e][0][0]=1;
    for (int i=0; i<n; i++) {
        memset(f[1-o],0,sizeof f[1-o]);
        for (int j=0; j<=i; j++) {
            if (n-i+1<j-1) break;

            for (int k=0; k<=e*2; k++) {
                for (int u=0; u<=1; u++) for (int v=0; v<=1; v++) {
                    fff p = f[o][j][k][u][v];
                    if (p<1) continue;

                    //create
                    if (k-i>0) {
                        if (!u) f[1-o][j+1][k-(i+1)][1][v]+=p;
                        if (!v) f[1-o][j+1][k-(i+1)][u][1]+=p;
                        if (k-2*i>0) 
                            f[1-o][j+1][k-2*(i+1)][u][v]+=p*(j+1-u-v);
                    }
                    //add
                    if (j) {
                        f[1-o][j][k][u][v]+=p*(j*2-u-v);
                        //start-add
                        if (!u) f[1-o][j][k+(i+1)][1][v]+=p;
                        //end-add
                        if (!v) f[1-o][j][k+(i+1)][u][1]+=p;

                        //merge
                        if (j>1) f[1-o][j-1][k+2*(i+1)][u][v]+=p*(j-1);
                    }

                }
            }
        }
        o=1-o;
    }

    fff ans = 0;
    for (int i=m+e; i<=e*2; i++) 
        ans+=f[o][1][i][1][1];
    for (int i=1; i<=n; i++) ans/=i;
//  cout<<ans<<endl;

    printf("0.");
    for (int i=1; i<k; i++) {
        ans*=10;
        printf("%d",((int)ans)%10);
    }
    if ((int)(ans*100)%10>=5) printf("%d",(int)(ans*10)%10+1); else printf("%d",(int)(ans*10)%10);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值