2024初秋集训——提高组 #26
C. 牛半仙的妹子 Tree
题目描述
给定一棵树,当一个结点上打了标记,那么下一个单位时间这个标记就会扩散到其相邻的结点上,你有以下三种操作:
- 给一个结点打上标记。
- 清除所有标记。
- 查询一个结点是否有标记。
思路
考虑根号分治。
我们对两次二操作之间的操作一数进行分治:
- 当操作一数 \(\le \sqrt N\) 时,对于每次查询直接暴力枚举之前每一个打了标记的结点并计算其距离,总时间复杂度 \(O(N\sqrt N \log N)\)。
- 否则,我们使用 bfs 求解,总时间复杂度 \(O(N\sqrt N)\)。
空间复杂度 \(O(N)\),时间复杂度 \(O(N\sqrt N \log N)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
const int MAXN = 100001, MAXM = 100001, B = 317;
struct query {
int op, x;
}s[MAXM];
int n, m, dep[MAXN], f[18][MAXN];
bool ans[MAXM], vis[MAXN];
vector<int> e[MAXN];
void dfs(int u, int fa) {
f[0][u] = fa, dep[u] = dep[fa] + 1;
for(int i = 1; i <= 17; ++i) {
f[i][u] = f[i - 1][f[i - 1][u]];
}
for(int v : e[u]) {
if(v != fa) {
dfs(v, u);
}
}
}
int LCA(int u, int v) {
if(dep[u] < dep[v]) {
swap(u, v);
}
int d = dep[u] - dep[v];
for(int i = 17; i >= 0; --i) {
if((d >> i) & 1) {
u = f[i][u];
}
}
if(u == v) {
return u;
}
for(int i = 17; i >= 0; --i) {
if(f[i][u] != f[i][v]) {
u = f[i][u], v = f[i][v];
}
}
return f[0][u];
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
for(int i = 1; i <= m; ++i) {
cin >> s[i].op >> s[i].x;
}
dfs(1, 0);
for(int i = 1, j = 1, cnt = 0; i <= m; i = ++j) {
for(; j <= m && s[j].op != 2; cnt += (s[i].op == 1), ++j) {
}
if(cnt <= B) {
vector<pii> pos;
for(int k = i; k < j; ++k) {
if(s[k].op == 1) {
pos.emplace_back(s[k].x, k);
}else {
for(auto [x, id] : pos) {
if(dep[s[k].x] + dep[x] - 2 * dep[LCA(s[k].x, x)] <= k - id) {
ans[k] = 1;
break;
}
}
}
}
pos.clear();
}else {
queue<int> que;
for(int k = i; k < j; ++k) {
queue<int> q;
for(; !que.empty(); q.push(que.front()), que.pop()) {
}
for(; !q.empty(); ) {
int u = q.front();
q.pop();
for(int v : e[u]) {
if(!vis[v]) {
vis[v] = 1;
que.push(v);
}
}
}
if(s[k].op == 1) {
que.push(s[k].x);
vis[s[k].x] = 1;
}else {
ans[k] = vis[s[k].x];
}
}
for(int i = 1; i <= n; ++i) {
vis[i] = 0;
}
}
}
for(int i = 1; i <= m; ++i) {
if(s[i].op == 3) {
cout << (ans[i] ? "wrxcsd\n" : "orzFsYo\n");
}
}
return 0;
}
D. 牛半仙的魔塔 II
题目描述
有 \(N\) 个结点,第 \(i\) 个结点上有一只怪物。一开始你在结点 \(1\),结点 \(1\) 上没有怪物。当你第一次到达一个不为 \(1\) 的结点,你会与上面的怪物战斗,每次战斗你和怪物轮流攻击,你先攻击,每次攻击的伤害为你的攻击减敌人的防御。当你的血量 \(\le 0\) 时你就死了。打死一只怪物之后你的防御会增加。求把所有的怪物打死最后的最大剩余血量。
思路
我们可以求出将哪个怪物优先打更优,但这个怪物不一定能直接打到,不过我们能确定当它的父亲被打死之后下一个一定来打它。也就是可以看作它和它父亲是连在一起的,所以可以把它们合并。我们可以先不考虑你的防御增加,求出最中有多少血。然后在合并时统计能让你少扣多少血。合并用并查集维护,求哪个怪物优先打使用优先队列维护。
空间复杂度 \(O(N)\),时间复杂度 \(O(N\log N)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 100001;
struct Node {
ll u, a, b;
}s[MAXN];
struct cmp {
bool operator()(const Node &a, const Node &b) const {
return 1ll * a.a * b.b > 1ll * b.a * a.b;
}
};
int n, f[MAXN], _f[MAXN];
ll HP, ATK, DEF;
vector<int> e[MAXN];
priority_queue<Node, vector<Node>, cmp> pq;
int getfa(int u) {
return (f[u] == u ? u : f[u] = getfa(f[u]));
}
void dfs(int u, int fa) {
_f[u] = fa;
for(int v : e[u]) {
if(v != fa) {
dfs(v, u);
}
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
iota(f + 1, f + n + 1, 1);
for(int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs(1, 0);
cin >> HP >> ATK >> DEF;
for(int i = 2, hp, atk, def; i <= n; ++i) {
cin >> hp >> atk >> def >> s[i].b;
s[i].u = i;
s[i].a = (hp + ATK - def - 1) / (ATK - def) - 1;
HP -= 1ll * s[i].a * atk;
pq.push(s[i]);
}
for(; pq.size(); ) {
auto [u, a, b] = pq.top();
pq.pop();
if(f[u] != u || u == 1 || a != s[u].a || b != s[u].b) {
continue;
}
int fu = getfa(_f[u]);
HP += 1ll * s[fu].b * a;
s[fu].a += a, s[fu].b += b;
f[u] = _f[u];
pq.push({fu, s[fu].a, s[fu].b});
}
cout << (HP > 0 ? HP : -1ll);
return 0;
}