P9338 [JOISC 2023] 合唱 题解
Description
在舞台上,有
从现在起,这些海狸将要演唱
-
至少有一只海狸唱这首歌。
-
唱这首歌的唱高音和唱低音的海狸数量应当相等。
-
如果只考虑唱这首歌的海狸,所有唱高音的海狸都在唱低音的海狸的右边。
指挥家 Bitaro 想找到一种方案,给出哪些海狸唱哪首歌,满足以上所有条件。由于 Bitaro 特别聪明,他注意到这可能无法实现。为了应对这个问题,Bitaro 将交换相邻两只海狸的位置多次,以便有一种方式可以调配海狸,从而满足上述条件。
由于 Bitaro 认为效率很重要,所以他想最小化要执行的操作数。然而,这是一个非常困难的问题。由于您是一位出色的程序员,Bitaro 请求您解决此问题。
编写一份程序,在给出合唱与演唱歌曲数量
Solution
首先由于每个方案都可以分裂,所以如果可以得到
考虑对于给定字符串,怎么求最小歌曲数。
容易发现一定是让第
如果把一个区间完全在另一个区间的右边看成一个偏序关系,上面的东西就等价于最小反链覆盖,等于最长链。这样就得到一个比较优美的贪心。
然后就可以 dp 了。
设
不妨设第
注意到
设
所以只需要考虑
时间复杂度:
Code
#include <bits/stdc++.h> #define int int64_t using i128 = __int128_t; using pii = std::pair<int, int>; const int kMaxN = 1e6 + 5; int n, k; int c[kMaxN], sumc[kMaxN], nxt[kMaxN]; int f[kMaxN], g[kMaxN], a[kMaxN], b[kMaxN]; std::string s; pii sub(pii a, pii b) { return {a.first - b.first, a.second - b.second}; } i128 mul(pii a, pii b) { return (i128)a.first * b.second - (i128)a.second * b.first; } void prework() { int cnta = 0, cntb = 0; for (int i = 1; i <= 2 * n; ++i) { if (s[i] == 'A') c[++cnta] = cntb; else ++cntb; } for (int i = 1; i <= n; ++i) sumc[i] = sumc[i - 1] + c[i]; for (int i = 1, j = 0; i <= n; ++i) { for (; j < n && c[j + 1] < i; ++j) {} nxt[i] = std::max(i, j); } } void add(pii *stk, int &top, pii p) { for (; top >= 2 && mul(sub(p, stk[top]), sub(stk[top], stk[top - 1])) >= 0; --top) {} stk[++top] = p; } int calc(pii p, int x) { return x * p.first + p.second; } std::pair<int, int> check(int x) { static int id[kMaxN]; static pii stk[kMaxN]; int top = 0; for (int i = 1, lst = -1; i <= n; ++i) { int now = std::min(i - 1, c[i]); for (; lst < now; ++lst, add(stk, top, {a[lst], b[lst]}), id[top] = lst) {} f[i] = 1e18, g[i] = 1e18; int L = 1, R = top + 1, res = 1; while (L + 1 < R) { int mid = (L + R) >> 1; if (calc(stk[mid], -i) < calc(stk[mid - 1], -i)) L = res = mid; else R = mid; } int j = id[res]; f[i] = -i * a[j] + b[j] + sumc[i] - x, g[i] = g[j] + 1; a[i] = i, b[i] = f[i] - sumc[nxt[i]] + i * nxt[i]; } return {f[n], g[n]}; } void dickdreamer() { std::cin >> n >> k >> s; s = " " + s; prework(); int L = -1e12, R = 1, res = 1; while (L + 1 < R) { int mid = (L + R) >> 1; if (check(mid).second <= k) L = res = mid; else R = mid; } auto p = check(res); std::cout << p.first + res * k << '\n'; } int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; // std::cin >> T; while (T--) dickdreamer(); // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n"; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2024-02-16 20240216 模拟赛 T2 题解