Codeforces Round 1051 (Div. 2)
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;
}
E. Make Good
题意:一个括号序列\(s\),你每次可以选择两个相邻且相等的位置把它们变成相反的括号,求一个可以变成的合法括号序列。
如果\(n\)是奇数,显然无解。
否则,我们将偶数为翻转,也就是左括号变右括号,右括号变左括号,记为\(s'\)。
那么此时对相邻两个位置交换,如果两个位置相等,在原串中它们就不相等,不能操作;如果两个位置不相等,那么原串里它们相等,可以操作,此时操作相当于交换这两个位置,例如,如果本来是"(("变成"))",翻转后就是"()"变成")("。那么如果从交换的角度看,两个位置不能操作的情况其实也算是可以交换,因为两个位置相同,交换后相当于没交换。
那么我们可以任意排列\(s'\)。
假设\(s'\)最后变成\(t'\),此时如果把\(t'\)偶数位置再翻转回来是合法括号序列的话,就有解。
记\(cnt\)为\(s'\)中'('的个数,那么\(t'\)中也恰好有\(cnt\)个'(',此时翻回来变成\(t\),记\(t\)中奇数位置有\(A\)个'(',偶数位置有\(B\)个'(',那么\(t'\)中奇数位置有\(A\)个'(',偶数位置有\(\frac{n}{2} - B\)个'('。因为\(t\)中应该恰好有\(\frac{n}{2}\)个'(',则有\(A + B = \frac{n}{2}\),而\(cnt = A + \frac{n}{2} - B = A + \frac{n}{2} - (\frac{n}{2} - A) = 2A\),所以\(A = \frac{cnt}{2}\)。即\(s'\)中'('的个数应该是偶数个。那么我们可以求出\(t\)中\(A, B\)的值,也就是奇数位偶数位分别有多少'('。
答案可以构造\(A - 1\)个"()",然后加上'('\(+\)\(B\)个"()"\(+\)')'。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
if (n % 2) {
std::cout << -1 << "\n";
return;
}
int cnt = 0;
for (int i = 0; i < n; ++ i) {
if ((i % 2 == 0 && s[i] == '(') || (i % 2 && s[i] == ')')) {
++ cnt;
}
}
if (cnt < 2 || cnt % 2) {
std::cout << -1 << "\n";
return;
}
int A = cnt / 2;
if (A > n / 2) {
std::cout << -1 << "\n";
return;
}
int B = n / 2 - A;
std::string ans;
for (int i = 1; i < A; ++ i) {
ans += "()";
}
ans += '(';
for (int i = 1; i <= B; ++ i) {
ans += "()";
}
ans += ')';
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;
}

浙公网安备 33010602011771号