2025/7/1 cw模拟赛总结
T1
小清新性质 + 双指针。
贪心的想,每次尽量填满 \(m\) 的容量一定是优的,考虑 \(\max{a_i}\) 和 \(\min{a_i}\) 配对。
其次,基础的思考时间为 \(\sum{a_i}\),那么我们只需要算因为无法同时容纳两个题的情况多出来的放弃时间。那么先排序然后双指针做,对于 \(a_l + a_r > m\) 的情况就会多出 \(1\) 的放弃 \(a_r\) 的时间(单独思考)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int Test, n, m, a[200010];
void solve() {
int ans = 0;
scanf ("%lld %lld", &n, &m);
for (int i = 1; i <= n; i ++) {
scanf ("%lld", &a[i]), ans += a[i];
}
stable_sort (a + 1, a + n + 1);
for (int l = 1, r = n; l <= r; ) {
ans ++;
while (l <= r) {
if (a[l] + a[r] > m) {
r --; break;
}
l ++, r --;
}
}
printf ("%lld\n", ans);
}
signed main() {
scanf ("%lld", &Test);
while (Test--) solve();
return 0;
}
T3
考虑如何构造字典序最小,贪心的想你发现我们想让左括号尽量向前靠,右括号则尽量不让它向前靠。
那么考虑删除 \((i,j)\) 这对匹配的括号,必然会与 \(i + 1 \sim j - 1\) 之间的括号有影响。假设对于这个例子: \(\texttt{)(()(()))}\),考虑删除红色部分:\(\color{blue}{)(} \color{red}{()} \color{blue}{(()))}\),那么后面的左括号会前移,字典序变小,括号串变成了 \(\texttt{)((()))}\),无论删除哪一对配对括号,都不能使答案更优。即出现匹配括号对套匹配括号对的情况:\(\texttt{(())}\),这种不用考虑。
现在可以有 \(O(n^2)\) 的 dp,记 \(f_{i}\) 表示后缀字典序最小的子串,同时记 \(to_i\) 表示与 \(i\) 匹配的右括号位置。
如果 \(to_i\) 不存在,则 \(f_i \leftarrow s_i + f_{i+1}\);如果 \(to_i\) 存在,则 \(f_i \leftarrow \min(f_{to_i + 1}, s_i + f_{i+1})\)。
考虑进行优化,你发现记 \(g_i\) 表示 \(f_i\) 的这个串的开头位置,那么考虑一个树上面插着 \(f_i\),那么 \(fa_i = g_{i+1}\)。我们需要做的事情就是比较从 \(g_i \to root\) 和 \(g_{to_i + 1} \to root\) 的两个串的字典序进行 dp 转移,因为是模拟在树上操作可以用倍增维护哈希比较字典序。
#include <bits/stdc++.h>
using namespace std;
constexpr int base = 233, N = 1000010, Mod = 1e9 + 7;
char s[N];
int stk[N], top;
int n, p[N][23], hsh[N][23], f[N], pw[N], to[N];
signed main() {
scanf ("%s", s + 1);
n = strlen(s + 1), pw[0] = 1;
for (int i = 1; i <= n; i ++) {
pw[i] = pw[i - 1] * base % Mod;
}
for (int i = 1; i <= n; i ++) {
if (s[i] == '(') stk[++top] = i;
else if (top > 0) to[stk[top--]] = i + 1;
}
p[n + 1][0] = f[n + 1] = n + 1;
for (int i = n; i >= 1; i --) {
p[i][0] = f[i + 1];
hsh[i][0] = s[i], f[i] = i;
for (int j = 1; j <= 20; j ++) {
p[i][j] = p[p[i][j - 1]][j - 1];
hsh[i][j] = (hsh[i][j - 1] + hsh[p[i][j - 1]][j - 1] * pw[1 << (j - 1)] % Mod) % Mod;
}
if (to[i] != 0) {
int x = i, y = f[to[i]];
for (int j = 20; j >= 0; j --) {
if (p[x][j] && p[y][j]) {
if (hsh[x][j] == hsh[y][j]) {
x = p[x][j], y = p[y][j];
}
}
}
if (s[x] > s[y] || y == n + 1) f[i] = f[to[i]];
}
}
int x = f[1];
for (; x <= n; x = p[x][0]) putchar(s[x]);
return 0;
}
T4
妙妙奇奇妙奇妙妙奇奇妙的 AC 自动机及其神秘秘秘神神秘秘神秘秘神的优化?
看到题发现是类似某些子串不可取,本质上是子串匹配,那你考虑放到 AC 自动机上做。
考虑一个 dp,记 \(f_{u,x}\) 表示在原图上点 \(u\),ACAM 上对应点 \(x\) 的最短路径长度。那么只需要对串尾打 \(flag\) 标记,如果点 \(v\) 不存在 \(flag\) 标记则 \((u,v)\) 可以直接松弛。
考虑优化,你发现我们关心的是既出现在 ACAM 上也出现在原图上的点,即如果 \(e(u,v)\) 在原图上存在且在 ACAM 上存在那么这条边会产生贡献。故对于 ACAM 上的点我们可以映射到原图,考虑对 ACAM 的每个点用主席树维护,\(root_u\) 即为 \(u\) 映射到原图中的点,其主席树叶子表示其出边。
如果 \(fail_u\) 不存在,直接枚举原图对应出边单点修改;如果存在,则枚举 ACAM 上的儿子单点修改。对于 \(fail_{son_u}\) 的更新依旧是在主席树上查询。
考虑如何优化松弛,只需要避免每条边重复松弛即可。那么我们直接 dfs 主席树,对于已经办掉的子树直接不管了,没有被办掉的就拿叶子节点松弛(叶子存出边和边权)。
如果能够走到 \(n\) 则输出堆顶,否则无解。
#include <bits/stdc++.h>
#define int long long
#define mkp make_pair
#define pii pair<int,int>
namespace FastIO {
constexpr int SZ = (1LL << 23);
static int stk[33];
char buf[SZ], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, SZ, stdin), p1 == p2) ? EOF : *p1++)
inline int read() {
int res = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
f = ch == '-' ? -1 : 1, ch = getchar();
while (isdigit(ch))
res = res * 10 + (ch ^ 48), ch = getchar();
return res * f;
}
inline void write (int x) {
int top = 0;
if (x < 0)
x = -x, putchar('-');
do {
stk[top++] = x % 10, x /= 10;
} while (x);
while (top)
putchar(stk[--top] + '0');
}
}
using namespace std;
using namespace FastIO;
const int N = 4e5 + 10, inf = 3e18;
int n, m, k;
int tot, point[N], id[N];
int root[N], fail[N], cnt;
bool sign[N];
set<pii> e[N], ch[N];
struct sgtNode {
bool sign;
int lson, rson;
int w, v;
} sgt[N << 6];
inline int newNode(int rt) {
sgt[++tot] = sgt[rt];
return tot;
}
int update(int pos, int l, int r, int rt, int v, int w) {
rt = newNode(rt);
if (l == r) {
sgt[rt].w = w;
sgt[rt].v = v;
return rt;
}
int mid = (l + r) / 2;
if (pos <= mid)
sgt[rt].lson = update(pos, l, mid, sgt[rt].lson, v, w);
else
sgt[rt].rson = update(pos, mid + 1, r, sgt[rt].rson, v, w);
return rt;
}
int query(int pos, int l, int r, int rt) {
if (l == r) {
return sgt[rt].v;
}
int mid = (l + r) / 2;
if (pos <= mid)
return query(pos, l, mid, sgt[rt].lson);
else
return query(pos, mid + 1, r, sgt[rt].rson);
}
void getFailPointer() {
queue<int> q;
for (pii pr : ch[0])
q.emplace(pr.second);
while (!q.empty()) {
int u = q.front();
q.pop();
int now = root[fail[u]];
sign[u] |= sign[fail[u]];
if (!fail[u]) {
for (pii pr : e[id[u]])
now = update(pr.first, 1, n, now, point[pr.first], pr.second);
}
for (pii pr : ch[u]) {
auto it = e[id[u]].lower_bound(mkp(pr.first, 0));
if (it != e[id[u]].end() && it -> first == pr.first)
now = update(pr.first, 1, n, now, pr.second, it -> second);
}
root[u] = now;
for (pii pr : ch[u]) {
fail[pr.second] = query(pr.first, 1, n, root[fail[u]]);
if (!fail[pr.second])
fail[pr.second] = point[pr.first];
q.emplace(pr.second);
}
}
}
bool vst[N];
int dis[N], now;
priority_queue<pii, vector<pii>, greater<pii >> q;
void solve(int rt, int l, int r) {
if (sgt[rt].sign) return;
sgt[rt].sign = true;
if (l == r) {
if (!sign[sgt[rt].v] && !vst[sgt[rt].v]) {
int dis0 = dis[now] + sgt[rt].w;
if (dis[sgt[rt].v] > dis0) {
dis[sgt[rt].v] = dis0;
q.push(mkp(dis0, sgt[rt].v));
}
}
return;
}
int mid = (l + r) / 2;
if (sgt[rt].lson)
solve(sgt[rt].lson, l, mid);
if (sgt[rt].rson)
solve(sgt[rt].rson, mid + 1, r);
}
bool dijkstra() {
if (sign[point[1]])
return false;
q.push(mkp(0, point[1]));
memset (dis, 0x3f, sizeof(dis));
dis[point[1]] = 0;
while (!q.empty()) {
int u = q.top().second;
if (id[u] == n) {
write(q.top().first);
return true;
}
q.pop();
if (vst[u])
continue;
vst[u] = true;
now = u, solve(root[u], 1, n);
}
return false;
}
signed main() {
n = read(), m = read(), k = read();
for (int i = 1, u, v, w; i <= m; i ++) {
u = read(), v = read(), w = read();
e[u].insert(mkp(v, w));
}
for (int i = 1; i <= k; i ++) {
int p, now = 0;
p = read();
while (p--) {
int x, nxt = 0;
x = read();
auto iter = ch[now].lower_bound(mkp(x, 0));
if (iter == ch[now].end() || iter -> first != x) {
ch[now].insert(mkp(x, nxt = ++tot)), id[tot] = x;
} else {
nxt = iter -> second;
}
now = nxt;
}
sign[now] = true;
}
for (int i = 1; i <= n; i ++) {
auto iter = ch[0].lower_bound(mkp(i, 0));
if (iter == ch[0].end() || iter -> first != i) {
ch[0].insert(mkp(i, ++tot));
point[i] = tot, id[tot] = i;
} else {
point[i] = iter -> second;
}
}
getFailPointer();
if (!dijkstra())
write(-1);
return 0;
}

浙公网安备 33010602011771号