2020-2021 ICPC, NERC, Southern and Volga Russian Regional Contest
A. LaIS (CF contest 1468 A)
题目大意
给定数组\(a_{i}\),求最长几乎上升子数组长度。
一个有\(k\)个数的数组\(b_{i}\)如果满足
则数组\(b\)成为几乎上升数组。
解题思路
在最长上升子序列的基础商,最长上升子序列允许中间忽然插入一个很大的数。
于是,对于第\(i\)个数\(a_{i}\),它可以从前面小于\(a_{i}\)的\(a_{j}\)直接转移过来,或者在\((j,i)\)中插一个数较大的数\(a_{k}\)。
设\(dp1[i]\)表示以\(i\)结尾的最长几乎上升子序列的最大长度,\(dp2[i]\)表示以第\(i\)个数结尾的最长几乎上升子序列的最大长度。
从左到右遍历,对于第\(i\)个数\(a_{i}\)
- \(dp1[a_{i}] = \max_{x \leq a_{i}}(dp1[x]) + 1\)
- \(dp1[a_{i}] = \max_{x \leq a_{i} \& r_{pos_x} < i} (dp1[x]) + 2\)
其中\(r_{i}\)表示说位置\(i\)右边第一个比它大的数的位置,这个可以事先用单调栈预处理。
\(pos_{x}\)表示这个数\(x\)的位置。
第二种转移即为二维偏序,由于\(i\)是递增的,依照\(r_{pos_x}\)更新数据即可,值得注意的是添加的时候,是要添加那个位置的最大长度值,由于\(dp1\)已经把这个位置信息丢失了,我们需要\(dp2\)来更新。
最值用线段树维护。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
const int N = 5e5 + 8;
class Segment_Tree{
#define lson root << 1
#define rson root << 1 | 1
int maxx[N << 2];
public:
void build(int root, int l, int r){
if (l == r){
maxx[root] = 0;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
maxx[root] = max(maxx[lson], maxx[rson]);
}
void update(int root, int l, int r, int pos, int val){
if (l == r){
maxx[root] = max(maxx[root], val);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) update(lson, l, mid, pos, val);
else update(rson, mid + 1, r, pos, val);
maxx[root] = max(maxx[lson], maxx[rson]);
}
int query(int root, int l, int r, int ll, int rr){
if (ll <= l && r <= rr){
return maxx[root];
}
int mid = (l + r) >> 1;
int lans = 0, rans = 0;
if (ll <= mid) lans = query(lson, l, mid, ll, rr);
if (rr > mid) rans = query(rson, mid + 1, r, ll, rr);
int ans = max(lans, rans);
return ans;
}
}trans1, trans2;
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n;
read(n);
trans1.build(1, 1, n);
trans2.build(1, 1, n);
vector<int> a(n), r(n), dp1(n + 2), dp2(n + 2), id(n);
iota(id.begin(), id.end(), 0);
for(auto &i : a)
read(i);
stack<pair<int,int>> qwq;
for(size_t i = 0; i < a.size(); ++ i){
while(!qwq.empty() && qwq.top().first < a[i]){
r[qwq.top().second] = i;
qwq.pop();
}
qwq.push({a[i], i});
}
while(!qwq.empty()){
r[qwq.top().second] = n;
qwq.pop();
}
sort(id.begin(), id.end(), [&](int x, int y){
return r[x] < r[y];
});
int cur = 0;
dp1[a[0]] = 1;
dp2[0] = 1;
for(size_t i = 1; i < a.size(); ++ i){
dp1[a[i]] = max({1, trans1.query(1, 1, n, 1, a[i]) + 1, trans2.query(1, 1, n, 1, a[i]) + 2});
dp2[i] = dp1[a[i]];
while(cur < n && r[id[cur]] <= (int)i){
trans2.update(1, 1, n, a[id[cur]], dp2[id[cur]]);
++ cur;
}
trans1.update(1, 1, n, a[i], dp1[a[i]]);
}
int ans = *max_element(dp1.begin(), dp1.end());
write(ans, '\n');
}
return 0;
}
B. Bakery (CF contest 1468 B)
题目大意
解题思路
神奇的代码
C. Berpizza (CF contest 1468 C)
题目大意
一个餐厅,两个人\(M\)和\(P\)。依次有顾客进来,\(P\)知晓顾客消费金额。每次服务,\(M\)找最先进来的顾客服务,\(P\)找消费金额最高的,相同则最先进来的顾客服务。
现在依次给定发生事件序列,包括
- 顾客进来,附带\(P\)估计的消费金额
- \(M\)去服务顾客
- \(P\)去服务顾客
对于第二和第三种事件,输出它们服务的顾客编号。
解题思路
顾客进来的顺序就是顾客的编号。
用一个数组标记顾客是否被服务,用优先队列维护消费金额。
对于第二种就依次找没被标记的,对于第三种就从优先队列弹出队首直到是未被服务的顾客。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
int main(void) {
int q;
read(q);
priority_queue<pair<int,int>> k;
int l = 1;
vector<bool> sign(q + 1, false);
int a, b;
int t = 0;
while(q--){
read(a);
if (a == 1) {
read(b);
++ t;
k.push({b, -t});
}else if (a == 2){
while(sign[l]) ++ l;
sign[l] = true;
write(l);
}else{
while(true){
auto qwq = k.top();
k.pop();
if (sign[-qwq.second]) continue;
sign[-qwq.second] = true;
write(-qwq.second);
break;
}
}
}
puts("");
return 0;
}
D. Firecrackers (CF contest 1468 D)
题目大意
横向的格子有A和B,B抓A。B每个时刻想靠近A的方向移动一个格子,A有\(m\)个鞭炮,第\(i\)个鞭炮点燃后经过\(t_{i}\)时刻爆炸,A每个时刻可向左或向右移动一个,或者留在原地点燃一个鞭炮。问A被抓前最多能看到多少个鞭炮爆炸。
解题思路
我们假设\(A\)在左,\(B\)在右。
首先\(A\)不可能往右。
最优方案下,\(A\)在原地不断点燃鞭炮,直到\(B\)在\(A\)右边一个格,此时两人再双双向左走,等待鞭炮爆炸。
模拟下这个过程就能得到答案了。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n, m, a, b;
read(n);
read(m);
read(a);
read(b);
-- a;
-- b;
vector<int> qwq(m);
for(auto &i : qwq) read(i);
sort(qwq.begin(), qwq.end());
if (a > b){
a = n - 1 - a;
b = n - 1 - b;
}
int cnt = b - a - 1;
int ans = 0;
int up = b;
int cur = 1;
int r = m - 1;
while(cnt){
while(r >= 0 && cur + qwq[r] > up) -- r;
if (r < 0) break;
++ ans;
++ cur;
-- cnt;
-- r;
}
write(ans, '\n');
}
return 0;
}
E. Four Segments (CF contest 1468 E)
题目大意
二维平面给定四条直线要求平行于坐标轴放置使得闭合矩形面积最大,求最大值。
解题思路
显然就是最短边和次长边的乘积。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int kase; cin>>kase;
for (int ii = 1; ii <= kase; ii++) {
LL a[4];
cin >> a[0] >> a[1] >> a[2] >> a[3];
sort(a, a + 4);
cout << a[0] * a[2] <<endl;
}
return 0;
}
F. Full Turn (CF contest 1468 F)
题目大意
\(n\)个人在平面上的某点朝向某处,现在所有人以相同速度原地顺时针转,直到转了一圈。问期间有多少对人发生了对视。
解题思路
由于角速度相同,容易发现,会发生对视的那两个人它们的朝向在一条直线上,方向相反,用\(map\)统计平行且反向的向量对数即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n;
read(n);
map<pair<LL,LL>,int> cnt;
LL x, y, u, v;
LL ans = 0;
for(int i = 1; i <= n; ++ i){
read(x);
read(y);
read(u);
read(v);
auto qwq = __gcd(abs(u - x), abs(v - y));
// cout <<qwq << endl;
auto dir = make_pair((u - x) / qwq, (v - y) / qwq);
auto irdir = make_pair((x - u) / qwq, (y - v) / qwq);
cnt[dir] ++;
ans += cnt[irdir];
}
write(ans, '\n');
}
return 0;
}
G. Hobbits (CF contest 1468 G)
题目大意
平面图一个分段直线函数,最右边的高处有一个眼睛,一个点从最左边沿直线走到最右边,问有多少距离,眼睛是看不到点的。
解题思路
神奇的代码
H. K and Medians (CF contest 1468 H)
题目大意
给定\(1...n\),\(n\)个数,每次操作,选取\(k\)个数(\(k\)是奇数),删除除中位数之外的数。问最终能否得到给定的有\(m\)个数的数组\(b\)。
解题思路
首先如果\(n-m\)不是\(k-1\)的倍数,肯定不行。
思考删除过程,除了最后一次,期间的中位数是任意的,只要最后一次删除,中位数在数组\(b\)里即可。
于是我们只要判断要删除的前\(\lfloor \dfrac{k}{2}\rfloor\)个数之后的第一个在数组\(b\)的数的右边是否有\(\lfloor \dfrac{k}{2}\rfloor\)个数要删除即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
bool check(vector<bool> &b, int n, int m, int k){
int rm = 0;
for(int i = 0; i < n; ++ i){
if (!b[i]) ++ rm;
else if (rm >= k / 2 && (n - m - rm) >= k / 2) return true;
}
return false;
}
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n, m, k;
read(n);
read(k);
read(m);
vector<bool> b(n, false);
for(int a, i = 0; i < m; ++i){
read(a);
-- a;
b[a] = true;
}
if ((n - m) % (k - 1)){
puts("NO");
continue;
}
if (check(b, n, m, k)) puts("YES");
else puts("NO");
}
return 0;
}
I. Plane Tiling (CF contest 1468 I)
题目大意
解题思路
神奇的代码
J. Road Reform (CF contest 1468 J)
题目大意
给定一张无向图,你有一个操作是使某一边的边权加一或减一。要求一个图的生成树,树的所有边的边权的最大值恰好为\(k\),求最小的操作数。
解题思路
把边权小于等于\(k\)边权全部加到图上,如果形成了一棵树,就取图上最大边权和未在图上边权大于\(k\)的最小边权与\(k\)的差值的绝对值的最小值。
如果还不能形成一棵树,那就继续加边,把边权弄成\(k\),同时累计代价,直到形成一棵树。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
int findfa(int x, vector<int> &fa){
return x == fa[x] ? x : fa[x] = findfa(fa[x], fa);
}
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n, m;
LL k;
read(n);
read(m);
read(k);
vector<pair<LL,pair<int,int>>> edge(m);
LL w;
for(int u, v, i = 0; i < m; ++ i){
read(u);
read(v);
read(w);
-- u;
-- v;
edge[i] = make_pair(w, make_pair(u, v));
}
sort(edge.begin(), edge.end(), [](pair<LL,pair<int,int>> &a, pair<LL,pair<int,int>> &b){
return a.first < b.first;
});
vector<int> fa(n);
iota(fa.begin(), fa.end(), 0);
size_t id = 0;
int cnt = 0;
for(; id < edge.size(); ++ id){
if (edge[id].first > k) break;
auto a = findfa(edge[id].second.first, fa);
auto b = findfa(edge[id].second.second, fa);
if (a != b){
++ cnt;
fa[a] = b;
}
}
LL ans = 0;
if (cnt >= n - 1){
ans = k - edge[id - 1].first;
if (id != edge.size())
ans = min(ans, edge[id].first - k);
}else{
for(; id < edge.size(); ++ id){
if (cnt == n - 1) break;
auto a = findfa(edge[id].second.first, fa);
auto b = findfa(edge[id].second.second, fa);
if (a != b){
ans += edge[id].first - k;
++ cnt;
fa[a] = b;
}
}
}
write(ans, '\n');
}
return 0;
}
K. The Robot (CF contest 1468 K)
题目大意
平面图一个机器人在\((0,0)\),给定机器人的移动指令包含\(LRUD\),此时机器人最终不能回到原点。现在可以放置一个障碍物(机器人不能移动到障碍物处),使得机器人可以回到\((0,0)\),问障碍物的位置,或告知不存在方案。
解题思路
命令数只有\(5000\),我们就枚举\(ban\)掉的那个命令,即在执行某个命令到达后的位置放置障碍物,看能否回到原点。
值得注意的是如果这个放置障碍物的位置,之前走过,那么这个位置此时不能放置障碍物。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
bool check(string &s, int x, int y, size_t pos){
int bx = x;
int by = y;
int nx = x, ny = y;
if (s[pos] == 'L') -- bx;
if (s[pos] == 'R') ++ bx;
if (s[pos] == 'U') ++ by;
if (s[pos] == 'D') -- by;
for(size_t i = pos; i < s.size(); ++ i){
nx = x;
ny = y;
if (s[i] == 'L') -- nx;
if (s[i] == 'R') ++ nx;
if (s[i] == 'U') ++ ny;
if (s[i] == 'D') -- ny;
if (nx != bx || ny != by){
x = nx;
y = ny;
}
}
return x == 0 && y == 0;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int kase; cin>>kase;
for (int ii = 1; ii <= kase; ii++) {
string s;
cin >> s;
map<pair<int,int>, bool> qwq;
int x = 0, y = 0;
bool sign = false;
for(size_t i = 0; i < s.size(); ++ i){
if (check(s, x, y, i)){
sign = true;
}
if (s[i] == 'L') -- x;
if (s[i] == 'R') ++ x;
if (s[i] == 'U') ++ y;
if (s[i] == 'D') -- y;
if (sign && qwq[{x, y}]) sign = false;
qwq[{x, y}] = true;
if (sign) break;
}
if (!sign) x = 0, y = 0;
cout << x << ' ' << y << endl;
}
return 0;
}
L. Prime Divisors Selection (CF contest 1468 L)
题目大意
解题思路
神奇的代码
M. Similar Sets (CF contest 1468 M)
题目大意
给定\(n\)个集合,第\(i\)个集合包含\(k_{i}\)个数。求一对相似集合,或告知不存在。
两个集合,如果有两个不同的数都在这两个集合里,则这两个集合相似。
解题思路
神奇的代码
N. Waste Sorting (CF contest 1468 N)
题目大意
给了三种垃圾桶\(A,B,C\)及其容量,以及五种垃圾\(a,b,c,d,e,f\)数量。\(a\)扔\(A\),\(b\)扔\(B\),\(c\)扔\(C\),\(d\)扔\(A\)或\(C\),\(e\)扔\(B\)或\(C\)。问最后能不能把垃圾扔到正确的垃圾桶里且垃圾桶不爆满。
解题思路
\(a\)扔\(A\);\(b\)扔\(B\);\(c\)扔\(C\);\(d\)扔\(A\),满了扔\(C\);\(e\)扔\(B\),满了扔\(C\)。
看能不能塞下。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
bool check(){
int a1, a2, a3;
int v1, v2, v3, v4, v5;
cin >> a1 >> a2 >> a3 >> v1 >> v2 >> v3 >> v4 >> v5;
a1 -= v1;
a2 -= v2;
a3 -= v3;
if (a1 < 0) return false;
if (a2 < 0) return false;
if (a3 < 0) return false;
int cnt1 = min(a1, v4);
a1 -= cnt1;
a3 -= v4 - cnt1;
int cnt2 = min(a2, v5);
a2 -= cnt2;
a3 -= v5 - cnt2;
if (a1 < 0) return false;
if (a2 < 0) return false;
if (a3 < 0) return false;
return true;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int kase; cin>>kase;
for (int ii = 1; ii <= kase; ii++) {
if (check()) cout << "YES" <<endl;
else cout << "NO" << endl;
}
return 0;
}