链接:

P7914


题意:

有一堆规则,然后判断给定字符串有多少种填法符合规则。


分析:

一眼区间dp,状态数 \(n^2\),我们来分析这些规则。

把这些规则分成三类,第一类可以预处理出区间是否能表达成全部都是 * 的情况并且其长度小于等于 \(k\),后 \(O(1)\) 判断。第二类可以考虑枚举 \(S\) 的长度,\(O(n)\) 处理。第三类如果枚举分割点显然会算重,依套路我们枚举第一个括号的位置且要保证第一个括号无法被继续分割,考虑在每个区间维护一个无法被分割的方案数,也就是不计第三类的方案,记做 \(dp[l][r][0]\),总方案数记做 \(dp[l][r][1]\),然后再枚举点的长度,\(O(n^2)\) 处理,时间复杂度 \(O(n^4)\)。


优化:

对于第三类的处理,假如我们固定第一个括号的位置,那么需要被计算的其实是一个后缀和。这里我固定除去第一个括号的后面括号的位置,那么就是一个前缀和,两者没有什么区别。大概长这样:

于是我们考虑做一个前缀和,就是 \(sum[l][r]=\sum\limits_{k=l}^r dp[l][i][0]\)

那么第三类的转移只需要处理出前缀和左边的边界就可以 \(O(n)\) 转移了,这个边界会受到三个限制,一是至少要大于等于 \(l\),其次与 \(r\) 的距离会受到 \(k\) 的限制,再是如果前面有一个位置不能表示为 *,显然从这之前的都不能算,所以只需要预处理出一个位置之前最近的不能表示为 * 的位置就好了。然后还有一个细节就是这样做会多减一个没有 * 的情况,加上就好了。

时间复杂度 \(O(n^3)\)


算法:

先预处理出区间是否能表达成全部都是 * 的情况并且其长度小于等于 \(k\),以及一个位置之前最近的不能表示为 * 的位置,然后根据规则 dp 同时维护前缀和就好了。


