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\) 个数时,有转移:
选择第 \(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;
}

浙公网安备 33010602011771号