CH5302 金字塔(区间DP)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/82765238

题目

虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。首先,金字塔由若干房间组成,房间之间连有通道。如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。并且,每个房间的墙壁都涂有若干种颜色的一种。
探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。最后,机器人会从入口退出金字塔。
显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。因为结果可能会非常大,你只需要输出答案对10^9 取模之后的值。

题解

区间DP
容易设计状态表示f[l][r]表示s[l~r]可能对应多少中金字塔方案数。
转移相当棘手!
以区间[l,r]为例,s[l]一定为子树的根节点,s[l+1](有的话)一定为s[l]一个子节点,接下来的子树就是乱七八槽的了。

我们不妨先考虑一个简化版问题,把颜色序列改成编号,即没有两个节点有相同的颜色。
根据DFS序的思想,我们可以得到,当s[l]重复出现时,机器人一定重新回到了根节点。
我们可以把一棵子树拆成 以s[l+1]为根节点的子树 + (以s[l]为根节点的子树 - s[l+1]为根节点的子树)。
写一个solve函数来解决以s[l]为根节点,区间[l,r]的金字塔方案数。
对于每个区间可以枚举一个划分点k,这个k必须使得[k,r](包含k及其右边)是一棵完整的树,即s[k]==s[r];自然的[l+1,k-1]也会是一棵除去根节点s[l]的完整的树。两个子问题均可以用solve函数解决。
对于所有的k(l+2<=k<=r)的方案数要累加,对于两个子问题的答案要相乘。

如果允许颜色相同,最大的影响就是可能会重复计数。
对于样例ABABABA,产生

ABABABA
B  B  B
 \ | /
   A

有可能有两种情况:

   B        ABABA         ABABABA
B            B  B         B  B  B
 \     +     | /     =     \ | /
             A               A

  BAB       AB            ABABABA
B  B            B         B  B  B
 \ |    +      /     =     \ | /
   A         A               A


但是由于我们出色的DP方式,这个问题并不存在。在我们的转移方法中,我们会通过上面的方法得到这种情况。因为对于下面的情况,加号左边不是一棵不包含根的子树。
更抽象地说,我们的不含根子树的颜色序列长度为(k-1)-(l+1)+1,k在不断的增大,意味着这个子树的大小在不断增加。又因为“我们认为子树之间是有序的”,即对于以s[l]为根的所有子树大小(1,2)和(2,1)计入两次,而(1,1,1)计入一次。所以只要第一棵子树的大小在改变,其方案数一定是新的贡献。

下面给出转移方程数学表达式:

f[l][r]=f[l+1][r-1] + \sum_{l+2<=k<=r-2,s[k]=s[r]}^{ }f[l+1][k-1]*f[k][r]

若k=r-1,f[l+1][r-2]\times f[r-1][r]=f[l+1][r-2]\times0=0

若k=r,f[l+1][r-1]\times f[r][r]=f[l+1][r-1]\times1=f[l+1][r-1]

扫描二维码关注公众号,回复: 3482824 查看本文章

综上,我们可以简化代码成:

f[l][r]=\sum_{l+2<=k<=r,s[k]=s[r]} f[l+1][k-1]\times f[k][r]

初始化:

f[i][i]=1

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=310;
const ll mod=1e9;

char s[maxn];int n;
int f[maxn][maxn];

int solve(int l,int r)
{
    if(f[l][r]!=-1) return f[l][r];
    if(l>r) return f[l][r]=0;
    if(l==r) return f[l][r]=1;//初态
    f[l][r]=0;
    for(int k=l+2;k<=r;k++)
        if(s[l]==s[k])
            f[l][r]=(f[l][r]+1ll*solve(l+1,k-1)*solve(k,r)%mod)%mod;
    for(int k=l+2;k<=r;k++)
    return f[l][r];
}

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    memset(f,-1,sizeof(f));
    printf("%d\n",solve(1,n));
    return 0;
}


 

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/82765238