**【T1】**Cow Dating
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 考虑如何快速计算一个区间 [ l , r ] [l,r] [l,r] 的价值。
- 定义二元组 ( x , y ) (x,y) (x,y) 来描述一个区间, x x x 表示区间中恰好有一个关键点的概率, y y y 表示区间中没有关键点的概率。显然,对于两个区间 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) ,上述信息是可以合并的,即 ( x 1 , y 1 ) + ( x 2 , y 2 ) = ( x 1 y 2 + x 2 y 1 , y 1 y 2 ) (x_1,y_1)+(x_2,y_2)=(x_1y_2+x_2y_1,y_1y_2) (x1,y1)+(x2,y2)=(x1y2+x2y1,y1y2) ,并且,该运算存在逆运算,即 ( x 1 , y 1 ) − ( x 2 , y 2 ) = ( x 1 − y 1 ∗ x 2 y 2 y 2 , y 1 y 2 ) (x_1,y_1)-(x_2,y_2)=(\frac{x_1-y_1*\frac{x_2}{y_2}}{y_2},\frac{y_1}{y_2}) (x1,y1)−(x2,y2)=(y2x1−y1∗y2x2,y2y1) 。
- 那么,记录二元组的前缀和数组 s i s_i si ,区间 [ l , r ] [l,r] [l,r] 对应的二元组即为 s r − s l − 1 s_r-s_{l-1} sr−sl−1 。
- 考虑枚举 r r r ,最优化 l l l 的选取,即我们需要找到 l l l ,最大化 x 1 − y 1 ∗ x 2 y 2 y 2 \frac{x_1-y_1*\frac{x_2}{y_2}}{y_2} y2x1−y1∗y2x2 。记 k = x 2 y 2 k=\frac{x_2}{y_2} k=y2x2 ,即最大化 x 1 − k ∗ y 1 x_1-k*y_1 x1−k∗y1 ,注意到 k = x 2 y 2 k=\frac{x_2}{y_2} k=y2x2 是单调的,因此采用朴素的斜率优化即可。
- 时间复杂度 O ( N ) O(N) O(N) 。
【代码】
#include
using namespace std; const int MAXN = 1e6 + 5; const long double eps = 1e-8; 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(""); } int n, l, r, st[MAXN]; ld a[MAXN], x[MAXN], y[MAXN]; bool exclude(int a, int b, int c) { return (y[b] - y[a]) * (x[c] - x[b]) - (y[c] - y[b]) * (x[b] - x[a]) > 0; } int main() { freopen("cowdate.in", "r", stdin); freopen("cowdate.out", "w", stdout); read(n), x[0] = 0, y[0] = 1; for (int i = 1; i <= n; i++) { int v; read(v); a[i] = v * 0.000001; x[i] = a[i] * y[i - 1] + (1 - a[i]) * x[i - 1]; y[i] = y[i - 1] * (1 - a[i]); } ld res = 0; for (int i = n; i >= 0; i--) { ld k = x[i] / y[i]; while (r - l + 1 >= 2 && x[st[l + 1]] - y[st[l + 1]] * k > x[st[l]] - y[st[l]] * k) l++; if (r > l) chkmax(res, (x[st[l]] - y[st[l]] * k) / y[i]); while (r - l + 1 >= 2 && exclude(st[r - 1], st[r], i)) r--; st[++r] = i; } int finalans = (res + eps) * 1000000; printf("%d\n", finalans); return 0; }
**【T2】**Moorio Kart
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 考虑用所有的路径减去不合法的路径的长度。
- 所有路径的长度是容易计算的,只需要考虑每一条边被统计了多少次。
- 不合法的路径即长度不足 Y Y Y 的路径,可以通过每一个联通块内路径的数组卷积得到。
- 注意到当且仅当联通块大小达到 O ( V ) O(\sqrt{V}) O(V) 的级别,不足 Y Y Y 的所有路径长度才有可能被全部取到,因此在卷积时特判数组中为 0 0 0 的位置跳过即可。
- 时间复杂度 O ( N V V ) O(NV\sqrt{V}) O(NVV) 。
【代码】
#include
using namespace std; const int MAXN = 3005; 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(""); } int n, m, x, y, ans, f[MAXN]; int cnt[MAXN][MAXN], res[MAXN]; vector <pair <int, int> > a[MAXN]; 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 F(int x) { if (f[x] == x) return x; else return f[x] = F(f[x]); } void update(int &x, int y) { x += y; if (x >= P) x -= P; } void calccnt(int pos, int fa, int from, int len) { if (pos > from) cnt[F(pos)][max(len, 0)]++; for (auto x : a[pos]) if (x.first != fa) calccnt(x.first, pos, from, min(len + x.second, y)); } int dfs(int pos, int fa, int out, int tot) { int res = 1; calccnt(pos, pos, pos, 0); for (auto x : a[pos]) if (x.first != fa) { int tmp = dfs(x.first, pos, out, tot); update(ans, 1ll * tmp * (tot - tmp) * out % P * x.second % P); res += tmp; } return res; } int main() { freopen("mooriokart.in", "r", stdin); freopen("mooriokart.out", "w", stdout); read(n), read(m), read(x), read(y); for (int i = 1; i <= n; i++) f[i] = i; for (int i = 1; i <= m; i++) { int x, y, z; read(x), read(y), read(z); a[x].emplace_back(y, z); a[y].emplace_back(x, z); f[F(x)] = F(y); } static int cntp[MAXN], valp[MAXN]; for (int i = 1; i <= n; i++) cntp[F(i)]++; int tmp = 1, com = 0, inv2 = power(2, P - 2); for (int i = 1; i <= n; i++) if (cntp[i]) { valp[i] = 1ll * cntp[i] * (cntp[i] - 1) % P * inv2 % P; tmp = 1ll * tmp * valp[i] % P, com++; } y -= x * com; for (int i = 1; i <= n; i++) if (cntp[i]) { dfs(i, i, 1ll * tmp * power(valp[i], P - 2) % P, cntp[i]); update(ans, 1ll * tmp * x % P); } if (y >= 0) { res[0] = 1; static int tmp[MAXN]; for (int i = 1; i <= n; i++) { if (cntp[i] == 0) continue; memset(tmp, 0, sizeof(tmp)); for (int j = 0; j <= y; j++) { int tnp = cnt[i][j]; if (tnp == 0) continue; for (int k = 0; j + k <= y; k++) update(tmp[j + k], 1ll * tnp * res[k] % P); } memcpy(res, tmp, sizeof(tmp)); } for (int i = 1; i <= y - 1; i++) update(ans, P - 1ll * res[i] * (i + x * com) % P); } for (int i = 1; i <= com - 1; i++) ans = 2ll * ans * i % P; writeln(ans); return 0; }
**【T3】**Mowing Mischief
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 将所有点按照其横坐标为第一关键字,纵坐标为第二关键字排序,靠前的点无法转移到靠后的点。用树状数组进行动态规划,我们可以得到以每个点为结尾的选取序列的最优长度,并借此对点进行分层。
- 属于同一层的点横坐标递增,纵坐标递减。
- 接下来,我们只需要考虑层与层之间的转移。
- 转移方程大致如下:
d p i = M i n j i n t h e p r e v i o u s l a y e r a n d x j ≤ x i , y j ≤ y i { d p j + ( x i − x j ) ∗ ( y i − y j ) } dp_i=Min_{j\ in\ the\ previous\ layer\ and\ x_j\leq x_i,y_j\leq y_i}\{dp_j+(x_i-x_j)*(y_i-y_j)\} dpi=Minj in the previous layer and xj≤xi,yj≤yi{dpj+(xi−xj)∗(yi−yj)}
d p i = M i n j i n t h e p r e v i o u s l a y e r a n d x j ≤ x i , y j ≤ y i { d p j + x j y j − x i y j − x j y i ) } + x i y i dp_i=Min_{j\ in\ the\ previous\ layer\ and\ x_j\leq x_i,y_j\leq y_i}\{dp_j+x_jy_j-x_iy_j-x_jy_i)\}+x_iy_i dpi=Minj in the previous layer and xj≤xi,yj≤yi{dpj+xjyj−xiyj−xjyi)}+xiyi- 考虑 j , k j,k j,k 两处转移,令 j j j 优于 k k k ,则
d p j + x j y j − x i y j − x j y i ≤ d p k + x k y k − x i y k − x k y i dp_j+x_jy_j-x_iy_j-x_jy_i\leq dp_k+x_ky_k-x_iy_k-x_ky_i dpj+xjyj−xiyj−xjyi≤dpk+xkyk−xiyk−xkyi
( y k − y j ) x i + ( x k − x j ) y i ≤ d p k + x k y k − d p j − x j y j (y_k-y_j)x_i+(x_k-x_j)y_i\leq dp_k+x_ky_k-dp_j-x_jy_j (yk−yj)xi+(xk−xj)yi≤dpk+xkyk−dpj−xjyj- 注意到 y k − y j y_k-y_j yk−yj 和 x k − x j x_k-x_j xk−xj 必然一正一负,因此这个半平面是一个斜率为正的半平面,一定会将下一层的点分为前后两部分,因而若排除掉 x j ≤ x i , y j ≤ y i x_j\leq x_i,y_j\leq y_i xj≤xi,yj≤yi 的限制,该 d p dp dp 的转移具有决策单调性。
- 那么,先用线段树分治处理转移,再用决策单调性优化转移即可。
- 时间复杂度 O ( N L o g 2 N ) O(NLog^2N) O(NLog2N) 。
【代码】
#include
using namespace std; const int MAXN = 3e5 + 5; const int MAXV = 1e6 + 5; const long long INF = 1e18; 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 BinaryIndexTree { int n, a[MAXV]; void init(int x) { n = x; memset(a, 0, sizeof(a)); } void modify(int x, int d) { for (int i = x; i <= n; i += i & -i) chkmax(a[i], d); } int query(int x) { int ans = 0; for (int i = x; i >= 1; i -= i & -i) chkmax(ans, a[i]); return ans; } } BIT; int n, m, t, s, layer[MAXN]; ll dp[MAXN]; pair <int, int> p[MAXN]; vector <int> pos[MAXN]; struct SegmentTree { struct Node { int lc, rc; vector <int> trans; } a[MAXN * 2]; int n, size, root; vector <int> point, tmp; void build(int &root, int l, int r) { root = ++size; a[root].lc = a[root].rc = 0; a[root].trans.clear(); if (l == r) return; int mid = (l + r) / 2; build(a[root].lc, l, mid); build(a[root].rc, mid + 1, r); } void init(vector <int> tmp) { point = tmp; n = tmp.size(); root = size = 0; build(root, 0, n - 1); } void modify(int root, int l, int r, int x) { if (p[x].first >= p[point[r]].first && p[x].second >= p[point[l]].second) { a[root].trans.push_back(x); return; } if (p[x].first <= p[point[l]].first || p[x].second <= p[point[r]].second) return; int mid = (l + r) / 2; modify(a[root].lc, l, mid, x); modify(a[root].rc, mid + 1, r, x); } void modify(int x) { modify(root, 0, n - 1, x); } void solve(int l, int r, int ql, int qr) { ll ans = INF; int from = 0; int mid = (l + r) / 2, now = tmp[mid]; for (int i = ql; i <= qr; i++) { int pos = point[i]; ll res = dp[pos] + 1ll * (p[now].first - p[pos].first) * (p[now].second - p[pos].second); if (res < ans) { ans = res; from = i; } } chkmin(dp[now], ans); if (l < mid) solve(l, mid - 1, from, qr); if (mid < r) solve(mid + 1, r, ql, from); } void work(int root, int l, int r) { if (a[root].trans.size()) { tmp = a[root].trans; solve(0, tmp.size() - 1, l, r); } if (l == r) return; int mid = (l + r) / 2; work(a[root].lc, l, mid); work(a[root].rc, mid + 1, r); } void work() { work(root, 0, n - 1); } } ST; int main() { freopen("mowing.in", "r", stdin); freopen("mowing.out", "w", stdout); read(n), read(t); for (int i = 1; i <= n; i++) read(p[i].first), read(p[i].second); sort(p + 1, p + n + 1), BIT.init(t); for (int i = 1; i <= n; i++) { layer[i] = BIT.query(p[i].second) + 1; BIT.modify(p[i].second, layer[i]); pos[layer[i]].push_back(i); chkmax(m, layer[i]); } for (auto x : pos[1]) dp[x] = 1ll * p[x].first * p[x].second; for (int i = 2; i <= m; i++) { ST.init(pos[i - 1]); for (auto x : pos[i]) { dp[x] = INF; ST.modify(x); } ST.work(); } ll ans = INF; for (auto x : pos[m]) chkmin(ans, dp[x] + 1ll * (t - p[x].first) * (t - p[x].second)); writeln(ans); return 0; }