FJOIP 2021 & GDKOI 2021 Day 1

QOJ 6147 赛车竞技

最短路的边数一定不超过 \(n - 1\),所以换乘的次数一定不会超过 \(n - 1\)

于是设 \(f_{i, x, y}\) 表示换乘了 \(i\) 次,\(x\to y\) 的最短路。
转移就枚举最后一次换乘即可,\(f_{i - 1, x, z} + f_{0, z, y}\to f_{i, x, y}\)

对于 \(f_{0, x, y}\) 的预处理可以直接对 \(m\) 个图跑 floyd 后取 \(\min\)

时间复杂度 \(\mathcal{O}((n + m)n^3 + q)\)

代码
#include<bits/stdc++.h>
constexpr int maxn = 80 + 2;
int d[maxn][maxn], dis[maxn][maxn][maxn];
int main() {
   int n, m, q;
   scanf("%d%d%d", &n, &m, &q);
   memset(dis, 0x3f, sizeof(dis));
   for (; m--; ) {
      for (int i = 1; i <= n; i++) {
         for (int j = 1; j <= n; j++) {
            scanf("%d", &d[i][j]);
         }
      }
      for (int k = 1; k <= n; k++) {
         for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
               d[i][j] = std::min(d[i][j], d[i][k] + d[k][j]);
            }
         }
      }
      for (int i = 1; i <= n; i++) {
         for (int j = 1; j <= n; j++) {
            dis[i][j][1] = std::min(dis[i][j][1], d[i][j]);
         }
      }
   }
   for (int d = 2; d <= n; d++) {
      for (int i = 1; i <= n; i++) {
         for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
               dis[i][j][d] = std::min(dis[i][j][d], dis[i][k][d - 1] + dis[k][j][1]);
            }
         }
      }
   }
   for (int s, t, k; q--; ) {
      scanf("%d%d%d", &s, &t, &k);
      printf("%d\n", dis[s][t][std::min(k + 1, n)]);
   }
   return 0;
}

QOJ 6148 宝石项圈

考虑这个金珠不能相邻,就说明了金珠的前后一定得是个银珠。
于是考虑捆绑,把 金珠-银珠 捆绑到一起和单独的银珠考虑,放的是两个物品就能保证不会出现相邻的金珠。

接下来考虑断环成链设计 dp。
\(f_i, {0 / 1}\) 表示放了 \(i\) 个珠子,有偶数 / 奇数个金珠的方案数。
转移考虑 \(i\) 这里放的是什么珠子,有 \(f_{i, j} = f_{i - 1, j} + f_{i - 2, 1 - j}\)

对于统计答案,一个贡献肯定就是 \(f_{n, 1}\)
但是断环成链的过程中可能破坏了 \(n\)\(1\) 的 金珠-银珠 的捆绑,那么这个时候就可以直接删去这两个固定下来珠子,剩下的 \(n - 2\) 个任意组合即可,所以还有个 \(f_{n - 2, 0}\) 的贡献。

时间复杂度 \(\mathcal{O}(Tn)\),可以用矩阵优化到 \(\mathcal{O}(Tw^3\log n)\),其中 \(w = 4\)

代码
#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 1.2e5 + 10;
ll f[maxn][2];
int main() {
   int n; ll mod;
   while (~ scanf("%d%lld", &n, &mod)) {
      f[0][0] = 1ll;
      for (int i = 1; i <= n; i++) {
         for (int v : {0, 1}) {
            f[i][v] = (f[i - 1][v] + (i > 1 ? f[i - 2][v ^ 1] : 0ll)) % mod;
         }
      }
      printf("%lld\n", (f[n][1] + (n > 1 ? f[n - 2][0] : 0ll)) % mod);
   }
   return 0;
}

QOJ 6149 占领世界

问题 1:

考虑这个时候根是不固定的,所以先假定一个根看怎么做。

假设已经知道了根 \(rt\),那么第一步应该选哪个儿子去扩展。
此时贪心的,如果要扩展完这个子树所需要的时间是最长的,贪心的放在最前面去扩展一定不劣。

