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

来源:互联网 发布:第四套人民币淘宝 编辑:程序博客网 时间:2024/06/11 05:42

题面

这里写图片描述

很玄幻的一题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);}
原创粉丝点击