A. All Lengths Subtraction
题意:一个排列,对于每个\(k \in [1, n]\),你都要选择一个长度为\(k\)的子数组使得它们都减一,求有没有方案使得最终所有数都是\(0\)。
考虑\(k\)从大到小,发现做\(n\)的时候\(1\)变成\(0\),此时如果\(1\)不在两端,则\(k = n - 1\)时必然再把这个位置减一变成\(-1\),所以\(1\)应该在两端。
因为\(1\)在两端,所以都减一后舍去\(1\)的位置其它\(n-1\)个位置是一个\(n-1\)的排列,同理判断。
于是从小到大看数,最小的数必须在两端。
点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n;std::cin >> n;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}int l = 0, r = n - 1;for (int i = 1; i <= n; ++ i) {if (a[l] == i) {++ l;} else if (a[r] == i) {-- r;} else {std::cout << "NO\n";return;}}std::cout << "YES\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}
B. Discounts
题意:\(n\)个物品\(a\),你有\(k\)个优惠券\(b\),\(b_i\)的优惠券可以选择\(b_i\)个\(a\),然后其中的最小值免费。求每个物品都恰好购买一次且每个优惠券最多用一次的最小代价。
同一组物品,\(b_i\)越小优惠越大。
那么\(a\)从大到小排序,\(b\)从小到大排序,每次\(b_i\)个一组,免费其中最小的。
点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n, k;std::cin >> n >> k;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}std::vector<int> b(k);for (int i = 0; i < k; ++ i) {std::cin >> b[i];}std::ranges::sort(a, std::greater<>());std::ranges::sort(b);b.push_back(n + 1);i64 ans = 0;for (int i = 0, j = 0; i < n;) {if (n - i < b[j]) {ans += a[i];++ i;} else {for (int l = i; l < i + b[j] - 1; ++ l) {ans += a[l];}i += b[j];j ++ ;}}std::cout << ans << "\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}
C. Max Tree
题意:一棵无向树,每条边\((u, v)\)有\(x, y\)两个权值,你要给出一个排列\(p\),如果\(p_u > p_v\)则获得\(x\)的价值,否则获得\(y\)的价值。求所有方案的边权和的最大值。
结论是每条边一定可以取最大值。
如果只有两个点,显然可以。如果第\(i\)次加入一个点\(u\),如果它更大可以取到较大值,那么可以使得\(p_u = i\),如果他应该更小;则使得前\(i-1\)个点都加一,\(p_u = 1\)。
那么每条边应该是使得价值小的点向另一个点连边。做拓扑排序,从小到大给值。
点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n;std::cin >> n;std::vector<int> in(n);std::vector<std::vector<int>> adj(n);for (int i = 1; i < n; ++ i) {int u, v, x, y;std::cin >> u >> v >> x >> y;-- u, -- v;if (x <= y) {adj[u].push_back(v);++ in[v];} else {adj[v].push_back(u);++ in[u];}}std::queue<int> q;for (int i = 0; i < n; ++ i) {if (in[i] == 0) {q.push(i);}}std::vector<int> ans(n);int x = 1;while (q.size()) {int u = q.front(); q.pop();ans[u] = x ++ ;for (auto & v : adj[u]) {if ( -- in[v] == 0) {q.push(v);}}}for (int i = 0; i < n; ++ i) {std::cout << ans[i] << " \n"[i == n - 1];}
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}
D1. Inversion Graph Coloring (Easy Version)
题意:一个数组是好的,那么存在一种填色方案,使得每个位置颜色为蓝色或红色,且任意\(i > j\)且\(a_i > a_j\)的两个位置颜色不同。给你一个数组,求其有多少子数组是好的。\(n \leq 300\)。
手玩一下发现,如果有\(i > j > k\),且\(a_i > a_j > a_k\)必然无法填色。
那么最长严格下降子序列的长度小于等于\(2\)的数组是好的。
考虑求最长上升子序列的过程,最长下降子序列也是一样的。即\(b_i\)表示一个前缀长度为\(i\)的下降子序列的末尾最大值。这里\(i\leq 2\)。
那么记\(f[i][x][y](x \geq y)\)表示\(b_1 = x, b_2 = y\)的方案数。
此时不放\(a_i\),有\(f[i][x][y] += f[i - 1][x][y]\)。
如果放\(a_i\),如果\(a_i \geq x\),则\(f[i][a_i][y] += f[i-1][x][y]\),如果\(y \leq a_i < x\),则\(f[i][x][a_i] += f[i - 1][x][y]\),如果\(a_i < y\),则会产生长度为\(3\)的下降子序列,不能转移。
初始化\(f[0][0][0] = 1\)。答案就是\(\sum_{x=0}^{n} \sum_{y=0}^{x} f[n][x][y]\)。时间复杂度\(O(n^3)\)。用滚动数组可以把空间优化到\(O(n^2)\)。
代码省略取模类。z
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;void solve() {int n;std::cin >> n;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}std::vector f(n + 1, std::vector<Z>(n + 1));f[0][0] = 1;for (int i = 0; i < n; ++ i) {std::vector g(n + 1, std::vector<Z>(n + 1));for (int x = 0; x <= n; ++ x) {for (int y = 0; y <= x; ++ y) {g[x][y] += f[x][y];int nx = x, ny = y;if (a[i] >= x) {nx = a[i];ny = y;} else if (a[i] >= y) {nx = x;ny = a[i];} else {continue;}if (nx < ny) {std::swap(nx, ny);}g[nx][ny] += f[x][y];}}f = g;}Z ans = 0;for (int i = 0; i <= n; ++ i) {for (int j = 0; j <= n; ++ j) {ans += f[i][j];}}std::cout << ans << "\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}
D2. Inversion Graph Coloring (Hard Version)
题意:和\(d1\)一样,不过\(n \leq 2000\)。
考虑优化,发现对于\(a_i\),只会转移到\(f[i][a_i][y], (y \leq a_i)\)和\(f[i][x][a_i],(a_i < x)\)。那么是\(f[a_i][y] = \sum_{x=0}^{a_i} f[x][y]\),\(f[x][a_i] = \sum_{y = 0}^{a_i} f[x][y]\)。
可以用树状数组优化,记\(f[x]\)表示\(b_1 = x\)时对应\(b_2 \in [0, n]\)的值,其中\(f[x]\)是一个树状数组,同理\(g[y]\)表示\(b_2 = y\)时\(b_2 \in [0, n]\)的值,那么对于\(y \in [0, a_i]\),\(g[y][a_i] += g[y].sum(y, a_i)\),对于\(x \in [a_i + 1, n]\),\(f[x][a_i] += f[x].sum(1, a_i)\)。
这样后,我们还需要更新\(f[a_i]\)和\(g[a_i]\)的值,那么当\(b_1 = a_i\)的时候,\(y \in [1, a_i]\)的\(f[a_i][y] += g[y].sum(y, a_i)\),同理对于\(x \in [a_i + 1, n]\),\(g[a_i][x] += f[x].sum(1,a_i)\)。
代码省略取模类。
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;template <class T>
struct Fenwick {int n;std::vector<T> tr;Fenwick(int _n) {init(_n);}void init(int _n) {n = _n;tr.assign(_n + 1, T{});}void add(int x, const T &v) {for (int i = x; i <= n; i += i & -i) {tr[i] = tr[i] + v;}}T query(int x) {T res{};for (int i = x; i; i -= i & -i) {res = res + tr[i];}return res;}T sum(int l, int r) {return query(r) - query(l - 1);}
};void solve() {int n;std::cin >> n;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];a[i] += 1;}std::vector f(n + 2, Fenwick<Z>(n + 1));std::vector g(n + 2, Fenwick<Z>(n + 1));f[1].add(1, 1);g[1].add(1, 1);std::vector<Z> sum1(n + 2), sum2(n + 2);for (int i = 0; i < n; ++ i) {for (int y = 1; y <= a[i]; ++ y) {sum1[y] = g[y].sum(y, a[i]);g[y].add(a[i], sum1[y]);}for (int x = a[i] + 1; x <= n + 1; ++ x) {sum2[x] = f[x].sum(1, a[i]);f[x].add(a[i], sum2[x]);}for (int y = 1; y <= a[i]; ++ y) {f[a[i]].add(y, sum1[y]);} for (int x = a[i] + 1; x <= n + 1; ++ x) {g[a[i]].add(x, sum2[x]);}}Z ans = 0;for (int i = 1; i <= n + 1; ++ i) {ans += f[i].query(n + 1);}std::cout << ans << "\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}