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;
}
posted @ 2025-09-18 01:10  maburb  阅读(744)  评论(2)    收藏  举报