好冷好热好冷好热

贪心 模拟

首先可以想到很显然的解法,就是设计状态 表示如果第 秒温度为 ,前 秒是否均满足条件。当然这个是会炸掉的。

不难发现后面的 实际上可取的值是一个区间(所有人的温度要求的交和可能的温度的交)。所以我们只需要维护区间就行了。

但是枚举 还是会炸。所以考虑对时间进行离散化,离散化之后就只需要枚举最多 。如果当前 与上一个 的差值为 ,那么区间就变成 。再对当前 对应的所有要求温度区间取交集即可。如果取完变空集就不满足条件。

#include <fstream>
#include <algorithm>
#include <utility>
#include <vector>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 1e5 + 5;
 
ifstream cin("temp.in");
ofstream cout("temp.out");
 
LL l[kMaxN], h[kMaxN], t[kMaxN], val[kMaxN], T, n, m, k;
vector<pair<LL, LL>> v[kMaxN];
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T; T--) {
    cin >> n >> m;
    fill(v + 1, v + k + 1, vector<pair<LL, LL>>());
    k = 0;
    for (LL i = 1; i <= n; i++) {
      cin >> t[i] >> l[i] >> h[i];
      val[++k] = t[i];
    }
    sort(val + 1, val + k + 1);
    k = unique(val + 1, val + k + 1) - val - 1;
    for (LL i = 1; i <= n; i++) {
      t[i] = lower_bound(val + 1, val + k + 1, t[i]) - val;
      v[t[i]].emplace_back(l[i], h[i]);
    }
    []() {
      LL l = m, r = m;
      for (LL i = 1; i <= k; i++) {
        l -= val[i] - val[i - 1];
        r += val[i] - val[i - 1];
        for (const auto &j : v[i]) {
          if (r < j.first || l > j.second) {
            cout << "NO\n";
            return;
          }
          l = max(l, j.first);
          r = min(r, j.second);
        }
        if (l > r) {
          cout << "NO\n";
        }
      }
      cout << "YES\n";
    }();
  }
  return 0;
}

杀戮尖塔

线段树 二分 倍增 树链剖分

首先这道题目有多个不同的遗物,以区间或路径为基础很难处理这些不同的遗物。所以考虑分遗物处理区间。

对于 的路径,很显然我们可以写一个二分,二分出最下方一个点 使得 的路径上正好有一个遗物,开 棵动态开点线段树用于判断。当然,预处理倍增父亲后直接倍增就行了。

这样子是不好做的,因为我们无法快速查询 上方的祖先之间是否有 存在。考虑做树剖,先把 往上跳若干条没有 存在的链,然后在剩下的那条有 的链上做二分或者倍增。时间复杂度

这样子的在线做法的时间复杂度还是太高了。考虑离线,对于每个 可能作为哪些 的答案。不难发现每个 可能给 子树中的任意点做贡献,但是对于 它要求深度最大的 。所以按照深度枚举 然后对指定线段树上 进行子树覆盖即可。时间复杂度

当然我写的在线做法。

#include <fstream>
#include <vector>
 
using namespace std;
 
const int kMaxN = 1e5 + 5, kLog = 20;
 
ifstream cin("slay.in");
ofstream cout("slay.out");
 
struct Node {
  int lc, rc, sum;
} tr[kLog * kMaxN];
 
int rt[kMaxN], fa[kMaxN], dp[kMaxN][kLog], dfn[kMaxN], top[kMaxN], dep[kMaxN], siz[kMaxN], son[kMaxN], n, q, cnt, tim;
vector<int> e[kMaxN];
 
void modify(int &p, int l, int r, int x) {
  if (!p) {
    p = ++cnt;
  }
  tr[p].sum = 1;
  if (l == r) {
    return;
  }
  int m = (l + r) / 2;
  if (x <= m) {
    modify(tr[p].lc, l, m, x);
  } else {
    modify(tr[p].rc, m + 1, r, x);
  }
}
 
