• 9.28 二叉树计数


    题意

    给定一颗(N)个节点的二叉树并对其标号,标号方法如下:编号为(i)的结点在二叉树的前序遍历中恰好是第(i)个出现

    定义(A_i)表示编号为(i)的点在二叉树的中序遍历中出现的位置

    现在,给出(M)个限制条件,第(i)个限制(<u_i,v_i>)表示(A_{u_i}<A_{v_i}),即中序遍历中(u_i)(v_i)之前出现

    请计算有多少种不同的带标号二叉树满足上述所有限制条件,取模(10^9+7)

    (Nleq 500)


    解法

    在不考虑限制条件的情况下,答案显然就是卡特兰数

    不考虑卡特兰数,尝试用(DP)解决这个问题,首先我们有一个(O(N^3))(DP)

    可以发现对于结点(x),设其左子树的大小为(a),右子树的大小为(b)

    那么其左子树中的编号集合为(x+1 o x+a),右子树为(x+a+1 o x+a+b+1)

    那么我们设(f[x][y])为以(x)为根,大小为(y)的子树的答案

    那么转移就很简单了:我们枚举子树大小后再枚举左子树大小,用乘法原理进行转移

    [f[x][y]=sum_{i=0}^y f[x+1][i] imes f[x+i+1][y-i] ]

    我们在这个(DP)的基础上加上限制,那么就意味着我们只在满足所有限制的情况下进行转移

    观察每一个限制:如果在中序遍历中想要(u)出现在(v)之前,那么只有三种情况:

    • (u)(v)的左子树中
    • (v)(u)的右子树中
    • 对于(u)(v)的最近公共祖先(w)(u)(w)的左子树中,(v)(w)的右子树中

    可以发现子树中的编号都是连续的,这启示我们用类似前缀和的方式查询限制的有无

    由于限制是与两颗子树有关的,我们开一个二维数组记录所有限制,那么查询两颗子树内是否有限制实际上就意味着查询这两颗子树对应编号形成的矩形内是否有值

    这样我们记录一个二维前缀和就可以(O(1))判断了


    代码

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    
    using namespace std;
    
    const int MAX_N = 410;
    const int mod = 1e9 + 7;
    
    int read();
    
    int a[MAX_N][MAX_N];
    
    long long f[MAX_N][MAX_N];
    
    int check(int x1, int x2, int y1, int y2) {
    	return a[x2][y2] - a[x1 - 1][y2] - a[x2][y1 - 1] + a[x1 - 1][y1 - 1];	
    }
    
    long long DFS(int l, int r) {
    	
    	if (l >= r)    return 1;
    	if (~f[l][r])  return f[l][r];
    	
    	long long res = 0;	
    	for (int i = l; i <= r; ++i) {
    		if (check(l, l, l + 1, i) || check(i + 1, r, l, i))  continue;
    		res = (res + DFS(l + 1, i) * DFS(i + 1, r) % mod) % mod;
    	}
    	
    	return f[l][r] = res;
    }
    
    int main() {
    	
    	int T = read();
    	
    	while (T--) {
    		int N = read(), M = read(); 
    		
    		memset(a, 0, sizeof a);
    		memset(f, -1, sizeof f);
    		
    		for (int i = 1; i <= M; ++i) {
    			int u = read(), v = read();
    			a[u][v]++;	
    		}
    		
    		for (int i = 1; i <= N; ++i)
    			for (int j = 1; j <= N; ++j)
    				a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
     		
    		printf("%lld
    ", DFS(1, N));
    	}
    	
    }
    
    int read() {
    	int x = 0, c = getchar();
    	while (!isdigit(c))  c = getchar();
    	while (isdigit(c))	 x = x * 10 + c - 48, c = getchar();
    	return x;
    }
    
  • 相关阅读:
    Cesium原理篇:4Web Workers剖析(2)
    Cesium原理篇:4Web Workers剖析
    Cesium原理篇:3最长的一帧之地形(1)
    Cesium原理篇:2最长的一帧之网格划分
    Cesium原理篇:1最长的一帧之渲染调度
    CSS3火焰文字特效制作教程
    一款非常棒的纯CSS3 3D菜单演示及制作教程
    jQuery/CSS3类似阿里巴巴的商品导航菜单实现教程
    CSS3 3D立方体翻转菜单实现教程
    强大!HTML5 3D美女图片旋转实现教程
  • 原文地址:https://www.cnblogs.com/VeniVidiVici/p/11616220.html
Copyright © 2020-2023  润新知