NOIP模拟一

NOIP 模拟一

T1 线团(thread)

题目描述

\(n\) 个小写字母串 \(s_1, s_2, \dots, s_n\)

定义 \(f_{i,j}\) 为从 \(s_1, s_2, \dots, s_i\) 中选出恰好 \(j\) 个串,它们最长的公共前缀长度。一个串的公共前缀为这个串长度本身。

对于 \(i = 1, 2, \dots, n\),求

\[\sum_{j=1}^{i} \left( f_{i,j} \oplus j \right) \]

数据范围与提示

对于所有数据,保证:

  • \(1 \le n \le 5 \times 10^5\)
  • \(\sum_{i=1}^{n} |s_i| \le 10^6\)

解法

这是一道糖糖题, 赛时想出来唐诗 \(O\!\left(\sum_{i=1}^{n} |s_i| \log n\right)\) 的 Trie \(+\) Segment Tree 做法, 但直接Trie就行, 省去一个 \(log\), 每一个节点维护一个 \(siz\), 如果对应 \(siz\)\(dep\) 有变化, 很显然对答案的贡献变化就是这次的减去上次的.

代码

还是一开始最唐的做法

#include <bits/stdc++.h>
#define int long long 
#define pb push_back

using namespace std;

const int N = 5e5 + 10;
int n, siz[N << 1];
string s;
struct SegmentTree{
    int sum, val;
}tr[N << 2];

void push_up(int i){
    tr[i].sum = tr[i << 1].sum + tr[i << 1 | 1].sum;
}

void change(int i, int l, int r, int x, int k){
    if(l == r){
        tr[i].val = max(tr[i].val, k);
        tr[i].sum = tr[i].val ^ l; return; 
    } int mid = (l + r) >> 1;
    if(x <= mid) change(i << 1, l, mid, x, k);
    else change(i << 1 | 1, mid + 1, r, x, k);
    push_up(i);
}

int qry(int i, int l, int r, int x, int y){
    if(x <= l && y >= r) return tr[i].sum;
    int mid = (l + r) >> 1, res = 0;
    if(x <= mid) res += qry(i << 1, l, mid, x, y);
    if(y > mid) res += qry(i << 1 | 1, mid + 1, r, x, y);
    return res;
}
 
struct node{
    signed ch[26][N << 1]; int cnt = 0;
    void update(string s){
        int x = 0, i = 0;
        while(x < (int)s.size()){
            int u = s[x ++] - 'a';
            if(!ch[u][i]){
                ch[u][i] = ++ cnt;
            } i = ch[u][i]; 
            change(1, 1, n, ++ siz[i], x);
        }
    }
}trie;

void build(int i, int l, int r){
    if(l == r){ tr[i].sum = l; return; }
    int mid = (l + r) >> 1;
    build(i << 1, l, mid); build(i << 1 | 1, mid + 1, r);
    push_up(i);
}

signed main(){
    cin.tie(NULL) -> sync_with_stdio(NULL);

    cin >> n; build(1, 1, n);
    for(int i = 1; i <= n; ++ i){
        cin >> s;
        trie.update(s);
        cout << qry(1, 1, n, 1, i) << "\n";
    }

    return 0;
}

T2 红色棋子(red)

题目描述

你有一个长度为 \(n\) 的序列。其中每个位置包含若干个棋子,棋子可能是红色、黄色和蓝色中的一种。保证每个位置至少有一个棋子,并且同一个位置上的棋子的颜色两两不同。

你希望把序列分成若干个连续段。一个段是好的,当且仅当这个段里面所有格子的红色棋子个数大于等于黄色棋子,并且红色棋子个数大于等于蓝色棋子个数。

你想知道,分段之后好的段的总长度最长是多少。

数据范围与提示

对于所有数据,保证:

  • \(1 \le n \le 2 \times 10^6\)
  • \(1 \le s_i \le 7\)

子任务:

  1. (10 分)\(n \le 10\)
  2. (20 分)\(n \le 5000\)
  3. (30 分)\(n \le 2 \times 10^5\)
  4. (40 分)无特殊限制。

解法

赛时只想到了最唐的 \(O(n^2)\) 的做法, 就是令 \(f_i\) 为以 \(i\) 为结尾的最长合法长度, 转移显然为 $$f_i = \max\limits_{j=0}^{n}{f_j + i - j}$$ 其中 \([j + 1, i]\) 是合法区间, 此外还有 \(i\) 不属于任何合法区间的情况, 直接与 \(f_{i - 1}\)\(max\) 即可, 瓶颈在于评定是否合法与取 \(max\) .

Subtask 3

令 $$X_i = \sum_{j = 1}^{i} \left(r_j - y_j\right), Y_i = \sum_{j = 1} ^ {i} \left(r_j - b_j\right) $$
其中 \(r_j\), \(y_j\), \(b_j\) 分别表示当前点红色、黄色、蓝色的数量.
一段区间合法 $ [j + 1, i] $ , 显然有 $$ {X_i - X_j} \geq 0, {Y_i - Y_j} \ge 0$$
所以转移方程即为 $$f_i = \max_{j < i,; X_j \leq X_i,; Y_j \leq Y_i} (f_j + i - j)$$
这个东西显然可以用三位偏序最大值做,时间复杂度为 \(O\!\left(n\log^2n\right)\), (卡卡常能过) .

