**【T1】**Balance Beam
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 考虑一个指数暴力,首先枚举每一个位置选择操作 1 1 1 还是操作 2 2 2 。
- 记 E i E_i Ei 表示从 i i i 出发的期望收益。若在 i i i 处选择操作 2 2 2 ,那么 E i = A i E_i=A_i Ei=Ai ,否则,令 i i i 之前第一个选择操作 2 2 2 的点为 p r e pre pre ,之后第一个操作 2 2 2 的点为 s u f suf suf ,有 E i = ( i − p r e ) ∗ A s u f s u f − p r e + ( s u f − i ) ∗ A p r e s u f − p r e E_i=\frac{(i-pre)*A_{suf}}{suf-pre}+\frac{(suf-i)*A_{pre}}{suf-pre} Ei=suf−pre(i−pre)∗Asuf+suf−pre(suf−i)∗Apre ( 1 ) (1) (1) 。
- ( 1 ) (1) (1) 式的证明:
由题, E i = E i − 1 + E i + 1 2 E_i=\frac{E_{i-1}+E_{i+1}}{2} Ei=2Ei−1+Ei+1 ,即 E i + 1 − E i = E i − E i − 1 E_{i+1}-E_i=E_i-E_{i-1} Ei+1−Ei=Ei−Ei−1 , E p r e , E p r e + 1 , . . . , E s u f E_{pre},E_{pre+1},...,E_{suf} Epre,Epre+1,...,Esuf 形成一个等差数列。
而 E s u f = A s u f , E p r e = A p r e E_{suf}=A_{suf},E_{pre}=A_{pre} Esuf=Asuf,Epre=Apre ,因此 E i E_i Ei 可以由等差数列的性质计算得到。- 因此,若我们选择的一系列点为 ( i 1 , A i 1 ) , ( i 2 , A i 2 ) , . . . ( i m , A i m ) (i_1,A_{i_1}),(i_2,A_{i_2}),...(i_m,A_{i_m}) (i1,Ai1),(i2,Ai2),...(im,Aim) ,在 i 1 , i 2 i_1,i_2 i1,i2 之间的点 j j j 的 E j E_j Ej 恰好为 ( i 1 , A i 1 ) , ( i 2 , A i 2 ) (i_1,A_{i_1}),(i_2,A_{i_2}) (i1,Ai1),(i2,Ai2) 连线的横坐标为 j j j 处的纵坐标。
- 不难发现,选择一个凸壳是最优的。
- 时间复杂度 O ( N ) O(N) O(N) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; 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, q[MAXN], top; ll a[MAXN], ans[MAXN]; bool exclude(int x, int y, int z) { return (y - x) * (a[z] - a[x]) - (z - x) * (a[y] - a[x]) >= 0; } int main() { freopen("balance.in", "r", stdin); freopen("balance.out", "w", stdout); read(n); for (int i = 1; i <= n; i++) read(a[i]); q[top = 1] = 0; for (int i = 1; i <= n + 1; i++) { while (top >= 2 && exclude(q[top - 1], q[top], i)) top--; q[++top] = i; } for (int i = 2; i <= top; i++) { int l = q[i - 1], r = q[i]; for (int j = l; j <= r; j++) ans[j] = 100000 * (a[l] * (r - j) + a[r] * (j - l)) / (r - l); } for (int i = 1; i <= n; i++) printf("%lld\n", ans[i]); return 0; }
**【T2】**Sort It Out
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 问题等价于计算序列字典序第 K K K 大的最长上升子序列。
- 按照求解最长上升子序列的 d p dp dp 数组分层 d p dp dp 计数即可。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; const long long INF = 2e18; 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; ll a[MAXN]; void init(int x) { n = x; memset(a, 0, sizeof(a)); } void modify(int x, ll d) { for (int i = x; i <= n; i += i & -i) a[i] = min(a[i] + d, INF); } void clear(int x) { for (int i = x; i <= n; i += i & -i) a[i] = 0; } ll query(int x) { ll ans = 0; for (int i = x; i >= 1; i -= i & -i) ans = min(ans + a[i], INF); return ans; } } BIT; bool ans[MAXN]; int n, a[MAXN], dp[MAXN]; ll k; vector <int> pos[MAXN]; vector <ll> cnt[MAXN]; int main() { freopen("itout.in", "r", stdin); freopen("itout.out", "w", stdout); read(n), read(k); for (int i = 1; i <= n; i++) read(a[i]); static int high[MAXN], Max = 0; high[0] = n + 1; for (int i = n; i >= 1; i--) { int l = 0, r = Max; while (l < r) { int mid = (l + r + 1) / 2; if (a[i] < high[mid]) l = mid; else r = mid - 1; } dp[i] = l + 1; pos[dp[i]].push_back(i); chkmax(Max, dp[i]); chkmax(high[dp[i]], a[i]); } BIT.init(n); for (auto x : pos[1]) cnt[1].push_back(1); for (int i = 2; i <= Max; i++) { unsigned p = 0; for (auto x : pos[i]) { while (p < pos[i - 1].size() && pos[i - 1][p] > x) { BIT.modify(n - a[pos[i - 1][p]] + 1, cnt[i - 1][p]); p++; } cnt[i].push_back(BIT.query(n - a[x] + 1)); } for (auto x : pos[i - 1]) BIT.clear(n - a[x] + 1); } for (int i = 1; i <= Max; i++) { reverse(pos[i].begin(), pos[i].end()); reverse(cnt[i].begin(), cnt[i].end()); } for (int i = Max, now = 0; i >= 1; i--) { for (unsigned j = 0; j < pos[i].size(); j++) if (pos[i][j] > now && a[pos[i][j]] > a[now]) { if (cnt[i][j] < k) { k -= cnt[i][j]; continue; } now = pos[i][j]; ans[a[pos[i][j]]] = true; break; } } writeln(n - Max); for (int i = 1; i <= n; i++) if (!ans[i]) writeln(i); return 0; }
**【T3】**The Cow Gathering
【题目链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【思路要点】
- 题目中对于尚有奶牛剩余,就要求每只奶牛都有朋友的条件等价于每一次必须删除树的一个叶子。
- 对于每一条限制 ( a , b ) (a,b) (a,b) ,令 c = L c a ( a , b ) c=Lca(a,b) c=Lca(a,b) ,若 c = a c=a c=a ,那么根节点 1 1 1 到 a a a 的路径上所有的点都不能被选作根节点,否则, a a a 子树内所有的点都不能被选作根节点,可以通过树上差分解决。
- 这样的做法会漏判一类两个限制互相成环的情况,但这种情况一旦出现,所有节点都无法成为根节点,因此任选一个被判为有解的点用拓扑排序验证一下即可。
- 时间复杂度 $ 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(""); } int n, m, d[MAXN], cnt[MAXN], depth[MAXN], father[MAXN][MAXLOG]; vector <int> a[MAXN], b[MAXN]; bool ans[MAXN]; 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 get(int x, int y) { for (int i = MAXLOG - 1; i >= 0; i--) if (depth[father[y][i]] > depth[x]) y = father[y][i]; return y; } void dfs(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 (auto x : a[pos]) if (x != fa) dfs(x, pos); } void getans(int pos, int fa) { cnt[pos] += cnt[fa]; if (cnt[pos]) ans[pos] = 0; else ans[pos] = 1; for (auto x : a[pos]) if (x != fa) getans(x, pos); } void work(int pos, int fa) { for (auto x : a[pos]) if (x != fa) { work(x, pos); b[x].push_back(pos), d[pos]++; } } bool solved(int root) { work(root, 0); static int q[MAXN]; int l = 1, r = 0; for (int i = 1; i <= n; i++) if (d[i] == 0) q[++r] = i; while (l <= r) { int tmp = q[l++]; for (auto x : b[tmp]) if (--d[x] == 0) q[++r] = x; } return r == n; } bool solved() { for (int i = 1; i <= n; i++) if (ans[i]) return solved(i); return false; } int main() { freopen("gathering.in", "r", stdin); freopen("gathering.out", "w", stdout); read(n), read(m); 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); } dfs(1, 0); for (int i = 1; i <= m; i++) { int x, y; read(x), read(y); b[x].push_back(y), d[y]++; int Lca = lca(x, y); if (Lca != x) cnt[x] += 1; else cnt[1] += 1, cnt[get(x, y)] -= 1; } getans(1, 0); if (solved()) { for (int i = 1; i <= n; i++) writeln(ans[i]); } else { for (int i = 1; i <= n; i++) writeln(0); } return 0; }