2025 暑期 mx 集训 7.27

T1

https://www.mxoj.net/problem/P110066?contestId=79

题意

\(4\) 种括号,(),[],{},<>

给你一个字符串,由这 \(4\) 种括号和 ? 组成。? 可以变成任意一个字符。

你要求有多少种方案使得字符串是合法的括号序列。

\(n\leq 30\)

Solution

很典(但我赛后才知道很典)。

考虑区间 dp。设 \(f(l,r)\) 表示区间 \([l,r]\) 的方案数。

\[f(l, r) = \sum_{i = l}^{r} val(l, i) \times f(l + 1, i - 1) \times f(i + 1, r) \]

初始化 \(f(i, i - 1) = 1\)

然后就做完了。时间复杂度 \(O(n^3)\)

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

const int N = 40, inf = 0x3f3f3f3f;

int n, b[N], c[N];
string s;
char a[10][2];
ll f[N][N];

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> s; n = s.size();
    s = " " + s;
    a[1][0] = '(', a[1][1] = ')';
    a[2][0] = '[', a[2][1] = ']';
    a[3][0] = '{', a[3][1] = '}';
    a[4][0] = '<', a[4][1] = '>';
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= 4; j++)
            for (int k = 0; k < 2; k++)
                if (s[i] == a[j][k]) b[i] = j, c[i] = k;

    auto val = [&](int i, int j) -> ll {
        int ans = 0;
        if (b[i] == b[j] && !c[i] && c[j]) ans = 1;
        if (b[i] != 0 && !c[i] && b[j] == 0) ans = 1;
        if (!b[i] && b[j] && c[j]) ans = 1;
        if (b[i] == 0 && b[j] == 0) ans = 4;
        return ans;
    };
    for (int i = 1; i <= n + 1; i++) f[i][i - 1] = 1;
    for (int l = 2; l <= n; l++) {
        if (l % 2) continue;
        for (int i = 1, j = i + l - 1; j <= n; i++, j++) {
            for (int k = i; k <= j; k++)
                f[i][j] += val(i, k) * f[i + 1][k - 1] * f[k + 1][j];
        }
    }
    cout << f[1][n];
    return 0;
}

T2

https://www.mxoj.net/problem/P110067?contestId=79

题意

\(n\) 个数,第 \(i\) 个数的取值范围为 \([l_i, r_i]\)

求最长非降连续子序列。

\(n \leq 10^6, -10^9\leq l_i\leq r_i \leq 10^9\)

Solution

首先考虑固定左端点,这个最长的如何求。

那么考虑左端点一定希望尽可能小,那肯定是选择 \(l\)

然后接下来这个数肯定只能选 \([\max(l_{i - 1}, l_i), r_i]\) 了。

然后如果这个区间没了,那就不能选了。

接下来考虑移动左端点。

那随着左端点的后移,这个 \(\max(l_i)\) 肯定是不升的,所以对于你 \(i - 1\) 选的那些数,在 \(i\) 这同样符合。

所以这个满足单调性,直接双指针。然后还需要求区间 \(l_i\)\(\max\),这个随便维护。

时间复杂度 \(O(n \log n)\),可以单调队列做到线性。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10, inf = 0x3f3f3f3f;

int n, x[N], y[N], tr[N << 2];

#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define mid (l + r) / 2

void push_up(int rt) { tr[rt] = max(tr[lson], tr[rson]); }

void build(int rt, int l, int r)
{
    if (l == r) return tr[rt] = x[l], void();
    build(lson, l, mid), build(rson, mid + 1, r);
    push_up(rt);
}

int query(int rt, int l, int r, int sp, int ep)
{
    if (sp <= l && r <= ep) return tr[rt];
    int res = -inf;
    if (sp <= mid) res = query(lson, l, mid, sp, ep);
    if (ep > mid) res = max(res, query(rson, mid + 1, r, sp, ep));
    return res;
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
    build(1, 1, n);
    int ans = 1;
    for (int i = 1, j = 1; i <= n; i++) {
        while (j + 1 <= n) {
            if (y[j + 1] >= query(1, 1, n, i, j)) j++;
            else break;
            ans = max(ans, j - i + 1);
        }
    }
    cout << ans;
    return 0;
}

T3

https://www.mxoj.net/problem/P110068?contestId=79

题意

给你一个 \(n\) 个点的内向树(除了根每个点恰好有一条出边,即每个点向根的方向连),每个点有 \(a_i\) 个人。

