BZOJ 2461: [BeiJing2011]符环

Description

在可以炼制魔力强大的法杖的同时,Magic Land 上的人们渐渐意识到,魔力强大并不一定能给人们带来好处——反而,由此产生的破坏性的高魔力释放,给整个大陆蒙上了恐怖的阴影。

可控的魔力释放,成为了人们新的追求。这种控制魔力释放的技术,也就是被现在的我们熟知的“魔法”。在远古时期,“魔法”由法师们口口相传,但也因为这样,很多“古代魔法”已经成为传说——因为那时没有良好的记录魔法的方法。

后来,天才法师Ferdinand 发现了一种记录魔法的方法:将一种特殊材料做成的正反面均有 1行 N列格子的带子的一端扭转 180度之后与另一端粘贴,这样就得到了一个仅有一面的环,被称为“符环” (Spell Ring) 。
符环上的某一个格子为“起始位”,并标有起始方向,这样,我们就可以给这个环上的每一个格子进行编号: 起始位编号是 0,向起始方向移动一格为 1,这样,一共有 2N 个格子,并且第 i 个格子的背面(虽然带子是一面的,但是仍然有“背面”这个概念)是第(i+N) mod N 格。

法师们将魔法用一个由魔法标记“(”和“)”组成的串表示。人们发现,所有魔法对应的串都为合法的括号序列,并且任何一个合法的括号序列都对应一个魔法。可以发现,合法的括号序列长度均为偶数,这样就可以把一个魔法写在符环之中:从起始格开始,向起始方向,依次写入魔法标记。

这种特殊的材料使得符环带有美丽的色彩:假如一个格子的两面写有相同的魔法标记(即:假设这个带子是透明的,两个魔法标记重合),那么这个格子会变为绯红色(Scarlet) ;反之,若两面的魔法标记不同,会变为深蓝色(Deep blue)。
现在,你得到了一些古代的符环,由于年代久远,魔法标记已经变得模糊不清,但是颜色依然保持完好。你希望知道:给定的颜色信息,对应了多少种不同的魔法?

Input

第一行包含一个正整数T,表示符环的数量。
接下来的 T 行,每一行包含一个符环的颜色信息:
一个长度为 N的由大写字母“S”和“D”组成的字符串。
“S”表示绯红色(Scarlet),“D”表示深蓝色(Deep blue)。
从左到右依次为第 0、1、……、N-1 个格子的颜色。

Output

包含T 行:
每行一个正整数,表示该符环对应的不同魔法的数量。

本题一看数据范围就是dp,但是如何dp呢?首先我们来看题目中的状态;根据题目描述,很显然要对应一个魔法就意味着要有一个方案使得括号全部匹配,S代表前后有两个相同的括号,D代表前后的括号方向不同,且这两个括号的间距为N。
我们先来排除不合法的情况,先看前N个(后N个与前N个一一对应),显然不合法的情况就是在第i个位置之前,右括号数大于左括号数,因为前面的情况都已确定,而后面不管怎样都不可能有左括号与这些右括号匹配。因此就可以dp了。
我们将整个序列分成前后两部分,长度分别为N:
这里写图片描述

假设我们决策到了第i个位置,那么我们的状态可以表示为s1=第一部分中第i个位置之前的左括号数-右括号数,s2=第二部分中的为匹配的左括号数,s3=第二部分中为匹配的右括号数;由于要求第一部分中的左括号数要时刻大于等于右括号数,所以s1显然一直为非负数(否则状态不合法);
状态的转移就要分几种情况():

long long &x=f[i][s1][s2][s3];
if(s[i]=='S') {
  x+=dp(i-1,s1-1,s2-1,s3)+dp(i-1,s1+1,s2+1,s3);
  if(s2==0) x+=dp(i-1,s1+1,s2,s3-1);
}
else {
  x+=dp(i-1,s1-1,s2+1,s3)+dp(i-1,s1+1,s2-1,s3);
  if(s2==0) x+=dp(i-1,s1-1,s2,s3-1);
} x+=1;return x-1;

