Codeforces Round 1051 (Div. 2) A~D2

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\) 个数时,有转移:

\[dp_{i,j,k}=dp_{i-1,j,k} \]

选择第 \(i\) 个数时,有转移:

\[\begin{aligned} dp_{i,a_i,k} = dp_{i,a_i,k} + dp_{i-1,j,k} & & a_i \ge j \\ dp_{i,j,a_i} = dp_{i,j,a_i} + dp_{i-1,j,k} & & j > a_i \ge k \end{aligned} \]

\(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;
}
posted @ 2025-09-19 21:40  Ke_scholar  阅读(47)  评论(0)    收藏  举报