bzoj2616: SPOJ PERIODNI——笛卡尔树+DP

不连续的处理很麻烦

导致序列DP又找不到优秀的子问题

自底向上考虑?

建立小根堆笛卡尔树

每个点的意义是:高度是(自己-father)的横着的极大矩形

子问题具有递归的优秀性质

f[i][j]i为根子树,放j个

儿子背包合并

考虑本层的矩形放多少个

枚举一共放t个,本层放j个

对于子树里的放置的t-j个,不论怎么放,一定占据了t-j列,剩下W[i]-(t-j)个位置

转移是:

https://blog.csdn.net/qq_39972971/article/details/79359547

当前节点的:枚举放多少个、占哪些行、占哪些列、具体先后顺序。

代码:

C(n,m)时刻注意n>=0&&m>=0&&n>=m否则<0越界还看不出来调死

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define int long long
#define numb (ch^‘0‘)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch==‘-‘)&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=505;
const int mod=1e9+7;
ll f[N][N];
ll tmp[N];
ll jie[1000000+5],inv[1000000+5];
int qm(int x,int y){
    int ret=1;
    while(y){
        if(y&1) ret=(ll)ret*x%mod;
        x=(ll)x*x%mod;
        y>>=1;
    }
    return ret;
}
int n,k;
int ch[N][2],sz[N],fa[N],h[N];
int sta[N],top;
int a[N];
int build(){
    top=0;
    int las=0;
    for(reg i=1;i<=n;++i){
        las=0;
        while(top&&a[i]<a[sta[top]]){
            las=sta[top];
            --top;
            if(top&&a[sta[top]]>a[i]) ch[sta[top]][1]=las,fa[las]=sta[top];
            else ch[i][0]=las,fa[las]=i;
        }
        sta[++top]=i;
    }
    while(top>1) ch[sta[top-1]][1]=sta[top],fa[sta[top]]=sta[top-1],--top;
    return sta[1];
}
int C(int n,int m){
    if(n<0||m<0||n<m) return 0;
    return (ll)jie[n]*inv[m]%mod*inv[n-m]%mod;
}
void dfs(int x){
//    cout<<" x ff "<<x<<" "<<ff<<endl;
    f[x][0]=1;
    if(!x) return;
    sz[x]=1;
    dfs(ch[x][0]);dfs(ch[x][1]);
    sz[x]+=sz[ch[x][0]]+sz[ch[x][1]];
    h[x]=a[x]-a[fa[x]];
    f[x][0]=1;
    for(reg s=0;s<=1;++s){
        if(!ch[x][s]) continue;
        int y=ch[x][s];
        for(reg j=k;j>=0;--j){
            for(reg t=1;t<=j;++t){
                f[x][j]=(f[x][j]+f[x][j-t]*f[y][t])%mod;
            }
        }
    }
    for(reg i=k;i>=0;--i){
        for(reg j=1;j<=min(min(i,sz[x]),h[x]);++j){
            f[x][i]=(f[x][i]+f[x][i-j]*C(h[x],j)%mod*C(sz[x]-(i-j),j)%mod*jie[j]%mod)%mod;
        }
    }
}
int main(){
    rd(n);rd(k);
    int m=0;
    for(reg i=1;i<=n;++i) rd(a[i]),m=max(m,a[i]);
    m=max(m,max(n,k));
    jie[0]=1;
    for(reg i=1;i<=m;++i) jie[i]=(ll)jie[i-1]*i%mod;
    inv[m]=qm(jie[m],mod-2);
    for(reg i=m-1;i>=0;--i) inv[i]=(ll)inv[i+1]*(i+1)%mod;

    int rt=build();
//    cout<<" rt "<<rt<<endl;
    f[0][0]=1;
    dfs(rt);
    printf("%lld",f[rt][k]);
    return 0;
}

}
signed main(){
//    freopen("data.in","r",stdin);
//    freopen("my.out","w",stdout);
    Miracle::main();
    return 0;
}

总结:
建出笛卡尔树后有优秀的子问题性质

当前矩形的填法可以归为:先找到几行几列变成子正方形,L行L列的正方形的填法就是L!

原文地址:https://www.cnblogs.com/Miracevin/p/10376532.html

时间: 2024-11-02 14:34:40

bzoj2616: SPOJ PERIODNI——笛卡尔树+DP的相关文章

【BZOJ2616】SPOJ PERIODNI 笛卡尔树+树形DP