int query(int p, int l, int r, int s, int t) {
  if (!p || s > t) {
    return 0;
  }
  if (s <= l && r <= t) {
    return tr[p].sum;
  }
  int m = (l + r) / 2, res = 0;
  if (s <= m) {
    res |= query(tr[p].lc, l, m, s, t);
  }
  if (m < t) {
    res |= query(tr[p].rc, m + 1, r, s, t);
  }
  return res;
}
 
void dfs1(int x, int f) {
  dep[x] = dep[f] + 1;
  fa[x] = f;
  siz[x] = 1;
  dp[x][0] = f;
  for (int i = 1; i < kLog; i++) {
    dp[x][i] = dp[dp[x][i - 1]][i - 1];
  }
  for (int i : e[x]) {
    if (i == f) {
      continue;
    }
    dfs1(i, x);
    siz[x] += siz[i];
    if (siz[i] > siz[son[x]]) {
      son[x] = i;
    }
  }
}
 
void dfs2(int x, int t) {
  top[x] = t;
  dfn[x] = ++tim;
  if (son[x]) {
    dfs2(son[x], t);
  }
  for (int i : e[x]) {
    if (i == fa[x] || i == son[x]) {
      continue;
    }
    dfs2(i, i);
  }
}
 
int jump(int x, int y) {
  for (int i = 0; i < kLog; i++) {
    if (y & (1 << i)) {
      x = dp[x][i];
    }
  }
  return x;
}
 
int query(int x, int y) {
  for (; x && !query(rt[y], 1, n, dfn[top[x]], dfn[x]); x = fa[top[x]]) { }
  if (!x) {
    return -1;
  }
  int res = 0;
  for (int l = 0, r = dep[x] - 1; l <= r; ) {
    int m = (l + r) / 2, p = jump(x, m);
    if (query(rt[y], 1, n, dfn[p], dfn[x])) {
      res = p;
      r = m - 1;
    } else {
      l = m + 1;
    }
  }
  return res;
}
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 2, x; i <= n; i++) {
    cin >> x;
    e[x].push_back(i);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  for (int i = 1, k; i <= n; i++) {
    cin >> k;
    for (int x; k; k--) {
      cin >> x;
      modify(rt[x], 1, n, dfn[i]);
    }
  }
  cin >> q;
  for (int x, y; q; q--) {
    cin >> x >> y;
    cout << query(x, y) << '\n';
  }
  return 0;
}

故障机器人

广度优先搜索

首先机器人在路径长度超过 之后会炸掉,炸掉后对人没有影响,但是人只要被任意一个机器人的任意一种走法抓住就会死掉,所以我们直接规定所有机器人都不能炸掉就行了(保证每个机器人路径长度始终小于等于 )。

我们需要判断如果在 时刻到 点的话是否又被机器人抓到的可能性,如果有就不能在这个时刻走到 。具体来说,如果 个机器人当中有任何一个可以正好在 时刻到 就走不了。

然而,机器人可以在两个点之间反复横跳(如果已走步数小于 的话)。所以,我们只需要判断机器人是否有走到 的路径且这个路径长与 同奇偶,如果还路径长小于等于 的话,就是可以走到 的。 (如果 超出了 ,就把 变成小于等于 且和 同奇偶的数)这是奇偶最短路的模板。

现在我们的主要问题就是对于 个怪物跑单源最短路太慢了。不过我们只需要判断是否有一个怪物可到达即可,这预示着我们可以对所有怪物一起处理。即多个源点的奇偶最短路。

处理完所有点的是能否走的情况后,我们开始构造方案。对于一个点 ,如果有多个同奇偶的 可以经过这个 ,那么一定是 最小的更优,因为在所有同奇偶的 当中, 越大可以到达的点一定是更多的,这使得我们的行动愈发受限。

所以对 的起点跑奇偶最短路,辅佐对能否被怪物干死的判断,即可得到一条 的路径。在奇数长路径和偶数长路径中选更短的那条即可。

