**【T1】**Redistricting
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 将 G G G 看做 + 1 +1 +1 , H H H 看做 − 1 -1 −1 ,记原数组前缀和为 s i s_i si 。
- 则可以得到动态规划 d p i = ∑ j = 1 k d p i − j + [ s i − s i − j ≥ 0 ] dp_{i}=\sum_{j=1}^{k}dp_{i-j}+[s_i-s_{i-j}≥0] dpi=∑j=1kdpi−j+[si−si−j≥0] 。
- 用线段树 + + + 堆维护最近的 k k k 个位置的 d p dp dp 值,在转移时枚举 s i − s i − j ≥ 0 s_i-s_{i-j}≥0 si−si−j≥0 是否成立,查询对应 s s s 的可行区间的区间最小值即可。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 3e5 + 5; const int INF = 2e9; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct Heap { priority_queue <int, vector <int>, greater <int> > Main, Delt; void ins(int x) {Main.push(x); } void del(int x) {Delt.push(x); } int top() { while (!Main.empty() && !Delt.empty() && Main.top() == Delt.top()) { Main.pop(); Delt.pop(); } if (Main.empty()) return INF; else return Main.top(); } } Heap[MAXN * 2]; struct SegmentTree { struct Node { int lc, rc; int Min; } a[MAXN * 4]; int n, root, size; void update(int root) { a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min); } void build(int &root, int l, int r) { root = ++size; if (l == r) { a[root].Min = INF; return; } int mid = (l + r) / 2; build(a[root].lc, l, mid); build(a[root].rc, mid + 1, r); update(root); } void init(int x) { n = x, root = size = 0; build(root, 1, n); } void del(int root, int l, int r, int pos, int val) { if (l == r) { Heap[l].del(val); a[root].Min = Heap[l].top(); return; } int mid = (l + r) / 2; if (mid >= pos) del(a[root].lc, l, mid, pos, val); else del(a[root].rc, mid + 1, r, pos, val); update(root); } void del(int pos, int val) { del(root, 1, n, pos, val); } void add(int root, int l, int r, int pos, int val) { if (l == r) { Heap[l].ins(val); a[root].Min = Heap[l].top(); return; } int mid = (l + r) / 2; if (mid >= pos) add(a[root].lc, l, mid, pos, val); else add(a[root].rc, mid + 1, r, pos, val); update(root); } void add(int pos, int val) { add(root, 1, n, pos, val); } int query(int root, int l, int r, int ql, int qr) { if (l == ql && r == qr) return a[root].Min; int ans = INF, mid = (l + r) / 2; if (mid >= ql) chkmin(ans, query(a[root].lc, l, mid, ql, min(qr, mid))); if (mid + 1 <= qr) chkmin(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr)); return ans; } int query(int l, int r) { if (l > r) return INF; else return query(root, 1, n, l, r); } } ST; char s[MAXN]; int n, k, dp[MAXN], sum[MAXN]; int main() { freopen("redistricting.in", "r", stdin); freopen("redistricting.out", "w", stdout); read(n), read(k); scanf("\n%s", s + 1); sum[0] = n; for (int i = 1; i <= n; i++) { sum[i] = sum[i - 1]; if (s[i] == 'G') sum[i]++; else sum[i]--; } ST.init(2 * n); ST.add(sum[0], dp[0]); for (int i = 1; i <= n; i++) { dp[i] = min(ST.query(1, sum[i]) + 1, ST.query(sum[i] + 1, 2 * n)); ST.add(sum[i], dp[i]); if (i >= k) ST.del(sum[i - k], dp[i - k]); } writeln(dp[n]); return 0; }
**【T2】**Exercise Route
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 问题等价于计算边相交的树上路径对数。
- 注意到两条路径的交或为空,或为一条路径,考虑枚举该路径上深度最小的一条边 x x x ,记其上方一条边为 y y y ,包含 x x x 的路径数为 c n t x cnt_x cntx ,同时包含 x , y x,y x,y 的路径数为 c n t x , y cnt_{x,y} cntx,y 。则以 x x x 为相交路径上深度最小的一条边的路径对数为 ( c n t x 2 ) − ( c n t x , y 2 ) \binom{cnt_x}{2}-\binom{cnt_{x,y}}{2} (2cntx)−(2cntx,y) 。 c n t x , c n t x , y cnt_x,cnt_{x,y} cntx,cntx,y 可以通过树上差分得到。
- 注意到一条路径可能存在两条深度最小的边,这种情况会被上述算法统计两次,因此我们需要将其多余计算的部分去除。即在两条路径的 L c a Lca Lca 处统计答案,两端所属的子树相同的路径可能形成这样的交,简单统计即可。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; const int MAXLOG = 20; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } vector <int> a[MAXN]; ll ans; vector <pair <int, int> > b[MAXN]; int cnt[MAXN], exclude[MAXN]; int depth[MAXN], father[MAXN][MAXLOG]; void work(int pos, int fa) { depth[pos] = depth[fa] + 1; father[pos][0] = fa; for (int i = 1; i < MAXLOG; i++) father[pos][i] = father[father[pos][i - 1]][i - 1]; for (unsigned i = 0; i < a[pos].size(); i++) if (a[pos][i] != fa) work(a[pos][i], pos); } int lca(int x, int y) { if (depth[x] < depth[y]) swap(x, y); for (int i = MAXLOG - 1; i >= 0; i--) if (depth[father[x][i]] >= depth[y]) x = father[x][i]; if (x == y) return x; for (int i = MAXLOG - 1; i >= 0; i--) if (father[x][i] != father[y][i]) { x = father[x][i]; y = father[y][i]; } return father[x][0]; } int jump(int x, int y) { for (int i = MAXLOG - 1; i >= 0; i--) if (depth[father[x][i]] > depth[y]) x = father[x][i]; return x; } void getans(int pos, int fa) { for (auto x : a[pos]) if (x != fa) { getans(x, pos); cnt[pos] += cnt[x]; exclude[pos] += exclude[x]; } ans += (cnt[pos] - 1ll) * cnt[pos] / 2; ans -= (exclude[pos] - 1ll) * exclude[pos] / 2; } int main() { freopen("exercise.in", "r", stdin); freopen("exercise.out", "w", stdout); int n, m; read(n), read(m), m -= n - 1; for (int i = 1; i <= n - 1; i++) { int x, y; read(x), read(y); a[x].push_back(y); a[y].push_back(x); } work(1, 0); for (int i = 1; i <= m; i++) { int x, y, z; read(x), read(y), z = lca(x, y); cnt[x]++, cnt[y]++, cnt[z] -= 2; exclude[x]++, exclude[jump(x, z)]--; exclude[y]++, exclude[jump(y, z)]--; if (x != z && y != z) b[z].emplace_back(x, y); } getans(1, 0); for (int i = 1; i <= n; i++) { map <pair <int, int>, int> exclude; for (auto c : b[i]) { int x = jump(c.first, i); int y = jump(c.second, i); if (x > y) swap(x, y); exclude[make_pair(x, y)]++; } for (auto x : exclude) ans -= (x.second - 1ll) * x.second / 2; } writeln(ans); return 0; }
**【T3】**Train Tracking 2
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 考虑找到一个区间最大值的位置,将问题递归至两边处理。
- 若当前区间 [ l , r ] [l,r] [l,r] 对应的 a l , a l + 1 , a l + 2 , . . . , a r − k + 1 a_l,a_{l+1},a_{l+2},...,a_{r-k+1} al,al+1,al+2,...,ar−k+1 均相等,那么问题显然可以用动态规划解决,即保证 [ l , r ] [l,r] [l,r] 中所有数都不超过最大值,且每连续的 k k k 个数中均存在一个最大值。
- 否则,我们一定可以找到一个确定的位置,使得其一定是区间最大值。
- 具体来说,令 M a x = m a x { a l , a l + 1 , a l + 2 , . . . , a r − k + 1 } Max=max\{a_l,a_{l+1},a_{l+2},...,a_{r-k+1}\} Max=max{al,al+1,al+2,...,ar−k+1} ,若 a l = M a x a_l=Max al=Max ,则表明 [ l , l + k − 1 ] [l,l+k-1] [l,l+k−1] 中存在区间最大值,我们找到第一个使得 a p o s < M a x a_{pos}<Max apos<Max 的 p o s pos pos ,那么 p o s − 1 pos-1 pos−1 处的值一定是区间最大值 M a x Max Max 。
- 否则,即 a l < M a x a_l<Max al<Max ,那么我们可以找到第一个使得 a p o s = M a x a_{pos}=Max apos=Max 的 p o s pos pos , p o s + k − 1 pos+k-1 pos+k−1 处的值一定是区间最大值 M a x Max Max 。
- 递归至两边处理即可。
- 具体实现时需要使用 S T ST ST 表。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; const int P = 1e9 + 7; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } namespace rmq { const int MAXN = 1e5 + 5; const int MAXLOG = 18; int Max[MAXN][MAXLOG], Min[MAXN][MAXLOG], Log[MAXN]; int queryMax(int l, int r) { int len = r - l + 1, tmp = Log[len]; return max(Max[l][tmp], Max[r - (1 << tmp) + 1][tmp]); } int queryMin(int l, int r) { int len = r - l + 1, tmp = Log[len]; return min(Min[l][tmp], Min[r - (1 << tmp) + 1][tmp]); } void init(int *a, int n) { for (int i = 1; i <= n; i++) { Min[i][0] = a[i]; Max[i][0] = a[i]; Log[i] = Log[i - 1]; if ((1 << (Log[i] + 1)) <= i) Log[i]++; } for (int t = 1; t < MAXLOG; t++) for (int i = 1, j = (1 << (t - 1)) + 1; j <= n; i++, j++) { Max[i][t] = max(Max[i][t - 1], Max[j][t - 1]); Min[i][t] = min(Min[i][t - 1], Min[j][t - 1]); } } } int power(int x, int y) { if (y == 0) return 1; int tmp = power(x, y / 2); if (y % 2 == 0) return 1ll * tmp * tmp % P; else return 1ll * tmp * tmp % P * x % P; } int n, k, a[MAXN]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } int same(int len, int Max) { static int dp[MAXN]; int sum = 0; dp[0] = 1; for (int i = 1; i <= len; i++) { sum = 1ll * sum * Max % P; update(sum, dp[i - 1]); if (i - k - 1 >= 0) update(sum, P - 1ll * dp[i - k - 1] * power(Max, k) % P); dp[i] = sum; } int ans = 0; for (int i = 0; i <= len && i <= k - 1; i++) update(ans, 1ll * dp[len - i] * power(Max, i) % P); return ans; } int solve(int l, int r, int RMax) { int len = r - l + 1; if (len < k) return power(RMax + 1, len); int Max = rmq :: queryMax(l, r - k + 1); int Min = rmq :: queryMin(l, r - k + 1); if (Max == Min) return same(len, Max); if (a[l] == Max) { int ql = l, qr = r - k + 1; while (ql < qr) { int mid = (ql + qr) / 2; if (rmq :: queryMin(l, mid) != Max) qr = mid; else ql = mid + 1; } int pos = ql - 1; return 1ll * solve(l, pos - 1, Max) * solve(pos + 1, r, Max) % P; } else { int ql = l, qr = r - k + 1; while (ql < qr) { int mid = (ql + qr) / 2; if (rmq :: queryMax(l, mid) == Max) qr = mid; else ql = mid + 1; } int pos = ql + k - 1; return 1ll * solve(l, pos - 1, Max) * solve(pos + 1, r, Max) % P; } } int main() { freopen("tracking2.in", "r", stdin); freopen("tracking2.out", "w", stdout); read(n), read(k); for (int i = 1; i <= n - k + 1; i++) { read(a[i]); a[i] = 1e9 - a[i]; } rmq :: init(a, n - k + 1); writeln(solve(1, n, 1e9)); return 0; }