A
给出两棵树和对应节点的权值,给出k个点,从k个点中删除一个点,若在a树中剩余k-1个点的lca对应的权值大于在b树中剩余k-1个点lca对应的权值,则该种删除方法是有效的删除方法;要求求出有效方案数的总和。
考虑k-1个点的lca该如何维护,这里使用两个数组来维护一棵树k-1个点的lca:一个pre数组,pre[i]代表k个节点中前i个节点的lca;一个lst数组,lst[i]代表从节点i到节点k的lca。则删除点i后的lca即为getlca(pre[i-1], lst[i+1])。然后枚举删除的点,找出合法的方案即可。
代码里面求解lca使用的是dfs序+RMQ求解的,不明白的可以在网上找找相关资料。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int N = 2e5+5;
struct lcatree
{
int pos[N], seq[N * 2], dep[N], val[N], f[N][20], tot = 0;
bool vis[N];
vector<int> son[N];
void dfs(int u, int d) { // u表示当前结点,d表示深度
vis[u] = 1;
pos[u] = ++tot; // 记录每个节点最后一次出现的位置
seq[tot] = u; // 记录欧拉序
dep[tot] = d; // 记录当前位置节点的深度
for (auto s : son[u]) { // 遍历所有子节点
if (vis[s]) continue;
dfs(s, d + 1); // 在子树上深搜
seq[++tot] = u; // 记录回溯到的节点
dep[tot] = d; // 记录节点的深度
}
}
void st_create() { // 创建ST表,求解RMQ问题
// f[i][j] 记录欧拉序中从 i 到 i + 2^j - 1 区间内深度最低的点
for (int i = 1; i <= tot; i++)
f[i][0] = i;
int k = log2(tot), f1, f2;
for (int j = 1; j <= k; j++) {
for (int i = 1; i <= tot - (1 << j) + 1; i++) {
f1 = f[i][j - 1], f2 = f[i + (1 << (j - 1))][j - 1];
f[i][j] = dep[f1] < dep[f2] ? f1 : f2;
}
}
}
int get_lca(int u, int v) { // 两个节点的lca为两个节点最后出现位置的区间中深度最小的节点
int l = pos[u], r = pos[v]; // 找到两个节点最后出现的位置
if (l > r) swap(l, r);
int k = log2(r - l + 1);
int f1 = f[l][k], f2 = f[r - (1 << k) + 1][k];
return dep[f1] < dep[f2] ? seq[f1] : seq[f2]; // 找到深度最小的节点
}
void init(int n) {
memset(vis, 0, sizeof vis);
int fa;
for (int i = 1; i <= n; i++)
scanf("%d", &val[i]);
for (int i = 2; i <= n; i++) {
scanf("%d", &fa);
son[fa].push_back(i);
}
dfs(1, 0);
st_create();
}
} treea, treeb;
int key[N], prea[N], preb[N], lsta[N], lstb[N];
int main(void)
{
int n, k, ans = 0;
scanf("%d%d", &n, &k);
for(int i = 1; i <= k; ++i) {
scanf("%d", &key[i]);
}
treea.init(n);
treeb.init(n);
prea[1] = key[1];
preb[1] = key[1];
for(int i = 2; i <= k; ++i) {
prea[i] = treea.get_lca(prea[i-1], key[i]);
preb[i] = treeb.get_lca(preb[i-1], key[i]);
}
lsta[k] = key[k];
lstb[k] = key[k];
for(int i = k-1; i >= 1; --i) {
lsta[i] = treea.get_lca(lsta[i+1], key[i]);
lstb[i] = treeb.get_lca(lstb[i+1], key[i]);
}
for(int i = 1; i <= k; ++i) {
if(i == 1) {
if(treea.val[lsta[2]] > treeb.val[lstb[2]])
++ans;
} else if(i == k) {
if(treea.val[prea[k-1]] > treeb.val[preb[k-1]])
++ans;
} else {
int lcaa = treea.get_lca(prea[i-1], lsta[i+1]);
int lcab = treeb.get_lca(preb[i-1], lstb[i+1]);
if(treea.val[lcaa] > treeb.val[lcab])
++ans;
}
}
printf("%d\n", ans);
return 0;
}
J
题目给出了一张地图,地图中有n个十字路口。每一行按照逆时针方向给出当前路口连接的点。在每个十字路口,右转到达的路不需要等红绿灯,其他的方向均需要等一个红绿灯。给出起始所在的道路和终点所在的道路,要求求出最短经过的红绿灯的数目。
本质上就是一个最短路的问题,右转的消耗为0,其他方向为1;同时这个问题还是01bfs,可以使用双端队列加速问题的求解。这个题目的意思比较难理解,问题的求解还是比较简单的,后悔当时场上没有写😭。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <deque>
using namespace std;
typedef pair<int, int> PII;
const int maxn = 5e5+5;
int mp[maxn][4];
bool vis[maxn][4];
int s1, s2, t1, t2;
deque<pair<PII, int> > que;
int main(void)
{
/* freopen("in.txt", "r", stdin); */
int n;
scanf("%d", &n);
for(int i = 1 ; i <= n; ++i) {
scanf("%d%d%d%d", &mp[i][0], &mp[i][1], &mp[i][2], &mp[i][3]);
vis[i][0] = vis[i][1] = vis[i][2] = vis[i][3] = false;
}
scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
que.push_back({{s1, s2}, 0});
bool flag = false;
while(que.size()) {
auto t = que.front(); que.pop_front();
int x = t.first.first, y = t.first.second;
if(x == t1 && y == t2) {
flag = true;
printf("%d\n", t.second);
break;
}
for(int j = 0; j < 4; ++j) {
if(mp[y][j] == 0 || vis[y][j]) {
continue;
} else if(mp[y][j] == x) { // 确定方向后拓展节点
vis[y][j] = true;
que.push_front({{y, mp[y][(j+1)%4]}, t.second}); // 将当前经过距离最短的放在队首,优先拓展
que.push_back({{y, mp[y][(j+2)%4]}, t.second+1});
que.push_back({{y, mp[y][(j+3)%4]}, t.second+1});
que.push_back({{y, mp[y][j%4]}, t.second+1});
}
}
}
if(!flag) {
printf("-1\n");
}
return 0;
}