[NOIP2017模拟]Fantasy Strange Tree

来源:互联网 发布:ubuntu root中文 编辑:程序博客网 时间:2024/06/05 16:18

题目描述
FST 的脑洞非常大,经常幻想出一些奇怪的东西。

某一天,FST 幻想出了一棵没有边际的二叉树,脑补着在那棵二叉树上行走的场景。

FST 一开始在二叉树的根,然后 FST 写下了一个由 ‘L’‘R’ 两种种字符构成的串,他称这个串为初始串,按照这个串的顺序,碰到一个 ‘L’ 就移动到左儿子,碰到一个 ‘R’ 就移动到右儿子。FST 最后的位置就是他的起点。

然后 FST 有写下一个串,他称这个串为操作串,由 ‘U’‘L’‘R’ 三种字符构成,‘U’ 表示移动到当前点的父亲(特殊地,我们定义根节点的父亲为根自己), ‘L’ ‘R’ 同上。

但是 FST 觉得直接按照操作串一步一步走十分无聊,所以 FST 会跳过一些操作(心情不好的时候也可能跳过所有操作,或不跳过),现在 FST 想知道他会走到多少种可能的终点。

由于答案可能很大,所以只需输出答案 mod(10^9+7) 的值。

输入格式
第一行一个由‘L’‘R’两种字符构成的串。
第二行一个由‘U’‘L’‘R’三种字符构成的串。

输出格式
输出一行,为答案 mod(10^9+7)。

样例数据
输入
LL
RU
输出
3

备注
【样例说明】
FST 可以操作的串为 ” ‘R’ ‘U’ ‘RU’。
但是串 ‘RU’ 的效果和串 ” 是一样的,所以只能走到 3 个节点,如下图:
这里写图片描述
(这棵二叉树的大小是无穷的,这里只画出一部分)

【数据范围】
这里写图片描述

分析:
算法一: 构树 30%
我们可以用某种方式把树构出来,然后枚举子序列在树上遍历。
表示出来的树的大小不会超过 220

算法二: 构一部分树 50%
因为在树上遍历的范围只和操作串有关,所以构树时深度只要保留 10 层就行了。

算法三: 特殊点讨论 70%
操作没有 U,也就是只会往下走,那么我们可以 DP
状态有 4 种,没有去过左儿子的点的个数,没有去过右儿子的点的个数,左右儿
子都没去过的点的个数,左右儿子都去过的点的个数。
转移根据这个状态定义应该显然。

算法四: 100%
对于 U 操作,我们只需做一些小修改。
因为 LR操作会和 U 操作抵消,所以我们的实际操作时的使用序列一定是这样的:
UU…UUULRLLRLRLRLLRL…….
也就是 U 操作只会被用在开头,那么处于这个性质,我们可以对初始串(起点)维护一个栈(维护 LR 方向) ,那么每次遇到 U 操作就取出栈顶,判断需要加入哪种新点。
更简单的理解就是,因为可以跳过点,所以每个能走到的点都可以是终点,所以把它们都涂黑。这样,有新操作的时候,就把它们的左/右儿子也涂黑就行(不用建树,dp就行,见代码);而对于找父亲的操作,因为下面的点都是由父亲走到的,所以只用关心最上面的那个点还有没有父亲,可不可以更新(见代码)。

代码:
代码简直清晰明了到爆

#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<algorithm>using namespace std;const int N=1e5+5,p=1e9+7;long long k=1;char t[N];int m,n;long long ans,f[4];//f[1]表示只走过左儿子的点的个数,f[2]表示只走过右儿子的点的个数;// f[0]表示两个儿子都没走过的点的个数,f[3]表示两个儿子都走过的点的个数; int main(){    freopen("fstalways.in","r",stdin);    freopen("fstalways.out","w",stdout);    char c;    for(c=getchar();c>'Z'||c<'A';c=getchar());//保险操作,去空格    for(;c>='A'&&c<='Z';c=getchar())//记录初始串走向        t[++m]=c;    f[0]=1;//跳过所有点,起点也可以是终点,现在它是一个左右儿子都没走过的点    for(c=getchar();c>'Z'||c<'A';c=getchar());//依然保险操作,去空格去回车    for(;c>='A'&&c<='Z';c=getchar())    {        if(c=='U')//读到走父亲        {            if(m)            {                c=t[m--];                if(c=='L')//最上面的点是父亲的左儿子,所以把这个父亲涂黑,加入到只走过左儿子的点中                    f[1]++;                else //右儿子,同理                    f[2]++;                continue;            }            else//如果最上面的点都是根节点了,只能自己往自己走,对答案没有贡献                continue;        }        if(c=='L')//读到走左儿子        {            f[1]=(f[1]+f[0])%p;//所有没有走过儿子的点更新成只走了左儿子的点            f[3]=(f[3]+f[2])%p;//只走了右儿子的点更新成所有儿子都走了的点            f[0]=(f[0]+f[2])%p;//没有走过儿子的点更新成之前没有走过儿子的点左儿子的个数(哎呀有点绞,这些就是加进来新的左儿子,这些左儿子都是没有走过任何儿子的点)加上只走了右儿子的点左儿子个数            f[2]=0;//只走过右儿子的点就没了            continue;        }        if(c=='R')//读到走右儿子,同理走左儿子        {            f[2]=(f[2]+f[0])%p;            f[3]=(f[3]+f[1])%p;            f[0]=(f[0]+f[1])%p;            f[1]=0;            continue;        }    }    for(int i=0;i<4;i++)//四种点加起来就是所有终点        ans=(ans+f[i])%p;    cout<<ans<<'\n';    return 0;}

本题结。

原创粉丝点击