但是这个值并不清楚,于是考虑 dp,设 \(f_u\) 表示已经扩展了 \(u\),要扩展完 \(u\) 的子树所需要的时间。
那么对于叶子 \(u\) 就是 \(f_u = 0\)
否则对其儿子 \(v_{1\sim k}\) 按照 \(f_{v_i}\) 降序排序,贪心的从前往后扩展,就有 \(f_u = \max\limits_{i = 1}^k \{f_{v_i} + i\}\)

于是就已经可以做到 \(\mathcal{O}(n^2\log n)\) 了。

但是对于每个根都做一次这个 dp 还是不太优秀,所以考虑换根。

换根的实质其实就是把父亲的信息插入到儿子内。
于是假设知道了以 \(u\) 为根,在原树的 \(\operatorname{fa}\) 所对应的子树的 dp 值 \(x\) 该怎么做。
那么按照上面的 dp,首先就应该把 \((\operatorname{fa}, x)\) 也插入到 \((v_i, f_{v_i})\) 中,就得到了 \(u\) 为根的信息了。
接下来还要考虑传给 \(v_i\) 以其为根 \(u\) 的信息。
此时考虑如果把 \((v_i, f_{v_i})\) 删去后的影响,其实就是 \(v_j(j < i)\)\(f_u\) 的影响不会变,\(v_j(j > i)\)\(f_u\) 的影响会因为前移了一位而 \(-1\)
于是预处理前缀和后缀信息就可以 \(\mathcal{O}(1)\) 算出此信息。

时间复杂度 \(\mathcal{O}(n\log n)\)

问题 2:

首先此时根已经定好了,但是发现 \(A, B\) 中间的点究竟由哪边扩展是不知道的。
所以需要找到一个 \(A, B\) 之间的分界线使得一部分分给 \(A\) 扩展,一部分给 \(B\) 扩展。

考虑到要 \(A\) 扩展的点如果更多,那么 \(A\) 要扩展完的时间一定是不降的。
对于 \(B\) 这也同理。

那么这就说明,假设一开始的分界线是 \(A\) 一点也不用扩展,\(B\) 需要扩展全部。
那么在分界线一直往右移直至分界线变为 \(A\) 扩展全部,\(B\) 不用扩展时,\(f_A\) 是不降的,\(f_B\) 是不增的。
而对于一个分界线,其所需要的时间是 \(\max\{f_A, f_B\}\)
那么这就说明,当 \(f_A\ge f_B\) 时,一定是当 \(f_A\) 取到最小值时能使这部分的贡献最小,那么这刚好是满足条件的分界线中最靠近 \(B\) 的那条。
对于 \(f_A < f_B\) 时同理。

于是可以考虑二分这个分界线。
\(f_A\ge f_B\) 时,就往右移分界线;否则往左移分界线。

对于 \(f_A, f_B\) 的计算,可以考虑直接做,就是 \(\mathcal{O}(n\log^2 n)\) 的。
也可以考虑类似换根的想法,维护把一个新子树插入进一个点的过程,可以直接枚举应该插在那里,因为边数是 \(\mathcal{O}(n)\) 的,所以可以做到 \(\mathcal{O}(n\log n)\)

代码实现给的是 \(\mathcal{O}(n\log n)\) 的做法。

