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;
}