初始所有边都是 \(0\),你可以选择连续的一条路径,满足 \(b_{i} \to b_{i + 1}\) 然后删除中间这些边,并添加一条 \(b_1 \to b_m\)\(1\) 边。

最终求 \((a, b)\) 对数的最小值,满足 \(a,b\) 是不同的两个人,且他们位于不同的点上,能够通过若干条边从 \(a\) 到达 \(b\)

\(n \leq 10^5, a_i \leq 10^6\)

Solution

所有边一定不交(端点处可以重合)。

首先考虑我们所有的 \(0\) 边都会替换成 \(1\) 边。

然后每个点的子树内只能有一个点是向上传递的,就是每个点的所有儿子之中,只有其中一个儿子里的点会传到父亲之上,其余的都会连到父亲这里。

所以考虑 \(f(u, i)\) 表示以 \(u\) 为根的子树中 \(i\) 向上传,子树内其余点的最小贡献。

转移考虑枚举这个 \(i\)。(\(\text{sub}(i)\) 表示 \(i\) 的子树)

\[f(u, i) = f(v, i) + \sum_{w \in \text{son}(u) \setminus \{v\} } \min_{j\in \text{sub}(w)} \{ f_{k, j} + a_u a_j\} \]

\[f(u, u) = \sum_{w \in \text{son}(u) } \min_{j\in \text{sub}(w)} \{ f_{k, j} + a_u a_j\} \]

然后第二个式子显然是 \(O(n)\) 的,第一个式子因为对于 \(i\) 只有一个 \(v\) 是符合要求的,然后后面这个 \(\min\) 是可以预处理的,所以也是 \(O(n)\) 的。

总时间复杂度 \(O(n^2)\)。有 \(75\) 分。

然后考虑对于一个 \(j\) 看作一个直线:\(a_j x + f(w, j)\)。然后李超树 + 线段树合并维护。

这个我不会,鸽了。

然后就是还有个问题,我把 \(f(u, u)\) 的转移写了就不对,要去掉,并且所有 \(f\) 初始化为 \(inf\),只有叶子才 \(f(u, u) = 0\)
然后就对了,这个我不太理解。我写出来的错误代码和答案比较是小了的,但是这个写法不应该小。这个写法由于你非叶子点向上传,一定有 \(u \to v, v \to w\) 这种结构,但是你把他直接 \(u \to w\) 是更优的。所以不应该小,但是不懂啊。

ok,因为上边那个东西绝对劣,所以直接不写那个转移就行。然后为啥不对呢,因为这样的话你 \(u\) 下边连向 \(u\) 的边会通过 \(u\) 往上传的这条边连到上边的点。
比如 \(1\to 2, 2\to 3, 2\to 4\),这样你 \(2\) 向上传,那么 \(3,4\) 一定连向 \(2\) 了,然后他们可以通过 \(2\) 去到 \(1\),所以 \((a_3 + a_4) \times a_1\) 这个贡献是漏了的。所以不对。

Code

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 5e3 + 10;

int n, L[N], R[N], cnt;
vector<int> e[N];
int a[N], f[N][N], b[N], mn[N], s[N];

void dfs(int u)
{
    L[u] = ++cnt; b[cnt] = a[u];
    int res = 0;
    for (auto v : e[u]) {
        dfs(v);
        mn[v] = 1e18;
        for (int j = L[v]; j <= R[v]; j++) mn[v] = min(mn[v], f[v][j] + a[u] * b[j]);
        res += mn[v];
    }
    for (auto v : e[u]) 
        for (int i = L[v]; i <= R[v]; i++)
            f[u][i] = f[v][i] + res - mn[v];
    if (!e[u].size()) f[u][L[u]] = 0;
    if (u == 1) cout << res << "\n";
    R[u] = cnt;
}

signed main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 2; i <= n; i++) {
        int x; cin >> x;
        e[x].push_back(i);
    }
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) f[i][j] = 1e18;
    dfs(1);
    return 0;
}

T4

https://www.mxoj.net/problem/P110069?contestId=79

题意

给你 \(n\),将 \(1\sim n\) 按字典序排序,设第 \(i\) 个数为 \(a_i\),求:

\[(\sum_{i = 1}^n (i - a_i) \bmod 998244353) \bmod 10^9 + 7 \]

\(n < 10^{15}\)

Solution

首先可以直接暴力字符串 sort。

然后考虑 dfs 出来的就是字典序,然后记一下这个是第几个即可。\(O(n)\)

后面就鸽了。

posted @ 2025-08-14 20:39  Dtwww  阅读(44)  评论(0)    收藏  举报