代码
#include<bits/stdc++.h>
constexpr int maxn = 5e5 + 10;
int n, a, b;
std::vector<int> to[maxn];
namespace q1 {
   int f[maxn], g[maxn];
   void dfs1(int u, int fa) {
      std::vector<int> G;
      for (int v : to[u]) {
         if (v == fa) continue;
         dfs1(v, u);
         G.push_back(g[v]);
      }
      std::sort(G.begin(), G.end(), std::greater<>());
      int t = 0;
      for (int x : G) {
         g[u] = std::max(g[u], x + (++t));
      }
   }
   void dfs2(int u, int fa, int f_) {
      std::vector<std::pair<int, int> > F;
      for (int v : to[u]) {
         F.emplace_back(v == fa ? f_ : g[v], v);
      }
      std::sort(F.begin(), F.end(), std::greater<>());
      std::vector<int> pre(F.size()), suf(F.size());
      for (int i = 0; i < F.size(); i++) {
         pre[i] = suf[i] = F[i].first + i + 1;
      }
      for (int i = 1; i < F.size(); i++) pre[i] = std::max(pre[i], pre[i - 1]);
      for (int i = (int)F.size() - 2; i >= 0; i--) suf[i] = std::max(suf[i], suf[i + 1]);
      f[u] = pre.back();
      for (int i = 0; i < F.size(); i++) {
         if (F[i].second == fa) continue;
         dfs2(F[i].second, u, std::max(i == 0 ? 0 : pre[i - 1], i + 1 == F.size() ? 0 : (suf[i + 1] - 1)));
      }
   }
   inline void solve() {
      dfs1(1, 0);
      dfs2(1, 0, 0);
      int ans = f[1];
      for (int i = 2; i <= n; i++) {
         ans = std::min(ans, f[i]);
      }
      printf("%d\n", ans);
   }
}
namespace q2 {
   bool vis[maxn];
   int key[maxn], m;
   void dfs1(int u, int fa) {
      vis[u] |= u == b;
      for (int v : to[u]) {
         if (v == fa) continue;
         dfs1(v, u);
         vis[u] |= vis[v];
      }
      if (vis[u]) key[++m] = u;
   }
   int f[maxn];
   void dfs2(int u, int fa) {
      std::vector<int> F;
      for (int v : to[u]) {
         if (v == fa) continue;
         dfs2(v, u);
         F.push_back(f[v]);
      }
      std::sort(F.begin(), F.end(), std::greater<>());
      int t = 0;
      for (int x : F) {
         f[u] = std::max(f[u], x + (++t));
      }
   }
   std::vector<int> fv[maxn], pre[maxn], suf[maxn];
   inline std::pair<int, int> calc(int c) {
      int fl = pre[c].size() ? pre[c].back() : 0;
      for (int i = c - 1; i >= 1; i--) {
         if (fv[i].empty()) {
            fl++; continue;
         } else if (fv[i].back() > fl) {
            fl = std::max(pre[i].back(), fl + (int)fv[i].size() + 1);
         } else {
            for (int j = 0; ; ) {
               if (fl >= fv[i][j]) {
                  fl = std::max({j == 0 ? 0 : pre[i][j - 1], fl + j + 1, suf[i][j] + 1});
                  break;
               }
            }
         }
      }
      int fr = pre[c + 1].size() ? pre[c + 1].back() : 0;
      for (int i = c + 2; i <= m; i++) {
         if (fv[i].empty()) {
            fr++; continue;
         } else if (fv[i].back() > fr) {
            fr = std::max(pre[i].back(), fr + (int)fv[i].size() + 1);
         } else {
            for (int j = 0; ; ) {
               if (fr >= fv[i][j]) {
                  fr = std::max({j == 0 ? 0 : pre[i][j - 1], fr + j + 1, suf[i][j] + 1});
                  break;
               }
            }
         }
      }
      return std::make_pair(fl, fr);
   }
   inline void solve() {
      dfs1(a, 0);
      for (int i = 1; i <= m; i++) {
         const int u = key[i];
         for (int v : to[u]) {
            if (vis[v]) continue;
            dfs2(v, u);
            fv[i].push_back(f[v]);
         }
         std::sort(fv[i].begin(), fv[i].end(), std::greater<int>());
         int t = 0;
         for (auto &x : fv[i]) x += ++t;
         pre[i] = suf[i] = fv[i];
         for (int j = 1; j < fv[i].size(); j++) {
            pre[i][j] = std::max(pre[i][j], pre[i][j - 1]);
         }
         for (int j = (int)fv[i].size() - 2; j >= 0; j--) {
            suf[i][j] = std::max(suf[i][j], suf[i][j + 1]);
         }
      }
      int l = 1, r = m - 1, ans = 1e9;
      while (l <= r) {
         int mid = l + r >> 1;
         auto [x, y] = calc(mid);
         ans = std::min(ans, std::max(x, y));
         if (x <= y) l = mid + 1;
         else r = mid - 1;
      }
      printf("%d\n", ans);
   }
}
int main() {
   scanf("%d%d%d", &n, &a, &b);
   for (int i = 1, x, y; i < n; i++) {
      scanf("%d%d", &x, &y);
      to[x].push_back(y), to[y].push_back(x);
   }
   q1::solve(), q2::solve();
   return 0;
}

