CF Round 771 Div2 題解
A題 Reverse
給定一個長度為 \(n\) 的排列,我們可以選擇將其中一個區間反轉,問怎樣反轉可以使得這個新排列的字典序最小,並輸出這個最小的字典序。
\(1\leq n \leq 500\)
如果字典序已經最小,那麼就不管他。
假設區間 \([1,l-1]\) 上面的數都在合適的位置上,而 \(p_l\not= l\),那麼我們直接找到後面等於 l 的位置,下標記為 r,然後反轉區間 \([l,r]\) 即可。
#include<bits/stdc++.h> using namespace std; const int N = 510; int n, p[N]; void solve() { cin >> n; for (int i = 1; i <= n; ++i) cin >> p[i]; for (int i = 1; i <= n; ++i) if (p[i] != i) { int l = i, r; for (int j = n; j > i; j--) if (p[j] == i) { r = j; break; } reverse(p + l, p + r + 1); break; } for (int j = 1; j <= n; ++j) cout << p[j] << " "; cout << endl; } int main() { int T; cin >> T; while (T--) solve(); return 0; }
B題 Odd Swap Sort (構造)
給定一個長度為 \(n\) 的數列 \(\{a_n\}\)。如果兩個相鄰元素之和為奇數,那麼我們就可以將其進行交換。問我們能否通過若干次操作將這個數列變為單調不降?
\(1\leq n \leq 10^5,1\leq a_i \leq 10^9\)
一個不是很直觀的冷知識:如果兩個數都是奇數(偶數),那麼他們的相對位置不會產生變化(他倆不可能交換順序)。
所以我們直接按照奇偶,按順序將原陣列分成兩個,如果兩個陣列均單調不降,那麼最後這個陣列也是可以通過交換而變為單調不降的。
#include<bits/stdc++.h> using namespace std; int n; vector<int> vec[2]; bool solve() { vec[0].clear(), vec[1].clear(); cin >> n; for (int i = 1; i <= n; ++i) { int x; cin >> x; vec[x % 2].push_back(x); } for (int x = 0; x < 2; ++x) { vector<int> &v = vec[x]; for (int i = 1; i < v.size(); ++i) if (v[i - 1] > v[i]) return false; } return true; } int main() { int T; cin >> T; while (T--) puts(solve() ? "Yes" : "No"); return 0; }
C題 Inversion Graph(圖,數學,資料結構)
給定一個長度為 \(n\) 的排列 \(\{p_n\}\),我們將依照此排列來建立一個圖:
圖起初有 \(n\) 個孤立點,倘若存在逆序對 \((i,j)\),則在這兩個點之間連一條邊。
圖建立完畢後,問這個圖的連通塊的數量是多少。
\(n\leq 10^5\)
有一個並不顯然的性質:倘若存在逆序對 \([l,r]\),那麼整個區間內的點都在一個連通塊內。
證明:我們記 \(p_l=x,p_r=y,p_i=z(l<i<r)\),那麼:
- \(z<x\) 時,有 \(p_l>p_i,l<i\),i 和 l 之間連邊
- \(z>x\) 時,有 \(p_i>p_r,i<r\),i 和 r 之間連邊
又因為 l 和 r 之間存在邊,所以整個區間 \([l,r]\) 都在一個連通塊內。
那麼,我們將最後的連通塊按照形成順序排序,那麼他們顯然是一個升序,這一流程類似單調棧,所以我們用棧來維護。
我們記棧頂為 \(x\)(空則為 0),新點的值為 \(y\),如果 \(y>x\) 則直接推進去,反之則一直從棧頂 pop,直到棧頂元素小於 \(y\),然後將 \(x\) 推進去。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N];
int solve() {
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
stack<int> s;
for (int i = 1; i <= n; ++i) {
int x = s.empty() ? 0 : s.top();
while (!s.empty() && s.top() > a[i]) s.pop();
s.push(max(a[i], x));
}
return s.size();
}
int main()
{
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
D題 Big Brush(BFS,構造)
給定一張 \(n*m\) 的格子圖,現在我們可以進行若干次操作,每次操作選定一個座標 \((x,y)\) 和一個顏色 \(c\),然後將 \((x,y),(x+1,y),(x,y+1),(x+1,y+1)\) 全部塗上 \(c\)。
現在給定一張已經塗好色的地圖,問我們能否構造出我們的操作順序(至多進行 \(nm\) 次操作)?不能則輸出 -1。
\(2\leq n,m\leq 10^3,1\leq a_i\leq nm\)
我們首先將地圖上可以直接進行操作的那幾塊進行操作,然後將他們標記上 0。
隨後,我們地圖上不停找點,如果這個點可進行操作(且有必要進行操作),那麼下一次就對他進行操作。
暴力尋找是不可取的(電腦不像人眼一樣能一眼看出來),但是每次將一個塊操作完之後,我們只考慮其周圍的八個點,並考慮下一步對他們進行操作。這個 FIFO 的模式,我們肯定寫一個佇列來處理。
考慮到操作的次數限制,加上避免死迴圈,所以額外開一個 vis 陣列來標記,避免某個點重複進入佇列。
總複雜度 \(O(nm)\),不過自帶大常數,需要花心思優化。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, a[N][N], vis[N][N];
inline bool can(int i, int j) { return i >= 1 && i < n && j >= 1 && j < m; }
int check1(int x, int y) {
return a[x][y] == a[x][y + 1] && a[x][y] == a[x + 1][y] && a[x][y] == a[x + 1][y + 1];
}
int check2(int x, int y) {
if (!can(x, y)) return 0;
set<int> s;
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
if (a[x + i][y + j])
s.insert(a[x + i][y + j]);
return s.size() == 1 ? *s.begin() : 0;
}
void update(int x, int y) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
a[x + i][y + j] = 0;
}
struct Node { int x, y, c; };
queue<Node> q;
stack<Node> ans;
int main()
{
//read
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> a[i][j];
//solve
for (int i = 1; i < n; ++i)
for (int j = 1; j < m; ++j)
if (check1(i, j)) {
vis[i][j] = 1;
Node t = (Node){i, j, a[i][j]};
q.push(t), ans.push(t);
}
while (!q.empty()) {
int x = q.front().x, y = q.front().y;
q.pop();
update(x, y);
for (int i = -1; i <= 1; ++i)
for (int j = -1; j <= 1; ++j) {
int v = check2(x + i, y + j);
if (v && !vis[x + i][y + j]) {
vis[x + i][y + j] = 1;
Node t = (Node){x + i, y + j, v};
q.push(t), ans.push(t);
}
}
}
//output
int res = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (!a[i][j]) ++res;
if (res != n * m) puts("-1");
else {
cout << ans.size() << endl;
while (!ans.empty()) {
Node t = ans.top();
printf("%d %d %d\n", t.x, t.y, t.c);
ans.pop();
}
}
return 0;
}