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\),求
数据范围与提示
对于所有数据,保证:
- \(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\)。
子任务:
- (10 分)\(n \le 10\)。
- (20 分)\(n \le 5000\)。
- (30 分)\(n \le 2 \times 10^5\)。
- (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;
}

浙公网安备 33010602011771号