Loading

1.12 CW 模拟赛 赛时记录

看题

不是哥们怎么感觉一堆原题但是都不会做
没复习最悲惨的一次

策略肯定还是暴力, 没有什么看上去简单的题

\(\rm{T1}\)

思路

侥幸心理找了一下没有啊, 必须自己想

合法串显然就是满足匹配的串
考虑这种经典问题的常见转化 : 令 (\(1\) , )\(-1\) , 合法括号串仅当其任意前缀 \(\geq 0\) 且全串之和 \(= 0\)

先考虑确定 \(i\) 时怎么计算
首先计算 \(T [i : n]\) 中的前缀, 记录所有串和 \(\leq 0\) 的, \(> 0\) 的必定没有作用
然后考虑 \(T [1 : i]\) 中的后缀, 记录所有串和 \(\geq 0\) 的, \(< 0\) 的必定没有作用
有点感性, 但是应该是这样的

考虑答案合并
\(f_i\) 表示对于 \(T [i : n]\) 中的前缀, 所有串和 \(= -i\) 串串的个数, 令 \(g_i\) 表示对于 \(T [1 : i - 1]\) 中的后缀, 所有串和 \(= i\) 的串串的个数
容易发现 \(g_i, f_i\) 可以组成一个合法串
考虑方案数即为 \(f_i \times g_i\)

感觉就是这样, 手摸下样例

发现一些问题, \(f_i\) 还要新开一维表示串和, 但是显然是做不到的
\(f_{i, j}\) 表示对于所有以 \(i\) 开头的串串, 所有串和 \(= -j\) 串串的个数, 令 \(g_{i, j}\) 表示对于所有以 \(i\) 结尾的串串, 所有串和 \(= j\) 的串串的个数, 考虑怎么做到对于每一个 \(i\) , 快速算出 \(f, g\)

好消息是 \(\mathcal{O} (n^2)\) 能做了

考虑的更深一点, 其实可以对于全串的前后缀做处理进而找到 \(f, g\)
问题变成怎么做才能规避掉对前后缀串和的枚举


理一下

首先是一个基础:
对于 \(i\) 位置结果的统计, 你只需要考虑

  • \(i\) 结尾的后缀中, 串和 \(\geq 0\)
  • \(i\) 开头的前缀中, 串和 \(\leq 0\)

误区在于你还是要统计不满足考虑条件的作为转移, 不然会漏掉情况

那么具体的, 我们设计状态
\(f_{i, j}\) 表示以 \(i\) 结尾的后缀中, 串和为 \(j\)
\(g_{i, j}\) 表示以 \(i\) 开头的前缀中, 串和为 \(j\)

\(f, g\) 的状态转移是容易做到 \(\mathcal{O} (n)\) 的, 考虑 \(i\) 点的答案
容易做到分类讨论

  • 前缀 + \(i\) + 后缀
  • 前缀 + \(i\)
  • \(i\) + 后缀

非常经典的分类, 以后一定要注意不要漏了


但是你发现瓶颈在枚举 \(j\) 上, 怎么办, 必须解决这个问题

这个可能要改一下状态, 或者整体处理
不太会了

实现 \(30 \rm{pts}\)

框架

如上讨论即可

代码

#include <bits/stdc++.h>
#define int long long
const int MAXN = 5206; //41
const int MOD = 1e9 + 7;
namespace calc {
    int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
    int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
    int mul(int a, int b) { return (a * b * 1ll) % MOD; }
    void addon(int &a, int b) { a = add(a, b); }
} using namespace calc;

int n;
std::string bra;
int a[MAXN];
std::map<int, int> f[MAXN], g[MAXN];

signed main()
{
    std::cin >> bra; n = bra.length(); bra = ' ' + bra;
    for (int i = 1; i <= n; i++) a[i] = (bra[i] == '(') ? 1 : -1, f[i][a[i]] = 1, g[i][a[i]] = 1;

    /*转移*/
    for (int i = 1; i <= n; i++) for (int j = -n; j <= n; j++) addon(f[i][j], f[i - 1][j - a[i]]);
    for (int i = n; i >= 1; i--) for (int j = -n; j <= n; j++) addon(g[i][j], g[i + 1][j - a[i]]);

    int ans = 0;
    /*统计答案*/
    for (int i = 1; i <= n; i++) {
        int res = 0;
        for (int j = ((a[i] == 1) ? -1 : 1); j <= n; j++) addon(res, mul(f[i - 1][j], g[i + 1][-j - a[i]]));
        if (a[i] == 1) addon(res, g[i + 1][-a[i]]);
        if (a[i] == -1) addon(res, f[i - 1][-a[i]]);
        addon(ans, mul(res, i));
    }
    printf("%lld", ans);

    return 0;
}

\(\rm{T2}\)

\(\rm{T1}\) 想乱了, 先去想 \(\rm{T2}\) , 回头再看 \(\rm{T1}\)
甚至 \(\rm{T2}\) 也像做过的题

很危险, 赶紧把该拿的分拿了, 不要在死磕
先不管 \(\rm{T1}\)

今天的策略是把后面的题暴力打了死磕 \(\rm{T1}\)

思路

这个题绝对是做过的

原来至少三位数是这样的吗

好吧那不管这道题

\(\rm{T3}\)

目前得分: \(130 \ \rm{pts}\)
虽然那 \(100\) 应该不能算

思路

首先把每一行删除一位之后结果相同的删除选择并到一起
具体的, 如果删除 \(a_i, a_j\) 对于 \(i\) 行的结果一样, 那么把 \(i, j\) 加入同一个集合, 假设第 \(i\) 行对应的操作集合为 \(\mathbb{S}_{i, k}\)

考虑 \(\rm{dp}\) , 令 \(f_{i, j}\) 表示考虑到第 \(i\) 行, 其中这一行删除的位置属于 \(j\) 集合的方案数
考虑转移

\[f_{i, j} \gets \sum_{\mathbb{S}_{i - 1, k} \cap \mathbb{S}_{i, j} \neq \varnothing} f_{i - 1, k} \]

其中并集特殊实现
复杂度 \(\mathcal{O} (n^3)\) , 完全足够了, 开打

总结

\(\rm{T1}\) 思路不够优秀, 没有意识到这一点浪费了太多时间
\(\rm{T3}, \rm{T4}\) 部分分很多, 没有意识到这一点

以后一定要对自己的水平有清楚认知, 做好策略, 然后按照时间分配打下去, 切莫死磕

posted @ 2025-01-12 12:41  Yorg  阅读(15)  评论(0)    收藏  举报