[BZOJ2616]SPOJ PERIODNI Description Input 第1行包括两个正整数N,K,表示了棋盘的列数和放的车数. 第2行包含N个正整数,表示了棋盘每列的高度. Output 包括一个非负整数,表示有多少种放置的方案,输出答案mod 1000000007后的结果即可. Sample Input 5 2 2 3 1 2 4 Sample Output 43 HINT 对于100%的数据,有 N≤500,K≤500,h[i] ≤1000000. 题解:一看题就感觉应该是单调

NOIP2011pj表达式的值[树形DP 笛卡尔树]

题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例如:计算表达式A⊕B × C时,先计算 B × C,其结果再与 A 做⊕运算. 现给定一个未完成的表达式,例如+(*_),请你在横线处填入数字0 或者1 ,请问有多少种填法可以使得表达式的值为0 . 输入输出格式 输入格式: 输入文件名为exp.in ,共 2 行. 第1 行为一个整数 L,表示给定的表达式中除去横线外的运

BZOJ2616 : SPOJ PERIODNI

长为$A$,宽为$B$的矩阵放$K$个车的方案数$=C(A,K)\times C(B,K)\times K!$. 建立笛卡尔树,那么左右儿子独立,设$f[i][j]$表示$i$子树内放$j$个车的方案数. 合并左右儿子之后,枚举在底部矩形放几个车进行转移即可. 时间复杂度$O(n^3)$. #include<cstdio> const int N=505,M=1000005,P=1000000007; int n,m,i,a[N],mx,fac[M],inv[M],g[N],f[N][N];

HDU - 1506 Largest Rectangle in a Histogram (单调栈/笛卡尔树)

题意:求一个直方图中最大矩形的面积. 很经典的一道问题了吧,可以用单调栈分别求出每个柱子左右两边第一个比它低的柱子(也就相当于求出了和它相连的最后一个比它高的柱子),确定每个柱子的左右边界,每个柱子的高度乘上左右边界的宽度求最大值就行了. 也可以用笛卡尔树上dp的方法搞一搞,即用每个结点权值和所在子树的大小分别表示高度和宽度.(建笛卡尔树的过程也用到了单调栈,作用是维护右链) 单调栈做法: 1 #include<bits/stdc++.h> 2 using namespace std; 3 t

hdu 5412 CRB and Queries(线段树套笛卡尔树 - 动态区间第k大)

题目链接:hdu 5412 CRB and Queries 首先对所有出现过的值排序,建立线段树,每个线段树的节点是一棵笛卡尔树,笛卡尔树记录区间下标值. #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; #define lson(x) (x<<1) #define rson(x) ((x<<

笛卡尔树cartesian tree

笛卡尔树cartesian tree 笛卡尔树是一种特定的二叉树数据结构,可由数列构造,在范围最值查询.范围top k查询(range top k queries)等问题上有广泛应用.它具有堆的有序性,中序遍历可以输出原数列.笛卡尔树结构由Vuillmin(1980)[1]在解决范围搜索的几何数据结构问题时提出.从数列中构造一棵笛卡尔树可以线性时间完成,需要采用基于栈的算法来找到在该数列中的所有最近小数. 定义 无相同元素的数列构造出的笛卡尔树具有下列性质: 结点一一对应于数列元素.即数列中的每

POJ-2201-Cartesian Tree(笛卡尔树)

Description Let us consider a special type of a binary search tree, called a cartesian tree. Recall that a binary search tree is a rooted ordered binary tree, such that for its every node x the following condition is satisfied: each node in its left

[COGS 2421] [HZOI 2016] 简单的Treap 笛卡尔树

笛卡尔树就是你给两维限制,一维堆R,一维二叉搜索树K,平地拔起一棵Treap,最广范的应用:用LCA求区间最值,建Treap,还有个什么范围top k我表示并不会查都查不到.它最妙最高的地方在于用栈来建树:我们可以先排序K然后一个个插入,那么我们都是最右端,横容易被卡,那么我们不从上到下,我们从下到上,用栈维护,那就把时间复杂度从O(n^2)降到O(n),具体过程见下图从图一到图二就是这么一个过程,我们在把K为13的点插入时要找到一个合适的位置,上比他大,下比他小(假设大根堆) 下面见代码 #i

POJ 2559 Largest Rectangle in a Histogram ——笛卡尔树

[题目分析] 本来是单调栈的题目,用笛卡尔树可以快速的水过去. 把每一个矩阵看成一个二元组(出现的顺序,高度). 然后建造笛卡尔树. 神奇的发现,每一个节点的高度*该子树的大小,就是这一块最大的子矩阵的可能解. 用二元组的第一个下标来限制,使它们在一块儿,然后堆的性质又限制了宽度以及高度. 计算,取最值即可. [代码] #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath&g