比赛链接:

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;
}
posted on 2022-07-30 20:05  Hamine  阅读(23)  评论(0编辑  收藏  举报