CF2234E 思路分享(笛卡尔树,组合数学)

https://codeforces.com/problemset/problem/2234/E

题意概述

对于一个排列 \(p\),对于它每个位置 \(i\),定义 \(a_i\) 为满足:

  • \(1\le l \le i \le r \le n\)
  • \(\min(p_l,p_{l+1},\cdots,p_r) = p_i\)

\((l,r)\) 对数.

现给定长度为 \(n\) 的序列 \(a\),求能构成 \(a\) 序列的排列 \(p\) 的数量,模 \(10^9+7\).

思路

考虑构建笛卡尔树,\(a_i\) 为(\(i\) 的左子树大小 \(+1\)\(\cdot\)\(i\) 的右子树大小 \(+1\)).

对于位置 \(1\),它没有左孩子,因此右子树的大小可以确定.

因为笛卡尔树子树内序号是连续的,因此 \(1\) 的父节点 \(f\) 可以确定.

然后跳到 \(f\),因为已知 \(1\) 的子树大小,即 \(f\) 的左子树大小,和上述对 \(1\) 的讨论是一致的.

对于每个位置的右子树,进入右子树的序号区间,最左端的位置它的左子树为空,和上面讨论是一致的.

因此可以递归地把笛卡尔树构建出来.

然后考虑如何计数.

对于每个节点 \(u\)\(p_u\) 只能填没选的数中最小的,剩余的数分配给左右子树即可,因此贡献为

\[\binom{sz[left[u]]+sz[right[u]]}{sz[left[u]]} \]

最终答案为所有贡献的乘积.

时间复杂度 \(\mathcal{O}(n)\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

class inverse{
public:
	int n,MOD;
	vector<ll> fac,inv;
	
	ll qpow(ll a,ll b){
		ll res = 1;
		while (b){
			if (b&1) res = res*a%MOD;
			
			a = a*a%MOD;
			b >>= 1;
		}
		
		return res;
	}
	
	inverse(int _n,int _MOD){
		n = _n;
		MOD = _MOD;
		fac = inv = vector<ll>(n+1);
		
		fac[0] = 1;
		for (int i=1;i<=n;i++){
			fac[i] = fac[i-1]*i%MOD;
		}
		
		inv[n] = qpow(fac[n],MOD-2);
		for (int i=n-1;i>=0;i--){
			inv[i] = inv[i+1]*(i+1)%MOD;
		}
	}
	
	ll C(ll a,ll b){
		if (a<b || a<0 || b<0) return 0;
		return fac[a]*inv[b]%MOD*inv[a-b]%MOD;
	}
	
	ll A(ll a,ll b){
		if (a<b || a<0 || b<0) return 0;
		return fac[a]*inv[a-b]%MOD;
	}
	
	ll cal_inv(ll p){
		return qpow(p,MOD-2);
	}
};

const int MOD = 1e9+7;
inverse inv(5e5,MOD);

void solve(){
	int n;
	cin >> n;

	vector<ll> a(n+1);
	for (int i=1;i<=n;i++){
		cin >> a[i];
	}

	vector<int> left(n+1,-1),right(n+1,-1);
	vector<int> sz(n+1);
	bool ok = true;

	function<int(int,int)> build = [&](int l,int r){
		if (l<1 || l>n || r<1 || r>n || l>r || !ok) return -1;

		if (a[l]%(left[l]!=-1?sz[left[l]]+1:1)){
			ok = false;
			return -1;
		}
		ll temp = a[l]/(left[l]!=-1?sz[left[l]]+1:1)-1;
		sz[l] = 1+(left[l]!=-1?sz[left[l]]:0)+temp;
		ll next = l+1+temp;

		if (next>r+1){
			ok = false;
			return -1;
		}

		right[l] = build(l+1,next-1);
		if (!ok) return -1;

		if (next==r+1){
			return l;	
		}		

		left[next] = l;
		return build(next,r);
	};

	int head = build(1,n);
	if (!ok){
		cout << 0 << '\n';
		return;
	}

	ll res = 1;

	function<void(int)> dfs = [&](int u){
		res = res*inv.C((left[u]!=-1?sz[left[u]]:0)+(right[u]!=-1?sz[right[u]]:0),left[u]!=-1?sz[left[u]]:0)%MOD;

		if (left[u]!=-1){
			dfs(left[u]);
		}
		if (right[u]!=-1){
			dfs(right[u]);
		}
	};

	dfs(head);
	cout << res << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-06-11 13:02  kzssCCC  阅读(13)  评论(0)    收藏  举报