A. All Lengths Subtraction
思维。
每次选择长度为 \(k(k \in [1,n])\) 的区间减 \(1\),那么第一个首选的就是 \(a_i = n\) 的 位置,然后维护 \(n\) 所在的区间,检查 \(n-k+1\) 是否在其两边,有的话就扩大区间,否则就肯定不能实现。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;cin >> n;int l = 0, r = n;vector<int> p(n + 1);for (int i = 1; i <= n; i += 1) {cin >> p[i];if (p[i] == n) {l = r = i;}}for (int i = n - 1; i >= 1; i -= 1) {if (r + 1 <= n && p[r + 1] == i) {r += 1;}else if (l - 1 >= 1 && p[l - 1] == i) {l -= 1;}else {cout << "NO\n";return;}}cout << "YES\n";}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
B. Discounts
贪心。
由于价值 \(x\) 的劵需要购买 \(x-1\) 个最贵的,那么为了付出的钱更少白嫖的物品价值更高,那我们肯定得选择 \(x\) 小的劵去购买从大到小排序后前 \(x\) 个物品,按此方法模拟即可。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n, k;cin >> n >> k;vector<int> a(n + 1), b(k + 1);for (int i = 1; i <= n; i += 1) {cin >> a[i];}for (int i = 1; i <= k; i += 1) {cin >> b[i];}sort(a.begin() + 1, a.end());sort(b.begin() + 1, b.end());i64 ans = 0;int idx = 1;for (int i = n; i >= 1; i -= 1) {if (idx <= k && i - b[idx] + 1 >= 1) {for (int j = i; j > i - b[idx] + 1; j -= 1) {ans += a[j];}i = i - b[idx] + 1;idx += 1;}else {ans += a[i];}}cout << ans << "\n";}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
C. Max Tree
拓扑。
首先贪心的想,要想最大化贡献,那对于每一条边来说,其实就已经有了个评判标准,按照评判标准确定谁连向谁,父亲的值要大于儿子,所以只要对进入队列的点依次赋值 \(n,n-1,n-2...\) 即可。
我这里是按照贡献贪心的去判定边的连向,不过貌似多此一举了,因为只有 \(n-1\) 条边,所以只要对每条边判一下谁的贡献更大就可以确定朝向了。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;cin >> n;vector<array<int,3>> Q;for (int i = 1; i < n; i += 1) {int u, v, x, y;cin >> u >> v >> x >> y;Q.push_back({x, u, v});Q.push_back({y, v, u});}vector<int> in(n + 1);vector e(n + 1, vector<int>());set<array<int,2>> has;sort(Q.begin(), Q.end(), greater<>());for (auto &[_, u, v] : Q) {if (has.count({u, v}) || has.count({v, u})) {continue;}has.insert({u, v});in[v] += 1;e[u].push_back(v);}int now = n;vector<int> ans(n + 1);queue<int> pq;for (int i = 1; i <= n; i += 1) {if (in[i] == 0) {pq.push(i);}}while (pq.size()) {auto u = pq.front();pq.pop();ans[u] = now --;for (auto &v : e[u]) {if (!--in[v]) {pq.push(v);}}}for (int i = 1; i <= n; i += 1) {cout << ans[i] << " \n"[i == n];}}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
D1. Inversion Graph Coloring (Easy Version)
dp。
手推或者打表能发现一条规律,那就是对于 \(i<j< k\) 来说,存在 \(a_i > a_j > a_k\) 这样的序列是不合法的,因为这会导致三个点两两不同颜色,但我们最多只能涂两个颜色,即严格最长下降子序列的长度小于等于 \(2\),否则无法实现。
设 \(dp_{i,j,k}\) 表示前 \(i\) 个数中最长下降子序列的第一个数为 \(j\),第二个数为 \(k\) 的方案数。
那么当我们不选第 \(i\) 个数时,有转移:
选择第 \(i\) 个数时,有转移:
当 \(a_i < k\) 时,会形成长度为 \(3\) 的下降子序列,所以是不合法的转移。
代码中 Z 类型为取模类。
点击查看代码
using Z = ModInt<MOD[0]>;void solve() {int n;cin >> n;vector<int> a(n + 1);for (int i = 1; i <= n; i += 1) {cin >> a[i];}Z ans = 0;vector dp(n + 1, vector<Z>(n + 1));dp[0][0] = 1;for (int i = 1; i <= n; i += 1) {vector ndp(n + 1, vector<Z>(n + 1));for (int j = 0; j <= n; j += 1) {for (int k = 0; k <= j; k += 1) {ndp[j][k] += dp[j][k];if (a[i] >= j) {ndp[a[i]][k] += dp[j][k];}else if (a[i] >= k) {ndp[j][a[i]] += dp[j][k];}}}dp = move(ndp);}for (int i = 0; i <= n; i += 1) {for (int j = 0; j <= n; j += 1) {ans += dp[i][j];}}cout << ans << "\n";}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
D2. Inversion Graph Coloring (Hard Version)
数据结构优化 dp。
观察以上转移,可以发现,其实有 \(dp_{i,a_i,k} = \sum_{j=0}^{a_i}dp_{i-1,j,k},\ dp_{i,j,a_i}=\sum_{k=0}^{a_i}dp_{i-1,j,k}\) 。
这两个前缀和部分可以用树状数组维护,把 \(j、k\) 展平到二维上,就是一个维护第 \(k\) 列前 \(j\) 行的和,一个维护第 \(j\) 行前 \(k\) 列的和。
由于 \(n\le 2000\), 所以一定是要滚动的,一个方法是先把要转移的状态和值存下来放到最后转移。
如果你的树状数组不是从 \(1\) 开始的,记得偏移 \(1\) 位。
代码中 Z 是取模类。
点击查看代码
using Z = ModInt<MOD[0]>;template<typename T>
struct BIT {int n;vector<T> w;BIT() {}BIT(int n) {this->n = n;w.resize(n + 1);}void update(int x, T k) {for (; x <= n; x += x & -x) {w[x] += k;}}T ask(int x) {T ans = 0;for (; x; x -= x & -x) {ans += w[x];}return ans;}T ask(int x, int y) {return ask(y) - ask(x - 1);}
};void solve() {int n;cin >> n;vector<int> a(n + 1);for (int i = 1; i <= n; i += 1) {cin >> a[i];}Z ans = 0;vector f(2, vector(n + 1, BIT<Z>(n + 2)));f[0][0].update(1, 1);f[1][0].update(1, 1);for (int i = 1; i <= n; i += 1) {vector<tuple<int,int,Z>> st;for (int j = 0; j <= a[i]; j += 1) {Z res = f[0][j].ask(a[i] + 1);st.emplace_back(a[i], j, res);}for (int j = a[i] + 1; j <= n; j += 1) {Z res = f[1][j].ask(a[i] + 1);st.emplace_back(j, a[i], res);}for(auto &[u, v, res] : st){f[0][v].update(u + 1, res);f[1][u].update(v + 1, res);}}for (int i = 0; i <= n; i += 1) {ans += f[1][i].ask(n + 1);}cout << ans << "\n";}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}