12.24模拟赛

T1

树形DP,\(f_{u,1}\) 表示染了 \(k-1\) 种颜色,可以向上连边,\(f_{u,2}\) 表示染了 \(k\) 种颜色,转移式用优先队列维护即可。
一开始总想只用一维表示状态,后悔啊!这启示我们如果难处理的可以多记一维。

code
void dfs(int u, int p) {
    priority_queue<edge> q;
    for (edge &e : G[u]) {
        if (e.v == p) continue;
        dfs(e.v, u);
        q.push({e.v, dp[e.v][1] + e.w - dp[e.v][0]});
    }
    int cnt = 0;
    while (!q.empty()) {
        edge e = q.top(); q.pop();
        if (cnt < k && e.w > 0) dp[u][0] += e.w + dp[e.v][0];
        else dp[u][0] += dp[e.v][0];
        if (cnt < k - 1 && e.w > 0) dp[u][1] += e.w + dp[e.v][0];
        else dp[u][1] += dp[e.v][0];
        cnt++; 
    }
}

T2

一道概率题,我们发现我们只关心不同的颜色有多少种,以及他们的个数,我们并不在意他们是什么颜色,因为他们实质上是一样的。看到 \(k\) 这个数据范围想到矩阵乘法加速,于是考虑构建转移矩阵,具体看代码。

code
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define out() (cout << "sb\n")

constexpr int mod = 1e9 + 7, M = 45;

int n, k, cnt;
ll t;

struct mat {
    ll m[M][M];
    mat() {memset(m, 0, sizeof(m));};
    void init() {
        for (int i = 1; i <= cnt; i++) m[i][i] = 1;
    }
}base, res;
mat operator * (const mat &a, const mat &b) {
    mat c;
    for (int i = 1; i <= cnt; i++) 
        for (int k = 1; k <= cnt; k++) if (a.m[i][k])
            for (int j = 1; j <= cnt; j++)
                (c.m[i][j] += a.m[i][k] * b.m[k][j] % mod) %= mod;
    return c; 
}
mat mpow(mat a, ll b) {
    mat ans; ans.init();
    for (; b; b >>= 1) {
        if (b & 1) ans = ans * a;
        a = a * a;
    }
    return ans;
}
ll qpow(ll a, ll b) {
    ll ans = 1;
    for (; b; b >>= 1) {
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
    }
    return ans;
}
vector<int> temp, v[M];
void dfs(int now, int lst) {
    if (!now) {
        v[++cnt] = temp;
        return;
    } 
    for (int i = lst; i <= now; i++) {
        temp.push_back(i);
        dfs(now - i, i);
        temp.pop_back();
    }
}
int main() {
    // system("fc paint.out temp.out");
    // freopen("paint.in", "r", stdin);
    // freopen("paint.out", "w", stdout);
    IOS;
    cin >> n >> t >> k;
    if (n == k) {
        cout << qpow(qpow(n, mod - 2), t) << "\n";
        return 0;
    } 
    dfs(n, 1);
    for (int i = 1; i <= cnt; i++) {
        ll now = 0;
        for (int &j : v[i]) now += j * j;
        base.m[i][i] = now;
        for (int j = 0; j < v[i].size(); j++) {
            for (int k = 0; k < v[i].size(); k++) if (j != k) { //j->k
                vector<int> tem;
                for (int l = 0; l < v[i].size(); l++) 
                    if (l != j && l != k) tem.push_back(v[i][l]);
                if (v[i][j] != 1) tem.push_back(v[i][j] - 1);
                tem.push_back(v[i][k] + 1);
                sort(tem.begin(), tem.end());
                int pos = 1;
                while (v[pos] != tem) pos++;
                base.m[i][pos] += v[i][j] * v[i][k];
            }
        }
    }
    // for (int i = 1; i <= cnt; i++) 
    //     for (int j = 1; j <= cnt; j++) 
    //         cout << base.m[i][j] << " \n"[j == cnt]; 
    res = mpow(base, t);
    ll ans = 0;
    for (int i = 1; i <= cnt; i++) 
        if (v[i].size() >= k) ans = (ans + res.m[1][i]) % mod;
    // for (int i = 1; i <= cnt; i++) cout << res.m[1][i] << "\n";
    cout << ans * qpow(qpow(n * n, t), mod - 2) % mod << "\n";
    return 0;
}

T3

首先把字符串翻转,\(pre_i\)表示前缀,变成求最小长度 \(len\)使得 \(\forall 1\le i<j\le n\)\(pre_i\)\(pre_j\) 有长度为 \(len\) 的子序列不相同。
我们一次枚举每个 \(i\),考虑用栈维护当前所选的以 \(i\) 为结尾子序列,记录的是位置。 \(last_{ch_i}\) 记录上一个 \(ch_i\) 出现的位置,我们发现如果当前栈顶的下一个位置 \(\ge last_{ch_i}\),那么去掉栈顶,新的一定也是合法的,重复执行上述步骤,最后再加入 \(i\),再统计答案。
这一段讲的有点抽象,一定要结合这代码看:

code
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define out() (cout << "sb\n")

const int N = 3e6 + 5;

string s;
int T, st[N], tp, lst[30], ans = 0;

signed main() {
    cin >> T;
    while (T--) {
        cin >> s;
        int n = s.size();
        reverse(s.begin(), s.end());
        s = " " + s;
		// cout << s << "\n";
        for (int i = 1; i <= n; i++) {
            while (tp && st[tp - 1] >= lst[s[i] - 'a']) tp--; //找最小
            lst[s[i] - 'a'] = i;
            st[++tp] = i; //加入位置
            ans = max(ans, tp);
			// cout << tp << " ";
        }
        cout << ans << "\n";
        ans = tp = 0;
        for (int i = 0; i < 26; i++) lst[i] = 0;
    }
    return 0;
}

总结与展望

  • T1 跟往常一样,试过很多假思路,要1h左右才能写过,往后还要加强快速切题能力。
  • T2 是一道并不难的概率题,但我没怎么做过这种类型,矩阵乘法除了模板和斐波那契题几乎没写过,如果我冷静思考,感觉其实可以做出来,但是我从心底里畏惧去想去写,直接跳过去写 T3,心态这里让我说什么好呢?/wul
  • T3 这道题初看被 \(O(n \log n)\) 的做法限死了,好在在江队的提醒下写出了 \(O(n)\) 做法,以后遇到 \(10^5\) 可不能只想带 \(\log\) 的了。
posted @ 2025-12-26 18:33  lcx_OIer  阅读(5)  评论(0)    收藏  举报