Codeforces 629 C Famil Door and Brackets(dp)

题目链接;
Codeforces 629 C Famil Door and Brackets
题意:
给一个长度为 m 的且只含"(",")"的字符串,可以在这个字符串左右添加长度分别为 p,q 的只含 "(",")" 的字符串使得总长度为 n ,但是要满足:

  • 在这任意n个位置中都要满足前缀串的左括号数量大于等于右括号数量

    • 所有的左括号数量应和右括号数量相等
    • 求总的构造方案数。答案对 1e9+7 取模。
      数据范围: 1mn100000,nm2000
      分析:
      不妨将前缀左括号数量减去右括号数量的差值称为前缀和。那么题意就是任意位置前缀和非负。首先我们可以得到给出的长度为 m 的字符串的最小前缀和MIn和最终的前缀和 tmp ,将 n m的差值记为 diff ,即 diff=nm
      考虑在这个长度为 m 的字符串前面安排长度为q的括号字符串,除了这个长度为 q 的括号字符串自身需要满足在任意位置的前缀和都要大于等于0,还要满足最终的前缀和加上Min大于等于0。
      dp[i][j] 表示长度为 i 的串前缀和为j的方案数,状态转移方程:

      j=0dp[i][j]=dp[i1][j+1]j>0dp[i][j]=dp[i1][j1]+dp[i1][j+1]

      初始化:
      dp[0][0]=1,dp[1][0]=0,dp[1][1]=1,dp[i][j]=0

      因为要满足总串的前缀和为0,如果假设前面长度为 i 的串的前缀和为j,中间长度为 m 的串的前缀和为tmp,那么后面还剩下 nim 个位置并且任意位置的前缀和应是大于等于 (tmp+j) ,但是将最后一个串反过来考虑,其实就是在长度为 nim 的串中的任意前缀右括号的数量减去左括号的数量应大于等于 tmp+j ,这个的方案数和 dp[nim][tmp+j] 应是一样的。最后前后方案数相乘,枚举前面串长度和前面串最终前缀和累加即可。
      时间复杂度: O((nm)2)

      #include <iostream>
      #include <cstdio>
      #include <cstring>
      #include <string>
      #include <algorithm>
      #include <climits>
      #include <cmath>
      #include <ctime>
      #include <cassert>
      #define IOS ios_base::sync_with_stdio(0); cin.tie(0);
      using namespace std;
      typedef long long ll;
      const int MAX_N = 100010;
      const int MAX_M = 2010;
      const ll mod = (ll)(1e9) + 7;
      
      int n, m;
      char s[MAX_N];
      ll dp[MAX_M][MAX_M];
      
      int main()
      {
          while(~scanf("%d%d", &n, &m)) {
              scanf("%s", s);
              int Min = MAX_N, tmp = 0;
              for (int i = 0; i < m; ++i) {
                  if (s[i] == '(') tmp++;
                  else tmp--;
                  Min = min(Min, tmp); 
              }
              int diff = n - m;
              //printf("tmp = %d diff = %d Min = %d\n", tmp, diff, Min);
              if((n & 1) || tmp > diff || -tmp > diff) {
                  printf("0\n");
                  continue;
              }
              memset(dp, 0, sizeof(dp));
              dp[0][0] = 1;
              dp[1][0] = 0, dp[1][1] = 1;
              for (int i = 2; i <= diff; ++i) {
                  for (int j = 0; j <= i; ++j) {
                      if(j == 0) dp[i][j] = dp[i - 1][j + 1];
                      else dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
                      dp[i][j] %= mod;
                  }
              }
              ll ans = 0;
              int st = 0;
              if(Min < 0) st = -Min;
              for (int i = st; i <= diff; ++i) {
                  for (int j = st; j <= i; ++j) {
                      ans = (ans + dp[i][j] * dp[n - i - m][j + tmp] % mod) % mod;
                      //printf("i = %d j = %d ans = %lld\n", i, j, ans);
                  }
              }
              printf("%I64d\n", ans);
          }
          return 0;
      }
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.youkuaiyun.com/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值