EOJ Monthly 2020.11 Sponsored by TuSimple F题“天桥”题解

原题地址
大致题意:给你n个块(n为偶数),要对这n个块进行上色,有k种颜色可以选取,上的颜色需要两两配对并且不能交叉。若第x与y同色,u与v同色,当且仅当x<u<y<v时被认为是交叉。

可以考虑合法的染色数一定可以写成一个合法的括号匹配序列

如 abba 对应 (()) aabb 对应 ()()

但是这并不是唯一对应

如 aaaa 同时对应 (()) 和 ()()

那么显然可以考虑我每次从左到右如果都让未匹配的字母和它右边第一个相同的字母匹配,那么显然一个合法的序列对应唯一的一种括号匹配序列。
那么长度为2*n的合法括号匹配的个数为多少?

括号匹配序列和卡特兰数

设h(i)为长度为2*n的合法括号匹配的数量。
那么 h(0)=1,空括号看成一种方案
显然  h(1)=1: ()
    h(2)=2: ()() (())
对于h(n)我们可以考虑最左边的一定是左括号,那么我们可以枚举与之匹配的第一个右括号的位置,对这个括号的外部和内部填充合法的匹配方案,即可得到递推式:
h ( n ) = ∑ i = 0 n − 1 h ( i ) ∗ h ( n − 1 − i ) h(n)=\sum_{i=0}^{n-1}h(i)*h(n-1-i) h(n)=i=0n1h(i)h(n1i)
这正是卡特兰数的递推式
所以长度为2*n的合法括号匹配数为Catalan(n)
参考资料:
卡特兰数-百度百科

接下来考虑对括号进行染色

下文用“第一个括号”代表左括号在序列最左边的那对括号

上面卡特兰数的这种递推思想非常值得学习
设f(i)为长度为2*i的合法序列数。
f(0)=1
f(1)=k;
考虑和上面一样枚举第一个括号的位置,然后在其中填入之前已经算出来的合法序列。但是这样会出现以下问题,假设当前的n=4,k=2:
f(1)=2 :aa bb
计算f(2)的时候考虑第一个括号的颜色是a并且其右括号在第二个位置时有aaaa aabb

第一个括号的颜色是a并且其右括号在第四个位置时有
aaaa abba

aaaa被重复计数了!
这种情况其实是由于在一个合法序列的最外层加一层括号时改变了其内部本来的匹配方法,例如:
aa bb aa

在其外部加上bb之后变成
b a a b b a a b

那么考虑在外层加什么颜色的括号可以防止这种事情发生?
加原来序列里没有的颜色?不对。这样又缩小了范围,比如:
baab在其外部加上aa并不会影响其匹配关系。
如果当前加上的颜色和这个序列本身最外层括号的颜色都不同,那么就不会影响原来的匹配方法。
最外层括号: (()())() 的最外层括号为(____)()
回到刚刚的枚举过程,在枚举了第一个括号的位置后在其内部放入最外层括号的颜色都与第一个括号颜色不同的序列,然后其后面放正常的合法序列即可。
定义g(i)为长度为2*i 并且最外层括号不为某种特定颜色的方案数
g(0)=1
显然由上面的描述可以得到递推式为
f ( n ) = ∑ i = 0 n − 1 k ∗ g ( i ) ∗ f ( n − 1 − i ) f(n)=\sum_{i=0}^{n-1}k*g(i)*f(n-1-i) f(n)=i=0n1kg(i)f(n1i)

如何求g(n)?

同样考虑上文的枚举方法,枚举第一个扩号的右括号的位置,那么此时又会遇到和上面相同的问题,需要保证第一个括号内部的最外层的括号的颜色都与第一个括号不同。
正好就是g(i )的定义!!!
这需要深入理解g的定义,明白此时的“不为某种特定的颜色”和填在第一个括号外面的“不为某种特定的颜色”指的不是同一个颜色,整个g指的是不为将要在其外面加的括号的颜色相同,而在第一个括号内部的是指不和第一个括号相同,虽然指的不是同一个颜色但是显然数量上是相等的。
读者可手动模拟演算,帮助理解!
于是就有了g的递推式:
g ( n ) = ∑ i = 0 n − 1 ( k − 1 ) ∗ g ( i ) ∗ g ( n − 1 − i ) g(n)=\sum_{i=0}^{n-1}(k-1)*g(i)*g(n-1-i) g(n)=i=0n1(k1)g(i)g(n1i)

代码如下

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll g[1505],f[1505];
const ll mod=1e9+7;
int main()
{
    ll n,k;
    cin>>n>>k;
    n>>=1;
    g[0]=f[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=i-1;j++)
        {
            g[i]=(g[i]+(k-1)*g[j]%mod*g[i-1-j]%mod)%mod;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=i-1;j++)
        {
            f[i]=(f[i]+k*g[j]%mod*f[i-1-j]%mod)%mod;
        }
    }
    cout<<f[n]<<'\n';
    return 0;
}

这是本人写的第一篇博客,如果对内容有疑问欢迎指出,也欢迎和我交流讨论共同进步。

最后引用李煜东老师的一句话勉励自己:

在思维的迷宫里,有的人凭借天生的灵感直奔终点;有的人以持久的勤勉,铸造出适合自己的罗盘;有的人迷失了方向,宣告失败。

  • 11
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值