QOJ 7362 割

APIO 2024 gyr 讲题里的。

考虑这个 \(\frac{1}{2}\) 的限制,便可以考虑成从 \(2\) 个颜色中选择 \(1\) 个,那么一个颜色的边的数量就是期望 \(\frac{1}{2}|E|\) 的。

那么怎么表示边的颜色,使其还要满足这个为割的条件。
考虑到如果只保留割上的边是一个二分图的形式。

于是考虑边权转点权,为每个点随机一个 \(0/1\) 的点权表示分在二分图中的哪个部分。
那么如果点权不相同,这个边就是割,颜色就为 \(1\)

此时因为每条边颜色为 \(0 / 1\) 的概率都是 \(\frac{1}{2}\),所以期望下能随出 \(\frac{1}{2}|E|\)

时间复杂度 \(\mathcal{O}(T(n + m))\)\(T\) 为随机次数,挺优秀的,我不太会说明。

代码
#include<bits/stdc++.h>
std::mt19937_64 rnd(std::chrono::steady_clock::now().time_since_epoch().count());
constexpr int maxn = 1e5 + 10, maxm = 2e5 + 10;
int col[maxn];
int eu[maxm], ev[maxm];
int main() {
   int n, m;
   scanf("%d%d", &n, &m);
   for (int i = 1; i <= m; i++) scanf("%d%d", &eu[i], &ev[i]);
   for (; ; ) {
      for (int i = 1; i <= n; i++) col[i] = rnd() & 1;
      int cnt = 0;
      for (int i = 1; i <= m; i++) cnt += col[eu[i]] != col[ev[i]];
      if (cnt * 2 >= m) {
         for (int i = 1; i <= n; i++) printf("%d ", col[i]);
         break;
      }
   }
   return 0;
}

QOJ 7363 忙碌的出题人

首先因为对于 \((r - l + 1)\cdot \min\limits_{i = l}^r a_i\) 这个值,\(\min a_i\) 应该对应的优先级更高。
所以可以先建出小根笛卡尔树,维护每个点的管辖区间。

接下来考虑如果求出 rk 在 \(L\sim R\) 之间的。
一个想法就是直接对于 \(x\in [L, R]\) 都直接二分这个值 \(v\),并在笛卡尔树上经过一些分讨(具体细节有点麻烦,暂且省略了,可以见代码)知道 \(\ge v\) 的数量与 \(x\) 做比较。
这样子可以做到 \(\mathcal{O}((R - L + 1)n\log V)\)

考虑如何优化。
发现对于每一个 \(x\) 都进行一次二分其实是很浪费的。
考虑继续发掘要求的值的性质,发现要求的位置是连续的。

同时考虑对于笛卡尔树的节点 \(k\),如果令这个节点的值的区间长度 \(+1\),那么权值就会 \(+k\)
也就说明,对于节点 \(k\),区间越长这个值就越大。

于是可以先考虑得到 \(L\) 处的值 \(x\),并把 \(x\)\(L\) 多的部分也都先填上。
那么接下来就考虑填 \(> x\) 的数。
此时就可以放在笛卡尔树的节点上,知道此时这个节点的值要 \(> x\) 的区间长度是多少,就可以知道这个值具体为多少并且可以填出多少个这个值了。
那么把这 \(n\) 个节点的信息放在一个优先队列里,每次取出最小的值,并令其数量 \(-1\)
如果这个值的数量为 \(0\) 了,就说明对于这个节点必须扩展其区间长度了,进行更新即可。

