CF gym102501 J. Counting Trees(Catalan数、dfs/单调队列)

https://codeforces.com/gym/102501/problem/J
题意:给定一棵二叉树的中序遍历,问这棵树的形态可能有多少种。另外符合要求的树应满足:树上任意结点的权值大于等于其儿子结点的权值。

思路1:先选出序列中最小的几个数作为树的“根”,“根”的状态数就是它结点个数的Catalan数
在这里插入图片描述

如上图所示,“根”的状态确定后,被分出来的的几棵子树的位置相应也确定了,然后对子树重复进行前面的操作算出子树的状态数。最后将“根”、所有子树的状态数做乘积就是所求答案

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
//#define int long long
#define ll long long
#define uql unsigned long long
#define pii pair<int,int>
#define mid ((l + r)>>1)
#define chl (root<<1)
#define chr (root<<1|1)
#define lowbit(x) ( x&(-x) )
const int manx = 1e6 + 10;
const int manx2 = 4e7 + 10;
const int INF = 1e9;
const int mod = 1000000007;

int n,a[manx],Log2[manx];
int dp[manx][20],afterx[manx],pos[manx];
ll jiec[manx<<1];
void getLog2()
{
    for(int i=1,j=0;i<=n;i++){
        if(1<<(j+1)<=i)j++;
        Log2[i]=j;
    }
}
void init()
{
    getLog2();
    memset(pos,-1,sizeof pos);
    for(int i=0;i<=n;i++)dp[i][0]=i;//pos[i]=-1!!!
    for(int i=1;i<=Log2[n];i++)
        for(int j=1;j+(1<<i)-1<=n;j++){
            int x1=dp[j][i-1],x2=dp[j+(1<<(i-1))][i-1];
            if(a[x1]<a[x2])dp[j][i]=x1;
            else if(a[x1]>a[x2])dp[j][i]=x2;
            else dp[j][i]=x1<x2?x1:x2;
        }
    for(int i=n;i>=1;i--){
        afterx[i]=pos[a[i]];
        pos[a[i]]=i;
    }
    jiec[0]=1;
    for(int i=1;i<=2*n;i++)jiec[i]=jiec[i-1]*i%mod;
}
int query(int l,int r)
{
    int k=Log2[r-l+1];
    int x1=dp[l][k],x2=dp[r-(1<<k)+1][k];
    if(a[x1]<a[x2])return x1;
    else if(a[x1]>a[x2])return x2;
    else return x1<x2?x1:x2;
}
ll q_pow(ll a,int b)
{
    ll ans=1;
    while(b){
        if(b&1)
            ans*=a,ans%=mod;
        a*=a,a%=mod;
        b>>=1;
    }
    return ans;
}
ll dfs(int l,int r)
{
    if(l>=r)return 1;
    int per=l-1,p=query(l,r),num=0;
    ll ans=1;
    for(;~p&&p<=r;){
        ans*=dfs(per+1,p-1),ans%=mod;
        per=p;
        p=afterx[p];
        num++;
    }
    if(per<r-1)ans*=dfs(per+1,r),ans%=mod;
    return ans*jiec[2*num]%mod*q_pow(jiec[num]*jiec[num+1]%mod,mod-2)%mod;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    init();
    printf("%lld\n",dfs(1,n));
    return 0;
}

思路2:在前面的递归过程中,因为我们不断选出最小的数,所以第i个数的贡献只与区间 [ m a x { j ∣ 1 ≤ j ≤ i − 1 , a j < a i } , m i n { j ∣ i + 1 ≤ j ≤ n , a j < a i } ] [max\{j|1≤j≤i-1,a_j<a_i\},min\{j|i+1≤j≤n,a_j<a_i\}] [max{j1ji1,aj<ai}min{ji+1jn,aj<ai}]中数值等于 a i a_i ai的数的个数有关。(向左、向右第一个大于 a i a_i ai的数的位置),而这个个数可以在单调队列中维护。

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
//#define int long long
#define ll long long
#define uql unsigned long long
#define pii pair<int,int>
#define mid ((l + r)>>1)
#define chl (root<<1)
#define chr (root<<1|1)
#define lowbit(x) ( x&(-x) )
const int manx = 1e6 + 10;
const int manx2 = 4e7 + 10;
const int INF = 1e9;
const int mod = 1000000007;

int n,head,tail,a[manx];
int qu[manx],num[manx];
ll jiec[manx<<1];
void init()
{
    jiec[0]=1;
    for(int i=1;i<=2*n;i++)jiec[i]=jiec[i-1]*i%mod;
}
ll q_pow(ll a,int b)
{
    ll ans=1;
    while(b){
        if(b&1)
            ans*=a,ans%=mod;
        a*=a,a%=mod;
        b>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    a[n+1]=-1;
    init();
    ll ans=1;
    head=1,tail=0;
    for(int i=1;i<=n+1;i++){
        while(tail>=head&&qu[tail]>a[i]){
            int x=num[tail];
            ans*=jiec[x*2]*q_pow(jiec[x]*jiec[x+1]%mod,mod-2)%mod;
            ans%=mod;
            tail--;
        }
        if(tail>=head&&qu[tail]==a[i])num[tail]++;
        else qu[++tail]=a[i],num[tail]=1;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值