比赛链接:
https://ac.nowcoder.com/acm/contest/33188
A.Ancestor
题意:
已知两棵有 \(n\) 个节点的树 \(A\) 和 \(B\),每个节点都有自己对应的权重,有一个长为 \(k\) 的序列 \(x\),表示树中的关键节点,第 \(i\) 轮删除 \(x_i\) 这个关键节点,问 \(A\) 树中剩余关键节点的最近公共祖先的权重是否大于 \(B\) 树种剩余关键节点的最近公共祖先的权重。
思路:
删除了第 \(i\) 个节点,剩余节点的 \(lca\) 其实就是 \(lca(lca(a_1, a_2, ..., a_{i - 1}), lca(a_{i + 1}, ..., a_n))\)。所以先处理所有关键节点的一个前缀的 \(lca\),和一个后缀的 \(lca\),然后比较权重即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
struct Tree{
vector <int> top, son, dep, parent, sz;
vector < vector<int> > e;
Tree(int n) : top(n + 1), son(n + 1), dep(n + 1), parent(n + 1), sz(n + 1), e(n + 1) {}
void add(int u, int v){
e[u].push_back(v);
e[v].push_back(u);
}
void init(int s){
dfs1(s);
dfs2(s, s);
}
void dfs1(int u){ //计算子树大小、深度、父亲和重儿子
sz[u] = 1;
dep[u] = dep[parent[u]] + 1;
for (auto v : e[u]){
if (v == parent[u]) continue;
parent[v] = u;
dfs1(v);
sz[u] += sz[v];
if (!son[u] || sz[son[u]] < sz[v]){
son[u] = v;
}
}
}
void dfs2(int u, int up){ //计算链的头部节点
top[u] = up;
if (son[u]) dfs2(son[u], up);
for (auto v : e[u]){
if (v == parent[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int lca(int u, int v){
while(top[u] != top[v]){
if (dep[top[u]] >= dep[top[v]]){
u = parent[top[u]];
}
else{
v = parent[top[v]];
}
}
return (dep[u] < dep[v] ? u : v);
}
};
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL n, k;
cin >> n >> k;
vector <LL> x(k), a(n + 1), b(n + 1);
for (int i = 0; i < k; i ++ )
cin >> x[i];
Tree A(n), B(n);
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int u = 2; u <= n; u ++ ){
LL v;
cin >> v;
A.add(u, v);
}
for (int i = 1; i <= n; i ++ )
cin >> b[i];
for (int u = 2; u <= n; u ++ ){
LL v;
cin >> v;
B.add(u, v);
}
A.init(1);
B.init(1);
vector <LL> preA(k), preB(k);
for (int i = 0; i < k; i ++ ){ //处理前缀
if (!i){
preA[i] = x[i];
preB[i] = x[i];
}
else{
preA[i] = A.lca(preA[i - 1], x[i]);
preB[i] = B.lca(preB[i - 1], x[i]);
}
}
vector <LL> sufA(k), sufB(k);
for (int i = k - 1; i >= 0; i -- ){ //处理后缀
if (i == k - 1){
sufA[i] = x[i];
sufB[i] = x[i];
}
else{
sufA[i] = A.lca(sufA[i + 1], x[i]);
sufB[i] = B.lca(sufB[i + 1], x[i]);
}
}
LL ans = 0;
for (int i = 0; i < k; i ++ ){
if (!i){
if (a[sufA[1]] > b[sufB[1]]){
ans ++ ;
}
}
else if (i == k - 1){
if (a[preA[k - 2]] > b[preB[k - 2]]){
ans ++ ;
}
}
else{
if (a[A.lca(preA[i - 1], sufA[i + 1])] > b[B.lca(preB[i - 1], sufB[i + 1])]){
ans ++ ;
}
}
}
cout << ans << "\n";
return 0;
}
C.Concatenation
题意:
给定 \(s\) 个字符串,输出将它们拼接起来后字典序最小的字符串。
思路:
直接 \(sort\),stable_sort 较稳定。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL n;
cin >> n;
vector <string> s(n);
for (int i = 0; i < n; i ++ )
cin >> s[i];
sort(s.begin(), s.end(), [](string a, string b){
return a + b < b + a;
});
for (int i = 0; i < n; i ++ )
cout << s[i];
return 0;
}
H.Hacker
题意:
给定长为 \(n\) 的模式串 \(A\),\(k\) 次询问,每次给定一个长为 \(m\) 的字符串 \(B\),第 \(i\) 位对应一个权值 \(v\),如果某个子串是 \(A\) 和 \(B\) 的公共子串,可以得到该子串对应的权重之和,问最大能得到的权重之和为多少。
思路:
本质就是找 \(B\) 的哪些子串是 \(A\) 的子串,先按照模式串 \(A\) 构建后缀自动机,然后将 \(B\) 的每个后缀放进去匹配,最后可以得到它们的公共子串长度。
现在已知左右端点了,为了使权值最大,因为放的是后缀,即固定了右端点,对权值进行一次前缀和,那么只需要找到这个区间中前缀和最小的,用右端点减去它就是答案了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10;
struct SAM{
struct node{
int len, fa;
int to[26];
}nd[N << 1];
int tot, last;
SAM(){
nd[0].len = 0;
nd[0].fa = -1;
tot = last = 0;
}
void extend(int x){
int cur = ++ tot;
nd[cur].len = nd[last].len + 1;
int p = last;
while(p != -1 && !nd[p].to[x]){
nd[p].to[x] = cur;
p = nd[p].fa;
}
if (p == -1){
nd[cur].fa = 0;
}
else{
int q = nd[p].to[x];
if (nd[p].len + 1 == nd[q].len){
nd[cur].fa = q;
}
else{
int clone = ++ tot;
nd[clone] = nd[q];
nd[clone].len = nd[p].len + 1;
while(p != -1 && nd[p].to[x] == q){
nd[p].to[x] = clone;
p = nd[p].fa;
}
nd[q].fa = nd[cur].fa = clone;
}
}
last = cur;
}
}sam;
LL s[N];
struct Segt{
struct node{
LL l, r, mn;
}tr[N << 2];
void pushup(LL u){
tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
}
void build(LL u, LL l, LL r){
if (l == r){
tr[u] = {l, r, s[r]};
return;
}
tr[u] = {l, r};
LL mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
LL query(LL u, LL l, LL r){
if (tr[u].l >= l && tr[u].r <= r) return tr[u].mn;
LL mid = tr[u].l + tr[u].r >> 1, mn = 1e18;
if (l <= mid) mn = min(mn, query(u << 1, l, r));
if (r > mid) mn = min(mn, query(u << 1 | 1, l, r));
return mn;
}
}segt;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, m, k;
cin >> n >> m >> k;
string A;
cin >> A;
for (auto c : A)
sam.extend(c - 'a');
vector <int> v(m + 1);
for (int i = 1; i <= m; i ++ )
cin >> v[i];
for (int i = 1; i <= m; i ++ )
s[i] = s[i - 1] + v[i];
segt.build(1, 0, m); //因为是前缀和放到线段树中,所以要 0 到 m
while(k -- ){
string B;
cin >> B;
int p = 0, len = 0;
LL ans = 0;
for (int i = 0; i < m; i ++ ){
while(p && !sam.nd[p].to[B[i] - 'a']){
p = sam.nd[p].fa;
len = sam.nd[p].len;
}
if (!sam.nd[p].to[B[i] - 'a']) continue;
p = sam.nd[p].to[B[i] - 'a'];
len ++ ;
ans = max(ans, s[i + 1] - segt.query(1, i + 1 - len, i + 1));
}
cout << ans << "\n";
}
return 0;
}