正解

我们考虑优化掉一些没有必要的转移: 对于 $$X_i - X_j \geq 1 \land Y_i - Y_j \geq 1$$ 的情况, 除非 \(j = 0\), 否则一定能向左扩张, 因为扩张之后一定有 $$X_i - X_j \geq 0 \land Y_i - Y_j \geq 0$$ (题目给定颜色两两不同, 即每种颜色最多出现一次)
所以我们只用考虑 $${X_i = X_j \land Y_i \geq Y_j} \quad\text{or}\quad {Y_i = Y_j \land X_i \geq X_j} $$
这显然可以离散化后用树状数组维护前缀最大值, 时间复杂度为 \(O\!\left(n\log n\right)\) , 空间复杂度为 \(O\!\left(n\right)\) .
据说有线性做法, 可以询问 GPT Astra.

代码

其实可以用动态开点线段树做, 但考虑到常数有点大, 就使用了上文所述的离散化加上树状数组的做法, 但不能用 \(map\) 离散化, 常数实在是太大了, 严肃拿到了两只 \(log\) 的分数, 下面代码贴的是 \(Kimi-K3\) 提供优化, 又由笔者敲一遍的代码.

#include <bits/stdc++.h> 
#define pb push_back

using namespace std;

const int N = 2e6 + 10;
const int inf = 1e9;
int n, X[N], Y[N], cntx, cnty;
int idx[N], idy[N], ax[N], ay[N], f[N];
string s;
vector<int> ux, uy, cx[N], cy[N];

void addx(int rt, int x, int k){ for(; x < (int)cx[rt].size(); x += x & -x) cx[rt][x] = max(cx[rt][x], k); }
void addy(int rt, int x, int k){ for(; x < (int)cy[rt].size(); x += x & -x) cy[rt][x] = max(cy[rt][x], k); }
int qryx(int rt, int x){ int res = -inf; for(; x; x -= x & -x) res = max(res, cx[rt][x]); return res; }
int qryy(int rt, int x){ int res = -inf; for(; x; x -= x & -x) res = max(res, cy[rt][x]); return res; }

signed main(){
    cin.tie(NULL) -> sync_with_stdio(NULL);

    cin >> n >> s;
    ux.resize(n + 1), uy.resize(n + 1);
    for(int i = 1, r, y, b, k; i <= n; ++ i){
        k = s[i - 1] - '0'; b = k % 2; 
        k >>= 1; y = k % 2; r = k >> 1;
        X[i] = r - y + X[i - 1]; Y[i] = r - b + Y[i - 1];
        ux[i] = X[i], uy[i] = Y[i];
    }
    sort(ux.begin() + 1, ux.end());
    sort(uy.begin() + 1, uy.end());
    ux.erase(unique(ux.begin() + 1, ux.end()), ux.end());
    uy.erase(unique(uy.begin() + 1, uy.end()), uy.end());
    cntx = (int)ux.size() - 1, cnty = (int)uy.size() - 1;
    for(int i = 1; i <= n; ++ i){
        idx[i] = lower_bound(ux.begin() + 1, ux.end(), X[i]) - ux.begin();
        idy[i] = lower_bound(uy.begin() + 1, uy.end(), Y[i]) - uy.begin();
        cx[idx[i]].pb(idy[i]); cy[idy[i]].pb(idx[i]);
    }
    for(int i = 1; i <= cntx; ++ i){
        sort(cx[i].begin(), cx[i].end());
        cx[i].erase(unique(cx[i].begin(), cx[i].end()), cx[i].end());
    }
    for(int i = 1; i <= cnty; ++ i){
        sort(cy[i].begin(), cy[i].end());
        cy[i].erase(unique(cy[i].begin(), cy[i].end()), cy[i].end());
    }
    for(int i = 1; i <= n; ++ i){
        ax[i] = lower_bound(cx[idx[i]].begin(), cx[idx[i]].end(), idy[i]) - cx[idx[i]].begin() + 1;
        ay[i] = lower_bound(cy[idy[i]].begin(), cy[idy[i]].end(), idx[i]) - cy[idy[i]].begin() + 1;
    }
    for(int i = 1; i <= cntx; ++ i)
        cx[i].assign((int)cx[i].size() + 1, -inf);
    for(int i = 1; i <= cnty; ++ i)
        cy[i].assign((int)cy[i].size() + 1, -inf);
    for(int i = 1; i <= n; ++ i){
        f[i] = f[i - 1];
        if(X[i] >= 0 && Y[i] >= 0)
            f[i] = i;
        f[i] = max(f[i], i + qryx(idx[i], ax[i]));
        f[i] = max(f[i], i + qryy(idy[i], ay[i]));
        addx(idx[i], ax[i], f[i] - i);
        addy(idy[i], ay[i], f[i] - i);
    } cout << f[n] << "\n";


    return 0;
}
posted @ 2026-09-11 21:39  I_AM_AKer  阅读(9)  评论(0)    收藏  举报