"蔚来杯"2022牛客暑期多校训练营2 题解
F. NIO with String Game
对所有询问离线,并对q次询问后的所有T串建立Trie树,发现若某个串的字典序大于另一个串,则其在Trie上的DFS序大于另一个串。
DFS求得DFS序后建立线段树或树状数组,对每个T串,将所有DFS序大于其的节点+1。
对于S串,我们可以利用倍增法在Trie树上找到对应节点,
当删除末尾字符时,我们直接倍增往上跳,
当增加末尾字符时,我们设\(nxt[i][k]\)为节点 \(i\) ,往下沿着入边对应的字母跳 \(2^k\) 次能跳到哪个节点,倍增往下跳即可。注意判断S串不在Trie上的情况。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pir make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 8e5 + 5;
int n, q, cnt = 1, idx, id[MAXN], pos[MAXN], last[MAXN], sze[MAXN], trie[MAXN][40], fa[MAXN][40], nxt[MAXN][40], sum[MAXN];
string S, T[2][MAXN];
pii s;
char firstch;
struct Query {
int op, k;
char c;
}a[MAXN];
void Modify(int x, int k, int n = idx) {
while(x <= n) sum[x] += k, x += (x & (-x));
}
int Query(int x, int n = idx) {
int res = 0;
while(x) res += sum[x], x -= (x & (-x));
return res;
}
void DFS(int u, int f = 0) {
if(!u) return ;
fa[u][0] = f, id[u] = ++idx, sze[u] = 1;
for(int i = 1; i <= 30; i++) fa[u][i] = fa[ fa[u][i - 1] ][i - 1];
for(int i = 0; i < 26; i++) DFS(trie[u][i], u), sze[u] += sze[ trie[u][i] ];
}
void Up(int k) {
int minn = min(k, s.se); s.se -= minn, k -= minn;
if(k && s.se == 0) {
for(int i = 30; i >= 0; i--) if((k >> i) & 1) s.fi = fa[s.fi][i];
}
}
void Down(int k, char c) {
if(!k) return ;
if(s.se) s.se += k;
else {
if(!trie[s.fi][c - 'a']) s.se = k, firstch = c;
else {
s.fi = trie[s.fi][c - 'a'], k--;
for(int i = 30; i >= 0; i--) {
if(k >= (1 << i) && nxt[s.fi][i]) k -= (1 << i), s.fi = nxt[s.fi][i];
}
if(k) s.se = k, firstch = c;
}
}
}
void Insert(int x) {
int u = 1;
for(auto i : T[0][x]) {
if(!trie[u][i - 'a']) trie[u][i - 'a'] = ++cnt;
u = trie[u][i - 'a'];
}
pos[x] = u;
for(auto i : T[1][x]) {
if(!trie[u][i - 'a']) trie[u][i - 'a'] = ++cnt;
u = trie[u][i - 'a'];
}
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> q >> S;
for(int i = 1; i <= n; i++) cin >> T[0][i];
for(int i = 1; i <= n; i++) pos[i] = last[i];
for(int i = 1; i <= q; i++) {
cin >> a[i].op;
if(a[i].op == 1) cin >> a[i].k >> a[i].c, T[1][ a[i].k ].push_back(a[i].c);
else if(a[i].op == 2) cin >> a[i].k;
else if(a[i].op == 3) cin >> a[i].k >> a[i].c;
}
for(int i = 1; i <= n; i++) Insert(i);
DFS(1);
for(int i = 1; i <= cnt; i++) for(int j = 0; j < 26; j++) nxt[ trie[i][j] ][0] = trie[ trie[i][j] ][j];
for(int k = 1; k <= 30; k++) for(int i = 1; i <= cnt; i++) nxt[i][k] = nxt[ nxt[i][k - 1] ][k - 1];
s = {1, 0};
for(auto i : S) {
if(s.se || trie[s.fi][i - 'a'] == 0) {
if(!s.se) firstch = i;
++s.se;
} else s.fi = trie[s.fi][i - 'a'];
}
for(int i = 1; i <= n; i++) Modify(id[ pos[i] ], 1);
for(int i = 1; i <= q; i++) {
int op = a[i].op, k = a[i].k, c = a[i].c;
if(op == 1) {
Modify(id[ pos[k] ], -1);
pos[k] = trie[ pos[k] ][c - 'a'];
Modify(id[ pos[k] ], 1);
} else if(op == 2) {
Up(k);
} else if(op == 3) {
Down(k, c);
} else {
int res;
if(s.se) {
res = id[s.fi];
for(int j = firstch - 'a' - 1; j >= 0; j--) {
if(trie[s.fi][j]) { res = id[ trie[s.fi][j] ] + sze[ trie[s.fi][j] ] - 1; break; }
}
res = Query(res);
} else res = Query(id[s.fi] - 1);
cout << res << "\n";
}
}
return 0;
}
G. Link with Monotonic Subsequence
发现答案下界为 \(\lceil \sqrt{n} \rceil\),我们先将序列升序排序,再分为一个个长度为 \(\lceil \sqrt{n} \rceil\) 的块(不足 \(\lceil \sqrt{n} \rceil\) 的部分保留),对每个块降序排序即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5;
const int MOD = 1e9 + 7;
int n, m, p[MAXN];
string S;
void Solve() {
cin >> n;
if(n == 3) {
cout << "1 3 2\n";
return ;
}
int S = ceil( 1.0 * sqrt(n) );
for(int i = 0; ; i++) {
int l = i * S + 1, r = min((i + 1) * S, n);
for(int j = l; j <= r; j++) p[j] = j;
reverse(p + l, p + r + 1);
if(r == n) break;
}
for(int i = 1; i <= n; i++) cout << p[i] << " "; cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
H. Take the Elevator
分别考虑需要乘坐电梯上升或下降的人的情况,我们可以枚举出共有多少人需要乘坐电梯到每一层。
从高往低枚举每一层,如果当前电梯运行次数不够满足所有人到某一层的需求则增加电梯运行的次数,并记录当前层数,直到电梯运行的次数能满足所有人到某一层的需求。
经过上述过程后,我们可以知道电梯上升或下降时,电梯每次需要到达的最高层和运行次数,对上升或下降的最高层排序后枚举比较两者最大值,并将其加入答案即可。
注意k的值域很大,需要离散化。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
#define pir make_pair
using namespace std;
const int MAXN = 4e5 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n, m, k, a[MAXN >> 1], b[MAXN >> 1], val[MAXN];
vector<pii> Up, Down;
vector<int> UpCnt, DownCnt;
map<int, int> Map;
bool cmp(pii a, pii b) {
if(a.fi == b.fi) return a.se < b.se;
return a.fi > b.fi;
}
signed main()
{
//ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m >> k;
for(int i = 1; i <= n; ++i) {
int u, v; cin >> u >> v;
if(u < v) Up.push_back( {u, -1} ), Up.push_back( {v, 1} );
else Down.push_back( {u, 1} ), Down.push_back( {v, -1} );
}
sort(Up.begin(), Up.end(), cmp);
sort(Down.begin(), Down.end(), cmp);
int cnt = 0, UpCntSize = 0, DownCntSize = 0;
for(auto i : Up) {
cnt += i.se;
if(cnt > UpCntSize * m) UpCnt.push_back(i.fi), UpCntSize++;
}
for(auto i : Down) {
cnt += i.se;
if(cnt > DownCntSize * m) DownCnt.push_back(i.fi), DownCntSize++;
}
int maxSize = max(UpCntSize, DownCntSize);
while(UpCntSize < maxSize) UpCnt.push_back(0), UpCntSize++;
while(DownCntSize < maxSize) DownCnt.push_back(0), DownCntSize++;
sort(UpCnt.begin(), UpCnt.end());
sort(DownCnt.begin(), DownCnt.end());
int ans = 0;
for(int i = 0; i < maxSize; i++) ans += 2ll * (max(UpCnt[i], DownCnt[i]) - 1);
cout << ans << "\n";
return 0;
}
K. Link with Bracket Sequence I
设 \(f[i][j][k]\) 表示b串枚举到 \(i\),能匹配到a串前 \(j\) 个字符,当前有 \(k\) 个左括号需要匹配。有
\[f[i][j][k] = f[i - 1][j - 1][k - (S[k] == '(')] + f[i - 1][j + 1][k - (S[k] == ')']
\]
根据样例可知 \(n,m\) 为200的情况最多有5组,其复杂度为\(O(5nm^2)\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 205;
const int MOD = 1e9 + 7;
int n, m, f[MAXN][MAXN][MAXN];
string S;
void Solve() {
cin >> n >> m >> S; S = " " + S;
f[0][0][0] = 1;
for(int i = 1; i <= m; i++) {
for(int j = 0; j <= i; j++) {
for(int k = 0; k <= n; k++) {
if(j) f[i][j][k] = f[i][j][k] + f[i - 1][j - 1][k - (S[k] == '(')];
f[i][j][k] = f[i][j][k] + f[i - 1][j + 1][k - (S[k] == ')')];
f[i][j][k] %= MOD;
}
}
}
/*
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= m; j++) {
for(int k = 0; k <= n; k++) {
printf("%lld %lld %lld : %lld\n", i, j, k, f[i][j][k]);
}
}
}
*/
cout << f[m][0][n] << "\n";
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= m; j++) {
for(int k = 0; k <= n; k++) {
f[i][j][k] = 0;
}
}
}
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}

浙公网安备 33010602011771号