• 金字塔


    题目:金字塔

    网址:http://noi-test.zzstep.com/contest/0x50「动态规划」例题/5302 金字塔

    描述

    虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。

    经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。

    首先,金字塔由若干房间组成,房间之间连有通道。

    如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。

    并且,每个房间的墙壁都涂有若干种颜色的一种。

    探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。

    这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。

    机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。

    最后,机器人会从入口退出金字塔。

    显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。

    但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。

    现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。

    因为结果可能会非常大,你只需要输出答案对(10^9) 取模之后的值。

    输入格式

    输入仅一行,包含一个字符串S,长度不超过(300),表示机器人得到的颜色序列。

    输出格式

    输出一个整数表示答案。

    输入样例:
    ABABABA
    
    输出样例:
    5
    

    这道题看似一道区间DP,但它包含着树分治的思想。

    具体来说,什么是树分治呢?其实就是说,对于一个操作,它只允许两棵树之间进行,对于多棵树便略显吃力了。那么此时,我们先从最左端的开始,一颗一颗地合并,最终完成了树上的操作。

    不难想到定义(dp[i, j])代表序列([i, j])构成的不同的树的个数。

    这道题,对于一个序列而言,构成子树的充要条件为:序列左右字符一样。

    显然,如果不一样,dp值为0(因为它不可能是一颗完整的树啊)。

    那么,对于可以构成的子树而言,该如何转移呢?这时候,树分治的思想便有作用。

    我们只考虑第一棵子树,枚举第一棵子树的序列,剩下的交给后面的了。事实上,我们可以这样理解:找到k,用(dp[i + 1, k - 1] * dp[k, j])来更新答案。

    为什么这是对的?换句话说,为什么不是用(dp[i + 1, k - 1] * dp[k, j - 1]),那是因为枚举的(k)必须要保证是这棵子树的根节点。换言之,k这位的字符一定要与j的字符保持一致。

    初始化:(dp[i, i] = 1),其余均为0即可!

    当然这道题本身比较贴近树形DP,采取记忆化搜索也很自然。

    递推代码如下:

    #include<iostream>
    #include<cstdio>
    #include<string>
    #include<cmath>
    using namespace std;
    const int maxn = 300 + 5, mod = 1e9;
    int n;
    long long dp[maxn][maxn];
    string S;
    int main()
    {
    
    	cin >> S;
    	n = S.size();
    	for(int i = 0; i < n; ++ i) dp[i][i] = 1;
    	for(int len = 3; len <= n; ++ len)
    	{
    		for(int i = 0, j = len - 1; j < n; ++ i, ++ j)
    		{
    			if(S[i] != S[j]) continue;
    			for(int k = i + 2; k <= j; ++ k)
    			{
    				dp[i][j] = (dp[i][j] + dp[i + 1][k - 1] * dp[k][j]) % mod;
    			}
    		}
    	}
    	printf("%d
    ", dp[0][n - 1] % mod);
    	return 0;
    }
    

    记忆化搜索代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    using namespace std;
    const int maxn = 300 + 5, mod = 1e9;
    int n;
    long long dp[maxn][maxn];
    string S;
    long long dfs(int l, int r)
    {
    	if(dp[l][r] != -1) return dp[l][r];
    	long long &ans = dp[l][r];
    	ans = 0;
    	if(l > r || S[l] != S[r]) return ans;
    	if(l == r) return ans = 1;
    	for(int k = l + 2; k <= r; ++ k)
    	{
    		ans = (ans + dfs(l + 1, k - 1) * dfs(k, r)) % mod;
    	}
    	return ans;
    }
    int main()
    {
    	cin >> S;
    	n = S.size();
    	memset(dp, -1, sizeof(dp));
    	printf("%d
    ", dfs(0, n - 1));
    	return 0;
    }
    
  • 相关阅读:
    RQNOJ 342 最不听话的机器人:网格dp
    RQNOJ 329 刘翔!加油!:01背包
    RQNOJ 57 找啊找啊找GF:01背包
    RQNOJ 202 奥运火炬登珠峰:01背包
    RQNOJ 201 奥运大包围:LIS + 拼链成环
    2017SN多校D1T2 note:dp
    2017SN多校D1T1 loveletter:模拟
    HDU 2157 How many ways??:矩阵快速幂【i到j共经过k个节点的方法数】
    poj 3764 The xor-longest Path
    bzoj 1192 鬼谷子的钱袋
  • 原文地址:https://www.cnblogs.com/zach20040914/p/13215249.html
Copyright © 2020-2023  润新知