由于dp时特判很多,所以用记忆化搜索来代替dp方程,,如果状态不合法,直接返回0即可;f[i][s1][s2][s3]表示到当前位置为止(包括当前)的方案数(具体状态见上);
然后我们来看这个状态转移 当s[i]为’S’时,前后的括号方向一样,则分情况讨论:

  1. 若第一部分中当前位置为左括号,则第二部分当前位置为左括号,那么它的前一个状态就是f[i-1][s1-1][s2-1][s3];
  2. 若第一部分中当前位置为右括号,则第二部分当前位置为右括号,那么它的前一个状态就是f[i-1][s1+1][s2+1][s3];特殊的,当s2==0时,前一个状态还可能是f[i-1][s1+1][s2][s3-1];

以上内容自行理解一下,注意左右括号的抵消,还有在什么状态下它们不可能抵消,,s[i]==’D’的情况与其相似,请自行脑补。

还有一个细节那就是x在最后时加了一个1,返回值是x-1;这有什么用呢?我们注意到在函数开始时,我们有一个判断就是当f[i][s1][s2][s3]的值不为0时直接返回,这也是记忆化搜索的特点。但是有很多种状态,它们的值本就是0,显然会被程序当作没有处理过,从而大量计算。所以我们令每个已经计算的状态的值+1,从而使它们都为真,然后在返回时将1减去即可,,正确性十分显然,,还避免了重开数组记录访问情况的大量内存。。
代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long f[50][51][51][51],ans;
int T,l;  char s[60];
long long dp(int i,int s1,int s2,int s3) {
    if(i<0||s1<0||s2<0||s3<0) return 0;
    if(i==0) return f[i][s1][s2][s3];
    if(f[i][s1][s2][s3]) return f[i][s1][s2][s3]-1;
    long long &x=f[i][s1][s2][s3];
    if(s[i]=='S') {
      x+=dp(i-1,s1-1,s2-1,s3)+dp(i-1,s1+1,s2+1,s3);
      if(s2==0) x+=dp(i-1,s1+1,s2,s3-1);
    }
    else {
      x+=dp(i-1,s1-1,s2+1,s3)+dp(i-1,s1+1,s2-1,s3);
      if(s2==0) x+=dp(i-1,s1-1,s2,s3-1);
    } x+=1;return x-1;
}
int main() {
    scanf("%d",&T);
    while(T--) {
      scanf("%s",s); l=strlen(s);
      memset(f,0,sizeof(f));ans=0;int s1=0;
      for(int i=0;i<l;i++) if(s[i]=='S') s1++;if(s1%2) { printf("0\n");continue; }
      if(s[0]=='S') f[0][1][1][0]=1;else f[0][1][0][1]=1;
      for(int i=0;i<=l;i++) ans+=dp(l-1,i,0,i);
      printf("%lld\n",ans);
    }
}

加了个读优又前进了两名 233

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long f[50][51][51][51],ans;
int T,l;  char s[60];
long long dp(int i,int s1,int s2,int s3) {
    if(i<0||s1<0||s2<0||s3<0) return 0;
    if(i==0) return f[i][s1][s2][s3];
    if(f[i][s1][s2][s3]) return f[i][s1][s2][s3]-1;
    long long &x=f[i][s1][s2][s3];
    if(s[i]=='S') {
      x+=dp(i-1,s1-1,s2-1,s3)+dp(i-1,s1+1,s2+1,s3);
      if(s2==0) x+=dp(i-1,s1+1,s2,s3-1);
    }
    else {
      x+=dp(i-1,s1-1,s2+1,s3)+dp(i-1,s1+1,s2-1,s3);
      if(s2==0) x+=dp(i-1,s1-1,s2,s3-1);
    } x+=1;return x-1;
}
void input(char s[]) {
    l=0;char c;while((c=getchar())!='S'&&c!='D');
    s[l++]=c;while((c=getchar())=='S'||c=='D') s[l++]=c;
}
int main() {
    scanf("%d",&T);
    while(T--) {
      input(s);
      memset(f,0,sizeof(f));ans=0;int s1=0;
      for(int i=0;i<l;i++) if(s[i]=='S') s1++;if(s1%2) { printf("0\n");continue; }
      if(s[0]=='S') f[0][1][1][0]=1;else f[0][1][0][1]=1;
      for(int i=0;i<=l;i++) ans+=dp(l-1,i,0,i);
      printf("%lld\n",ans);
    }
}

“`

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值