zoukankan      html  css  js  c++  java
  • 【BZOJ】P2616 SPOJ PERIODNI

    笛卡尔树+树形dp

    你会发现,如果有两个高度分别为(h[i])(h[j])(满足(i<j))的棋盘,存在一个k满足(i<k<j)(h[i]>h[k],h[j]>h[k]),那么在任意高度H满足(H>min{h[k]}k in [i+1,j-1])的i和j棋盘都不会相互制约的。

    那么,我们能不能先在n个棋盘中找到一个最小的h,那么其两旁的棋盘在大于h的地方摆放都是不会互相影响的,那我们只用先算出在两旁大于h的地方摆放棋子的方案数,再合并一下,最后再统一算一遍这n个棋盘都在小于等于h的地方摆放的方案数,再累到答案中去。

    而对于统计其两旁棋盘的方案数,我们可以按照上述方法递归下去。


    于是,这就需要用到笛卡尔树了。

    笛卡尔树是一种特定的二叉树数据结构,可由数列构造,在范围最值查询、范围top k查询(range top k queries)等问题上有广泛应用。它具有堆的有序性,中序遍历可以输出原数列。笛卡尔树结构由Vuillmin(1980)在解决范围搜索的几何数据结构问题时提出。从数列中构造一棵笛卡尔树可以线性时间完成,需要采用基于的算法来找到在该数列中的所有最近小数。

    摘自度娘

    说白了,笛卡尔树就是这样一个东西:

    1.二叉树

    2.若只看权值,则其每个点在权值上形成一个

    3.若只看下标,则其每个点在下标上形成一个二叉搜索树

    它就是长这个样子。

    建树的话,上面已经提到,它可以在(O(n))时间内利用单调栈完成,大致流程如下:(假定我们构建的是小根堆)

    对于新加进一个数x,设栈顶元素为top

    1.如果(x>top),则将top的右儿子设置为x

    2.如果(x le top),则将top弹出,当(top>x)或者栈为空为止,记最后一个弹出的数为la,则将la设置为x的左二子;若栈不为空,则将x设置为top的右儿子

    代码如下:

    for(int i=1; i<=n; i++) {
    	int res=-1;
    	while(head<=tail&&A[i]<=A[Q[tail]])res=Q[tail--];
    	if(res!=-1) {
    		if(rt==res)rt=i;
    		ch[i][0]=res;
    	}
    	if(head<=tail)ch[Q[tail]][1]=i;
    	Q[++tail]=i;
    }
    

    不太懂得人建议先去做一下HDU1506


    回到这道题,我们构建一棵笛卡尔树后,就可以在上面跑树形dp了。

    (dp[x][i])表示在以x为根的子树中,放了i个棋子且放的位置大于(h[fa])的方案数,其中fa表示x的父亲

    那么,转移就直接拿左右儿子合并就好了:

    dp[x][0]=1;
    size[x]=1;
    for(int i=0; i<=1; i++) {
    	int t=ch[x][i];
    	if(t==0)continue;
    	DFS(t,x);
    	for(int i=min(K,size[x]); i>=0; i--) {
    		for(int j=min(K,size[t]); j>=1; j--) {
    			if(i+j>K)continue;
    			if(i+j==0)continue;
    			dp[x][i+j]=(dp[x][i+j]+dp[x][i]*dp[t][j]%MOD)%MOD;
    		}
    	}
    	size[x]+=size[t];
    }
    

    那么,现在我们就要考虑在([h[fa]+1,h[x]])这一区间放棋子的方案数了。

    由于以x为根的子树在序列中对应的都是连续区间,那么它们彼此之间必定互相牵制。

    于是问题就转化成了:

    给定一个(size[x]*(h[x]-h[fa]))的矩阵,在其中放K个棋子,其中每行每列最多只能放一个的方案数(假设(size[x])(h[x]-h[fa])都大于K)。

    那么显然,我们现在(size[x])中选K行,再在(h[x]-h[fa])中选K列,那么一共有(C_{size[x]}^{K}cdot C_{h[x]-h[fa]}^{K})种方案,对于每种方案,棋子又有(K!)种排法,于是一共有(C_{size[x]}^{K}cdot C_{h[x]-h[fa]}^{K} cdot K!)种方案。

    那么,我们在合并后枚举K,然后再转移一波。

    于是,这道题就愉快的A了。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int MOD=1000000007;
    int n,K,rt,head,tail,A[1010],ch[1010][2],Q[1010],dp[1010][1010],size[1010],frac[1001000],inv[1001000],infr[1001000];
    int Quick_Pow(int a,int p){
        int res=1;
        while(p){
            if(p&1)res=res*a%MOD;
            a=a*a%MOD;
            p>>=1;
        }
        return res;
    }
    int C(int x,int y){
        if(x<y)return 0;
        return frac[x]*infr[y]%MOD*infr[x-y]%MOD;
    }
    void DFS(int x,int fa){
        dp[x][0]=1;
        size[x]=1;
        for(int i=0;i<=1;i++){
            int t=ch[x][i];
            if(t==0)continue;
            DFS(t,x);
            for(int i=min(K,size[x]);i>=0;i--){
                for(int j=min(K,size[t]);j>=1;j--){
                    if(i+j>K)continue;
                    if(i+j==0)continue;
                    dp[x][i+j]=(dp[x][i+j]+dp[x][i]*dp[t][j]%MOD)%MOD;
                }
            }
            size[x]+=size[t];
        }
        int cha=A[x]-A[fa];
        for(int i=min(size[x],K);i>=0;i--){
            for(int j=size[x]-i;j>=1;j--){
                if(i+j>K)continue;
                if(i+j==0)continue;
                dp[x][i+j]=(dp[x][i+j]+dp[x][i]*C(size[x]-i,j)%MOD*C(cha,j)%MOD*frac[j]%MOD)%MOD;
            }
        }
    }
    void init(){
        frac[0]=infr[0]=1;
        for(int i=1;i<=1000010;i++)inv[i]=Quick_Pow(i,MOD-2);
        for(int i=1;i<=1000010;i++)frac[i]=frac[i-1]*i%MOD,infr[i]=infr[i-1]*inv[i]%MOD;
    }
    signed main() {
        init();
        scanf("%lld %lld",&n,&K);
        rt=1,head=1,tail=0;
        for(int i=1; i<=n; i++)scanf("%lld",&A[i]);
        for(int i=1; i<=n; i++) {
            int res=-1;
            while(head<=tail&&A[i]<=A[Q[tail]])res=Q[tail--];
            if(res!=-1) {
                if(rt==res)rt=i;
                ch[i][0]=res;
            }
            if(head<=tail)ch[Q[tail]][1]=i;
            Q[++tail]=i;
        }
        DFS(rt,0);
        printf("%lld",dp[rt][K]);
        return 0;
    }
    
  • 相关阅读:
    CodeForces
    hdu4003 树形dp
    hdu2196
    poj2486
    hdu1502 树形dp入门题
    cf 686D
    bzoj2763 分层图
    hdu4424 并查集+贪心+思维
    poj1734 最小环+输出路径
    集训题解1
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11496904.html
Copyright © 2011-2022 走看看