(为何是直接填完 \(x\) 而不是对于 \(x\) 也用优先队列做?这是因为 \(x\) 这个值的开头可能会离很远,使得要填的数的个数没有保障。)

时间复杂度 \(\mathcal{O}(n\log V + (R - L + 1)\log n)\)

代码实现不是建的笛卡尔树而是用的单调栈求的管辖范围,本质相同。

代码
#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 3e5 + 10;
int n, a[maxn];
int stk[maxn], top;
int sl[maxn], sr[maxn];
inline ll calc(ll val) {
   ll tot = 0ll;
   for (int i = 1; i <= n; i++) {
      int l = sl[i], r = sr[i];
      int k = std::min(val / a[i] - 1, 0ll + l + r);
      if (k < 0) continue;
      if (k <= l) {
         // 0 <= i <= k, k + 1 - i
         tot += 1ll * (k + 1) * (k + 1) - 1ll * (k + 1) * k / 2ll;
      } else if (k <= r) {
         // 0 <= i <= l, k + 1 - i
         tot += 1ll * (l + 1) * (k + 1) - 1ll * (l + 1) * l / 2ll;
      } else {
         // 0 <= i <= l, min(k + 1 - i, r + 1)
         int mid = std::min(k - r, l);
         tot += 1ll * (mid + 1) * (r + 1) + 1ll * (l - mid) * (k + 1) - 1ll * (l - mid) * (l + mid + 1) / 2ll;
      }
   }
   return tot;
}
std::priority_queue<std::pair<ll, int>, std::vector<std::pair<ll, int> >, std::greater<> > Q;
int len[maxn], cnt[maxn];
inline void update(int id) {
   cnt[id]--;
   if (cnt[id] <= 0) {
      len[id]++;
      if (len[id] <= sl[id]) {
         cnt[id] = len[id] + 1;
      } else if (len[id] <= sr[id]){
         cnt[id] = sl[id] + 1;
      } else if (len[id] <= sl[id] + sr[id]){
         cnt[id] = sl[id] - (len[id] - sr[id]) + 1;
      } else {
         return ;
      }
   }
   Q.emplace(1ll * a[id] * (len[id] + 1), id);
}
int main() {
   scanf("%d", &n);
   for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
   for (int i = 1; i <= n; i++) {
      sl[i] = i;
      while (top && a[i] < a[stk[top]]) {
         sl[i] = sl[stk[top--]];
      }
      stk[++top] = i;
   }
   top = 0;
   for (int i = n; i >= 1; i--) {
      sr[i] = i;
      while (top && a[i] <= a[stk[top]]) {
         sr[i] = sr[stk[top--]];
      }
      stk[++top] = i;
   }
   for (int i = 1; i <= n; i++) {
      sl[i] = i - sl[i], sr[i] = sr[i] - i;
      if (sl[i] > sr[i]) std::swap(sl[i], sr[i]);
   }
   ll qL, qR;
   scanf("%lld%lld", &qL, &qR);
   ll L = 1, R = 1e18, val = R;
   while (L <= R) {
      ll mid = L + R >> 1;
      if (calc(mid) >= qL) val = mid, R = mid - 1;
      else L = mid + 1;
   }
   ll qL_ = std::min(calc(val), qR);
   while (qL <= qL_) printf("%lld ", val), qL++;
   for (int i = 1; i <= n; i++) {
      len[i] = std::min(val / a[i] - 1ll, 0ll + sl[i] + sr[i]);
      update(i);
   }
   while (qL <= qR) {
      auto [val, id] = Q.top(); Q.pop();
      printf("%lld ", val), update(id), qL++;
   }
   return 0;
}

QOJ 7364 回文

详细题解

QOJ 7365 东方永夜抄

暂时不会这些技术,可以参考这篇这篇

posted @ 2025-03-31 21:14  rizynvu  阅读(47)  评论(0)    收藏  举报