代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int N=505;
const int mod=1e9+7;
int n,k,sum[N],dp[N][N][2],h[N][N],ex[N][N],last[N];
char s[N];
inline int add(int x,int y){int z=x+y;return (z>=mod)?(z-mod):z;}
inline int mul(int x,int y){int z=x*y;return (z>=mod)?(z%mod):z;}
inline void Add(int &x,int y){x=add(x,y);}
inline void Mul(int &x,int y){x=mul(x,y);}
signed main(){
freopen("bracket.in","r",stdin);
freopen("bracket.out","w",stdout);
cin>>n>>k>>(s+1);
for(int i=1;i<=n;i++)
if(s[i]=='*'||s[i]=='?')
sum[i]=sum[i-1]+1,last[i]=last[i-1];
else
last[i]=i,sum[i]=sum[i-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if((sum[j]-sum[i-1]==j-i+1&&j-i+1<=k)||j<i)
h[i][j]=1;
else h[i][j]=0;
for(int i=1;i<n;i++)
if((s[i]=='('||s[i]=='?')&&(s[i+1]==')'||s[i+1]=='?'))
dp[i][i+1][0]=dp[i][i+1][1]=ex[i][i+1]=1;
for(int i=3;i<=n;i++)
for(int l=1,r=l+i-1,temp=min(r-l-3,k);r<=n;l++,r=l+i-1){
if((s[l]=='('||s[l]=='?')&&(s[r]==')'||s[r]=='?')){
if(h[l+1][r-1])dp[l][r][0]++;
if(l+1<r-1)Add(dp[l][r][0],dp[l+1][r-1][1]);//1
if(s[l+1]=='('||s[l+1]=='?')
for(int t=1;t<=temp;t++)
Add(dp[l][r][0],dp[l+1][r-1-t][1]*h[r-t][r-1]);
if(s[r-1]==')'||s[r-1]=='?')
for(int t=1;t<=temp;t++)
Add(dp[l][r][0],dp[l+1+t][r-1][1]*h[l+1][l+t]);//2
dp[l][r][1]=dp[l][r][0];
for(int t=r-1;t>=l+2;t--){
int ll=max(t-1-k,l);
ll=max(ll,last[t-1]);
Add(dp[l][r][1],mul(dp[t][r][1],add(add(ex[l][t-1]-ex[l][ll],dp[l][ll][0]),mod)));
}//3
}
ex[l][r]=add(ex[l][r-1],dp[l][r][0]);//前缀和
}
cout<<dp[1][n][1];
return 0;
}
题外话:

区间dp做少了。

[CSP-S2021] 括号序列的更多相关文章

  1. 上午小测3 T1 括号序列 && luogu P5658 [CSP/S 2019 D1T2] 括号树 题解

    前 言: 一直很想写这道括号树..毕竟是在去年折磨了我4个小时的题.... 上午小测3 T1 括号序列 前言: 原来这题是个dp啊...这几天出了好几道dp,我都没看出来,我竟然折磨菜. 考试的时候先 ...

  2. BZOJ4350: 括号序列再战猪猪侠

    Description 括号序列与猪猪侠又大战了起来. 众所周知,括号序列是一个只有(和)组成的序列,我们称一个括号 序列S合法,当且仅当: 1.( )是一个合法的括号序列. 2.若A是合法的括号序列 ...

  3. DP专题——括号序列

    毕竟是个渣,写完一遍之后又按LRJ的写了一遍,再写了一遍递归版,最终加上输出解部分 括号序列 定义如下规则序列(字符串): 空序列是规则序列: 如果S是规则序列,那么(S)和[S]也是规则序列: 如果 ...

  4. 【BZOJ】2209: [Jsoi2011]括号序列(splay)

    http://www.lydsy.com/JudgeOnline/problem.php?id=2209 splay又犯逗........upd1那里的sum忘记赋值反............. 本题 ...

  5. 51nod1476 括号序列的最小代价

    这题应该可以用费用流写吧?不过我想不出贪心来TAT.其实还是单调队列乱搞啊T_T //ÍøÉϵÄ̰ÐÄËã·¨ºÃÉñ°¡¡£¡£¡£ÎÒÖ»»áÓÃ×îС·ÑÓÃ×î´óÁ÷ÅÜTAT #in ...

  6. lintcode: 有效的括号序列

    题目: 有效的括号序列 给定一个字符串所表示的括号序列,包含以下字符: '(', ')', '{', '}', '[' and']', 判定是否是有效的括号序列. 样例 括号必须依照 "() ...

  7. uoj #31. 【UR #2】猪猪侠再战括号序列 贪心

    #31. [UR #2]猪猪侠再战括号序列 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://uoj.ac/problem/31 Descript ...

  8. bzoj 1095 [ZJOI2007]Hide 捉迷藏(括号序列+线段树)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1095 [题意] 给定一棵树,树上颜色或白或黑而且可以更改,多个询问求最远黑点之间的距离 ...

  9. CODEVS 3657 括号序列

    [问题描述] 我们用以下规则定义一个合法的括号序列: (1)空序列是合法的 (2)假如S是一个合法的序列,则 (S) 和[S]都是合法的 (3)假如A 和 B 都是合法的,那么AB和BA也是合法的 例 ...

  10. bzoj1095: [ZJOI2007]Hide 捉迷藏 线段树维护括号序列 点分治 链分治

    这题真是十分难写啊 不管是点分治还是括号序列都有一堆细节.. 点分治:时空复杂度$O(n\log^2n)$,常数巨大 主要就是3个堆的初始状态 C堆:每个节点一个,为子树中的点到它父亲的距离的堆. B ...

随机推荐

  1. 完美数java

    完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数.它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身.如果一个数恰好等于它的因子之和,则称该数为&q ...

  2. bash-completion linux命令补全

    1.有时候用docker run 或者kubectl 想tab补全的时候用不了 这个时候可以安装一个神奇的包bash-completion yum install bash-completion 2. ...

  3. 集合遍历数组三种常用方式(Collecton和Map)

    Collection集合遍历数组的三种方式: 迭代器 foreach(增强for循环) JDK1.8之后的新技术Lambda 迭代器: 方法:public Iterator inerator():获取 ...

  4. 动态规划精讲(一)LC 最长递增子序列的个数

    最长递增子序列的个数 给定一个未排序的整数数组,找到最长递增子序列的个数. 示例 1: 输入: [1,3,5,4,7]输出: 2解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, ...

  5. Grid 网格布局详解

    Grid网格布局详解: Grid布局与Flex布局有着一定的相似性,Grid布局是将容器划分成行和列,产生单元格,可以看做是二维布局. 基本概念: 采用网格布局的区域,称为"容器" ...

  6. 配置Orchard Core 最新的包资源

    添加预览包源 在本文中,我们将添加一个指向预览包的新包源. 与从主分支构建的NuGet上的代码相比,每次在dev分支上提交一些代码时都会构建预览包. 它们是最新的版本,但不是最稳定的,可以包含突破性的 ...

  7. Jmeter系列(14)- Setup与tearDown线程组

    与普通线程组区别 #Setup线程组:在普通线程组执⾏前触发 #tearDown线程组:在普通线程组执⾏后触发 线程组属性配置详情完全⼀致 使⽤策略建议 #Setup 线程组 – 压测执⾏准备阶段,准 ...

  8. jenkin—持续集成

    jenkins与持续集成 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能.(百度百科) 持续集 ...

  9. 最详细STL(一)vector

    vector的本质还是数组,但是可以动态的增加和减少数组的容量(当数组空间内存不足时,都会执行: 分配新空间-复制元素-释放原空间),首先先讲讲vector和数组的具体区别 一.vector和数组的区 ...

  10. 现代 C++ 对多线程/并发的支持(上) -- 节选自 C++ 之父的 《A Tour of C++》

    本文翻译自 C++ 之父 Bjarne Stroustrup 的 C++ 之旅(A Tour of C++)一书的第 13 章 Concurrency.用短短数十页,带你一窥现代 C++ 对并发/多线 ...