T1
前缀后缀
首先 \(q\) 的数据范围是在搞笑,因为最多 \(n\) 次操作之后序列就没了。然后可以考虑 \(f_{l, r}\) 表示还剩 \([l, r]\) 时最多执行到了哪个操作。转移考虑下一个操作在左边做还是在右边做即可。可以对每个询问预处理出每个点左右第一个能接这个询问的点。时间复杂度 \(\mathcal{O}(n^2)\)。
代码
#include <iostream>
using namespace std;
inline void Cmax(int& x, int y) { x = max(x, y); }
bool bg;
int n, q;
int mn[5005][5005];
int d[5005], s[5005];
int tor[5005][5005], tol[5005][5005];
int dp[5005][5005];
int a[5005];
bool ed;
int main() {cerr << (&ed - &bg) / 1024.0 / 1024.0 << "\n";freopen("presuf.in", "r", stdin);freopen("presuf.out", "w", stdout);cin >> n >> q, q = min(n, q);for (int i = 1; i <= n; i++) cin >> a[i], mn[i][i] = a[i];for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) mn[i][j] = min(mn[i][j - 1], a[j]);}for (int i = 1; i <= q; i++) cin >> d[i] >> s[i];for (int j = 1; j <= q; j++) {for (int i = d[j]; i <= n; i++) tol[i][j] = ((mn[i - d[j] + 1][i] >= s[j]) ? i : tol[i - 1][j]);tor[n + 1][j] = n + 1;for (int i = n; i; i--) tor[i][j] = ((i + d[j] - 1 <= n && mn[i][i + d[j] - 1] >= s[j]) ? i : tor[i + 1][j]);}int ans = 0;for (int d = n; d; d--) {for (int l = 1, r = d; r <= n; l++, ++r) {int v = dp[l][r] + 1;if (tor[l][v] + ::d[v] - 1 <= r) Cmax(dp[tor[l][v] + ::d[v]][r], v);if (tol[r][v] - ::d[v] + 1 >= l) Cmax(dp[l][tol[r][v] - ::d[v]], v);}}for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) Cmax(ans, dp[i][j]);}cout << ans << "\n";return 0;
}
T2
拉起窗帘
首先容易发现最优策略一定是先把所有的窗帘都降到一个高度之下,然后直接开电动。这样对每一个限高就会存在一个最优的阈值。设限高为 \(r\),阈值为 \(b\) 时的答案为 \(f(r, b)\),注意到其由两部分组成,第一部分是手动把最大的砍下来的时间,第二部分是自动的时间。会发现当限高 \(r\) 固定时,随着 \(b\) 的增加,两部分都是下凸函数,因此它们的和也是下凸函数。到这里就可以直接二分做了,复杂度就是单 \(\log\),但是如果打表就会发现这个下凸的极值点随着 \(r\) 的增加存在决策单调性。因此可以直接维护两个指针扫,复杂度就是 \(\mathcal{O}(n)\)。
代码
#include <iostream>
#define int long long
using namespace std;
int n, t, s, K, q;
int cnt[100005], ans[100005], pre[100005], suf[100005];
int Suf[100005], a[100005];
int rt[100005]; // time required for auto to decrease i centimeters
// required height, border
int calc(int r, int b) { return t * (Suf[b] - b * suf[b]) + rt[b - r]; }
signed main() {freopen("curtain.in", "r", stdin);freopen("curtain.out", "w", stdout);cin >> n >> t >> s >> K;for (int i = 1; i <= n; i++) cin >> a[i], cnt[a[i]]++, Suf[a[i]] += a[i];pre[0] = cnt[0];for (int i = 1; i <= 100000; i++) pre[i] = pre[i - 1] + cnt[i];for (int i = 1; i <= 100000; i++) rt[i] = rt[i - 1] + s + K * (pre[i - 1]);for (int i = 100000; ~i; i--) suf[i] = suf[i + 1] + cnt[i], Suf[i] += Suf[i + 1];for (int i = 0; i <= 100000; i++) {ans[i] = (i ? ans[i - 1] : 0);ans[i] = max(ans[i], i);while (ans[i] < 100000 && calc(i, ans[i]) > calc(i, ans[i] + 1)) ++ans[i];}cin >> q;for (int i = 1, x; i <= q; i++) {cin >> x;cout << calc(x, ans[x]) << " ";}cout << "\n";return 0;
}
T3
直径
不难想到构造一个中心点,然后向外连 \(k\) 条边,再在这 \(k\) 条边的另一个端点处各挂一个菊花。这样可以很方便的控制直径的数量:若 \(k = 2\),则直径条数即为两个外挂菊花的叶子数之积。对于 \(k = 3\) 也是同理。通过打表可以发现 \(k = 2, 3\) 即可覆盖 \(n \le 5 \times 10^6\) 的所有情况,因此直接就做完了。
代码
#include <iostream>
using namespace std;
bool v[5000005];
int n;
int main() {freopen("diameter.in", "r", stdin);freopen("diameter.out", "w", stdout);// for (int i = 1; i <= 4996; i++) {// for (int j = i; i + j <= 4996; j++) {// for (int k = j; i + j + k <= 4996; k++) {// if (i * j <= 5000000) // v[i * j] = 1;// if (i * j + j * k + k * i <= 5000000) // v[i * j + j * k + k * i] = 1;// }// }// }// for (int i = 5000; i <= 5000000; i++) {// if (!v[i]) // cout << i << " ";// }// cout << "\n";cin >> n;for (int i = 1; i <= 4998; i++) {for (int j = 1; i + j <= 4998; j++) {if (i * j == n) {cout << i + j + 2 << "\n";cout << "1 2 1\n";for (int a = 1; a <= i; a++) cout << 1 << " " << a + 2 << " 1\n";for (int b = 1; b <= j; b++) cout << 2 << " " << b + i + 2 << " 1\n";return 0;}}}for (int i = 1; i <= 4996; i++) {for (int j = 1; i * j < n && i + j <= 4996; j++) {if ((n - i * j) % (i + j) != 0) continue;int k = (n - i * j) / (i + j);if (i + j + k <= 4996) {cout << i + j + k + 4 << "\n";cout << "1 2 1\n";cout << "1 3 1\n";cout << "1 4 1\n";for (int a = 1; a <= i; a++) cout << 2 << " " << a + 4 << " 1\n";for (int b = 1; b <= j; b++) cout << 3 << " " << b + i + 4 << " 1\n";for (int c = 1; c <= k; c++) cout << 4 << " " << c + i + j + 4 << " 1\n";return 0;}}}return 0;
}
T4
洗澡
首先把人按照 \(t_i\) 排序。先考虑 \(m = 1\),则对于每个 \(s_i\),有显然的下界 \(\max\limits_{1 \le j \le i} \{ t_j + (i - j) \times T \}\)。然后会发现这个下界总是能取到。感性理解的话可以考虑每次等待一定形成一条链,然后每条链的开头会给这条链当中的所有人提供这样的一个下界。因此这个一定可以取到。因此就是要求 \(\sum\limits_{i = 1}^n iT - t_i - \min\limits_{1 \le j \le i} \{ jT - t_j \}\)。前面那个是好搞的,考虑后面的。这就相当于一个求前缀 \(\min\) 的和。发现在 \(i\) 的时间更改之后他的位置可能发生变化。设原位置为 \(x\),新位置为 \(y\)。接下来只讨论 \(x < y\) 的情况,其他是类似的。
我们首先从第一个数开始跳,每次跳到后面第一个小于自己的数,直到下一个数在 \(x\) 之后。然后用当前的前缀最小值跳到 \([x + 1, y - 1]\) 这个区间当中,然后在这个区间里面往后跳,直到下一个数在 \(y\) 之后。然后判断当前前缀最小值和新插入的数的大小关系,再跳到 \([y + 1, n]\) 并跳到最后。这个跳的过程显然可以用倍增优化,要特殊处理的位置只有两个。于是 \(m = 1\) 就做完了。实现上,会发现我们要做的事就是拿着一个前缀最小值跳到区间 \([l, r]\) 中,然后要知道跳出来之后的前缀最小值以及区间内的前缀最小值的和。可以封装为一个函数 \(f(l, r, x)\),这样写起来更方便。
然后考虑 \(m > 1\),发现实际上是把所有人按照 \(\bmod m\) 分组,同一组内是 \(m = 1\) 的子问题,而原问题的答案是所有子问题答案的和。于是对每个模 \(m\) 的同余类开一个上面说的东西维护即可。这个时候就会出现不同等价类中的区间相互轮换的情况,需要特别注意。
代码
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int inf = 0x7ffffffffffffff;
ostream& operator<<(ostream& out, __int128 x) {if (x == 0) {out << "0";return out;}bool neg = (x < 0);x = (x < 0) ? -x : x;string str;while (x) str += (char)('0' + x % 10), x /= 10;reverse(str.begin(), str.end());if (neg) str = '-' + str;out << str;return out;
}
int n, m, q, T;
int a[1000005], stk[1000005], sz;
struct Data_Structure {int r, n;__int128 ans;vector<signed> tor[20];vector<__int128> a, val[20];void ini(int x) {r = x, n = a.size();a.emplace_back(inf);for (__int128 i = 0, c = inf; i < n; i++) a[i] = i * T - a[i], c = min(c, a[i]), ans += c;for (int i = 0; i < 20; i++) val[i].resize(n + 1), tor[i].resize(n + 1);tor[0][n] = stk[sz = 0] = n;for (int i = n - 1; ~i; i--) {while (sz && a[stk[sz]] > a[i]) --sz;val[0][i] = a[i] * (stk[sz] - i), tor[0][i] = stk[sz];stk[++sz] = i;}for (int i = 1; i < 20; i++) {for (int j = 0; j <= n; j++) {tor[i][j] = tor[i - 1][tor[i - 1][j]];val[i][j] = val[i - 1][j] + val[i - 1][tor[i - 1][j]];}}}pair<__int128, __int128> f(int L, int R, __int128 x, int v = 0) {L = L / m + (L % m > r);R = R / m - (R % m < r);if (L > R) return make_pair(x, (R - L + 1) * v * T);int q = L, p = R + 1;if (a[L] <= x) p = L;else {for (int i = 19; ~i; i--) {if (tor[i][q] > R) continue;if (a[tor[i][q]] <= x) p = tor[i][q];else q = tor[i][q];}}__int128 ret;ret = (p - L) * x;for (int i = 19; ~i; i--) {if (tor[i][p] <= R) ret += val[i][p], p = tor[i][p];}ret += (R - p + 1) * a[p];ret += (R - L + 1) * v * T;return make_pair(min(x, p <= R ? a[p] : inf), ret);}
} ds[5];
__int128 bans;
int V[5];
void fv(__int128& s, __int128& p, __int128 v) { (p <= v) ? (s += p) : (p = v, s += v); }
signed main() {cin >> n >> m >> q >> T;for (int i = 0; i < n; i++) cin >> a[i], ds[i % m].a.emplace_back(a[i]), bans += (i / m) * T - a[i];for (int i = 0; i < m; i++) ds[i].ini(i);while (q--) {int x, v;__int128 tmp = bans, c;__int128 ans = 0;cin >> x >> v, --x;bans += a[x], bans -= v;int y = lower_bound(a, a + n, v) - a;y -= (y > x);if (x == y) {for (int i = 0; i < m; i++) x % m != i ? (ans += ds[i].ans) : 0;pair<__int128, __int128> p = ds[x % m].f(0, x - 1, inf);c = p.first, ans += p.second;fv(ans, c, (x / m) * T - v);p = ds[x % m].f(x + 1, n - 1, c);ans += p.second;} else if (x < y) {for (int i = 0; i < m - 1; i++) {pair<__int128, __int128> p = ds[i].f(0, x - 1, inf); ans += p.second;p = ds[i + 1].f(x + 1, y, p.first); c = p.first; ans += p.second;if (y % m == i) fv(ans, c, (y / m) * T - v);p = ds[i].f(y + 1, n - 1, c); ans += p.second;}pair<__int128, __int128> p = ds[m - 1].f(0, x - 1, inf); ans += p.second;p = ds[0].f(x + 1, y, p.first + T); c = p.first; ans += p.second;if (y % m == m - 1) fv(ans, c, (y / m + 1) * T - v);p = ds[m - 1].f(y + 1, n - 1, c - T); ans += p.second;ans -= T * ((y + 1) / m - x / m);} else {for (int i = 1; i < m; i++) {pair<__int128, __int128> p = ds[i].f(0, y - 1, inf); c = p.first, ans += p.second;if (y % m == i) fv(ans, c, (y / m) * T - v);p = ds[i - 1].f(y, x - 1, c); ans += p.second;p = ds[i].f(x + 1, n - 1, p.first); ans += p.second;}pair<__int128, __int128> p = ds[0].f(0, y - 1, inf); c = p.first, ans += p.second;if (y % m == 0) fv(ans, c, (y / m) * T - v);p = ds[m - 1].f(y, x - 1, c - T, 1); ans += p.second;p = ds[0].f(x + 1, n - 1, p.first + T); ans += p.second;}cout << bans - ans << "\n";bans = tmp;}return 0;
}
T1 想好久不会,后来会的时候感觉唐完了。T3 一开始想到打表验证但是感觉太扯淡,后来打表发现确实是对的。
打表出奇迹。