最后是输出答案。本题的 std 书写错误,它是反着求最小字典序的(也就是 更优;而我的代码正确地处理了正着的最小字典序。因为 std 的错误处理,导致我 WA 了 分的测试点。现在展现的是我正确处理了字典序的代码(实际 分):

#include <fstream>
#include <algorithm>
#include <vector>
#include <queue>
 
using namespace std;
 
ifstream cin("defect.in");
ofstream cout("defect.out");
 
const int kMaxN = 1e5 + 5;
 
int dis[kMaxN][2], len[kMaxN][2], a[kMaxN], f[kMaxN], n, m, d, k;
vector<int> e[kMaxN], p[kMaxN][2], g[kMaxN];
queue<pair<int, int>> q;
 
void bfs() {
  fill(dis[0], dis[0] + 2 * kMaxN, 1e9);
  for (int i = 1; i <= k; i++) {
    dis[a[i]][0] = 0;
    q.emplace(a[i], 0);
  }
  for (; q.size(); q.pop()) {
    auto t = q.front();
    for (int i : e[t.first]) {
      if (dis[t.first][t.second] + 1 < dis[i][t.second ^ 1]) {
        dis[i][t.second ^ 1] = dis[t.first][t.second] + 1;
        q.emplace(i, t.second ^ 1);
      }
    }
  }
}
 
// 在 t 时刻,x 是否会被干掉
bool check(int x, int t) {
  if (t % 2 == d % 2) {
    t = min(t, d);
  } else {
    t = min(t, d - 1);
  }
  return dis[x][t % 2] <= t;
}
 
void solve() {
  if (check(1, 0)) {
    cout << "-1\n";
    exit(0);
  }
  fill(len[0], len[0] + 2 * kMaxN, 1e9);
  for (q.emplace(1, 0), len[1][0] = 0; q.size(); q.pop()) {
    auto t = q.front();
    for (int i : e[t.first]) {
      if (!check(i, len[t.first][t.second] + 1)) {
        if (len[t.first][t.second] + 1 < len[i][t.second ^ 1]) {
          p[i][t.second ^ 1].clear();
          p[i][t.second ^ 1].push_back(t.first);
          len[i][t.second ^ 1] = len[t.first][t.second] + 1;
          q.emplace(i, t.second ^ 1);
        } else if (len[t.first][t.second] + 1 == len[i][t.second ^ 1]) {
          p[i][t.second ^ 1].push_back(t.first);
        }
      } 
    }
  }
}
 
void paint(int x, int b) {
  if (f[x]) {
    return;
  }
  f[x] = 1;
  for (int i : p[x][b]) {
    g[i].push_back(x);
    paint(i, b ^ 1);
  }
}
 
void print() {
  for (int x = 1; x != n; ) {
    cout << x << ' ';
    x = *min_element(g[x].begin(), g[x].end());
  }
  cout << n << '\n';
}
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> d;
  for (int i = 1, u, v; i <= m; i++) {
    cin >> u >> v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  cin >> k;
  for (int i = 1; i <= k; i++) {
    cin >> a[i];
  }
  bfs();
  solve();
  if (len[n][0] == 1e9 && len[n][1] == 1e9) {
    cout << "-1\n";
    return 0;
  }
  if (len[n][0] < len[n][1]) {
    cout << len[n][0] << '\n';
    paint(n, 0);
  } else {
    cout << len[n][1] << '\n';
    paint(n, 1);
  }
  print();
  return 0;
}

树上纯树

李超线段树 树形dp

考虑最简单的动态规划:设 表示 到其子树中的任意叶子节点需要的最小费用。那么对于已经是叶子节点的 ,有 ;否则枚举子树中的任意节点 ,求

时间复杂度 ,可以写一个启发式合并。然后……就不会优化了。需要用李超线段树解决子树的问题,然后线段树合并。但是我不会。