目录
写在前面
比赛地址:https://codeforces.com/contest/1942。
过了这么长时间才来补太唐了、、、
赛时写 D 写了一坨呃呃,用刷表法总是不可避免地要多枚举一个 \(O(n)\) 比较+转移妈的,赛后一看填表法+堆就不用枚举了笑烂了
A
签到。
完全相等的数列随便轮换都是有序的,而不完全相等的有序数列,经过轮换后除非恢复原状一直是无序的。
则当且仅当 \(k=1\) 或 \(k=n\) 时有解。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n, k; std::cin >> n >> k;
if (k == 1) {
for (int i = 1; i <= n; ++ i) std::cout << i << " ";
std::cout << "\n";
} else if (n == k) {
for (int i = 1; i <= n; ++ i) std::cout << 1 << " ";
std::cout << "\n";
} else {
std::cout << -1 << "\n";
}
}
return 0;
}
B
简单思维。
考虑在顺序枚举数列 \(a\) 的同时维护当前 \(\operatorname{mex}\) 并构造排列。
由 \(\operatorname{mex}\) 的性质可知,若 \(p_i\not= \operatorname{mex}(p_1, \cdots, p_{i - 1})\) 时,必然有 \(p_i>\operatorname{mex}(p_1, \cdots, p_{i - 1})\) 使得 \(a_i < 0\)。
则对于 \(a_i<0\),加入 \(p_i\) 后 \(\operatorname{mex}\) 不变,可直接计算出 \(p_i= \operatorname{mex} - a_i\);对于 \(a_i>0\) 则一定有 \(p_i = \operatorname{mex}\),此时更新 \(\operatorname{mex}\) 即可。
总时间复杂度 \(O(n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN], p[kN];
int yes[kN];
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i], yes[i - 1] = 0;
int nowmex = 0;
for (int i = 1; i <= n; ++ i) {
if (a[i] < 0) {
p[i] = nowmex - a[i], yes[p[i]] = 1;
} else {
yes[nowmex] = 1;
while (nowmex < n && yes[nowmex]) ++ nowmex;
p[i] = nowmex - a[i];
}
}
for (int i = 1; i <= n; ++ i) std::cout << p[i] << " ";
std::cout << "\n";
}
return 0;
}
C1
枚举,思维。
首先把 \(x=2\) 的情况特判掉。
手玩下发现对于由被选中的 \(x\) 个点构成的 \(x\) 边形其最多分割出三角形的数量固定为 \(x-2\),再枚举特判被选中的相邻的两个点连线时能否在原图形中多分割出一个三角形(也即是否距离为 2)即可。
需要给点排个序,总时间复杂度 \(O(x\log x)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, x, y, c[kN];
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> x >> y;
if (x == 2) {
int a, b; std::cin >> a >> b;
if (a > b) std::swap(a, b);
if (n == 4 && abs(a - b) == 2) {
std::cout << 2 << "\n";
} else if (n > 4 && (b - a == 2 || a + n - b == 2)) {
std::cout << 1 << "\n";
} else {
std::cout << 0 << "\n";
}
} else {
for (int i = 1; i <= x; ++ i) std::cin >> c[i];
std::sort(c + 1, c + x + 1);
c[x + 1] = n + c[1];
int ans = 0;
for (int i = 2; i <= x + 1; ++ i) {
if (c[i] - c[i - 1] == 2) ++ ans;
}
std::cout << ans + x - 2 << "\n";
}
}
return 0;
}
C2
枚举,思维。
由 C1 的结论可知,每当多加一个被选择的点,则被选择的点构成的多边形边数加 1,则分割出三角形的数量至少增加 1。
那么是否可能增加超过 1 呢?答案是可能的。考虑四个相邻的点 \(A,B,C,D\) 且其中 \(A, D\) 已被选择,则若选择了 \(B\) 则可以额外分割出 \(\triangle BCD\)(等价于也选择了 \(C\))。
于是一种显然的贪心是考虑枚举所有被选择的点对 \(A, B\),考虑在 \(A, B\) 中间所有点中,从 \(A\) 开始以步长为 2 不断地选择未被选择的点;特别地,若 \(A, B\) 距离为奇数,则可以额外分割出一个三角形。
于是考虑将所有相邻两个被选择点之间的距离,按照第一优先级奇距离优先,第二优先级长度递增排序,然后对这些距离按上述思路贪心地加点即可。
总时间复杂度 \(O(x\log x)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, x, y, c[kN], d[kN];
//=============================================================
bool cmpd(int fir_, int sec_) {
if (fir_ % 2 != sec_ % 2) return fir_ % 2 < sec_ % 2;
return fir_ < sec_;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> x >> y;
for (int i = 1; i <= x; ++ i) std::cin >> c[i];
std::sort(c + 1, c + x + 1);
c[x + 1] = n + c[1];
for (int i = 1; i <= x; ++ i) d[i] = c[i + 1] - c[i];
std::sort(d + 1, d + x + 1, cmpd);
int ans = 0, delta = y, added = 0;
for (int i = 1; i <= x; ++ i) {
if (d[i] == 2) ++ ans;
else {
int temp = d[i], need = (temp - 1) / 2;
temp %= 2;
if (need <= delta) delta -= need, ans += need, added += need;
else ans += delta, added += delta, temp = kN, delta = 0;
if (temp == 0) ++ ans;
}
}
std::cout << ans + x + added - 2 << "\n";
}
return 0;
}
D
DP?
考虑仅求最大值的过程,发现每次涂色仅与上一次被涂色的位置有关,感觉很可 DP 的样子,考虑记状态 \(f_{i}(-1\le i\le n)\) 表示使用前 \(i\) 个位置进行涂色且第 \(i\) 个位置被占用(可能被涂色或未被涂色),可以达到的最大权值,初始化 \(f_{-1} = f_{0} = 0\),则有转移:
\[\begin{aligned} \forall 1\le i\le n,\ f_{i}\leftarrow \begin{cases} f_{i - 1}\\ \max\limits_{j\le i - 2} \left(f_{j} + a_{j+2, i}\right) \end{cases} \end{aligned}\]答案即为 \(f_n\)。
现要求前 \(k\) 大值,看到这个 DP 转移顺序有一种拓扑序上 \(k\) 短路的美感,显然想到在转移过程中仅有每个节点的前 \(k\) 短路会对最终的 \(k\) 短路有贡献。
考虑修改状态定义,记 \(f_i(-1\le i\le n)\) 表示表示使用前 \(i\) 个位置进行涂色且第 \(i\) 个位置被占用(可能被涂色或未被涂色),可以达到的前 \(k\) 大的权值的集合。初始化 \(f_{-1} = f_{0} = \{0\}\),转移时考虑维护一个大小为 \(i+1\) 的堆,将 \(-1\sim i-1\) 中的最大值转移后对 \(f_i\) 的贡献加入堆中,然后不断取出堆中的最大值,每次取出都将对应位置的集合中次大值转移后对 \(f_i\) 的贡献加入堆中。
答案即为 \(f_n\) 中的最大值。
发现每个状态中仅有 \(k\) 个值,每次转移都需要维护一个大小为 \(O(n)\) 级别的堆,且仅会进行 \(O(k)\) 次插入查询删除操作,则总时间复杂度 \(O(nk\log n)\) 级别。
实现时用了迭代器,太好用啦 STL!
//DP?
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pLi std::pair<LL,int>
#define mp std::make_pair
const int kN = 1e3 + 10;
const int kd = 1;
//=============================================================
int n, k, a[kN][kN];
std::vector <LL> f[kN];
std::vector <LL>::iterator it[kN];
//=============================================================
void Init() {
std::cin >> n >> k;
for (int i = 1; i <= n; ++ i) {
for (int j = i; j <= n; ++ j) {
std::cin >> a[i][j];
}
}
}
void Solve() {
for (int i = -1; i <= n + 1; ++ i) f[i + kd].clear();
f[-1 + kd].push_back(0);
f[0 + kd].push_back(0);
for (int i = 1; i <= n; ++ i) {
std::priority_queue <pLi> q;
for (int j = -1; j <= i - 1; ++ j) {
it[j + kd] = f[j + kd].begin();
q.push(mp(*it[j + kd] + (j != i - 1) * a[j + 2][i], j));
}
int cnt = k;
while (!q.empty() && cnt) {
LL v = q.top().first;
int p = q.top().second; q.pop();
f[i + kd].push_back(v);
-- cnt, ++ it[p + kd];
if (it[p + kd] == f[p + kd].end()) continue;
q.push(mp(*it[p + kd] + (p != i - 1) * a[p + 2][i], p));
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
Init();
Solve();
for (auto x: f[n + kd]) std::cout << x << " ";
std::cout << "\n";
}
return 0;
}
E
咕咕咕~
写在最后
学到了什么:
- D:填表法!