Codeforces Round 693 (Div 3)
技術標籤:Codeforces圖線性優化
Codeforces Round 693(Div 3)
F New Year’s Puzzle
這裡就不放原題目描述了。有 2 × n 2\times n 2×n的方格,我們有 1 × 2 1\times2 1×2和 2 × 1 2\times1 2×1的瓷磚,方格里有若干個點已經貼了瓷磚,問能否完美貼住?(瓷磚數量沒有限制)
輸入:
樣例數
t
(
1
≤
t
≤
1
0
4
)
t(1\leq t\leq 10^4)
t(1≤t≤104)
整數n和m,n是方格的長(寬永遠是2),m是已經有的瓷磚數量。
1
≤
n
≤
1
0
9
,
1
≤
m
≤
2
×
1
0
5
1\leq n \leq 10^9, 1\leq m\leq 2 \times 10^5
m行,每行都是
r
i
,
c
i
r_i,c_i
ri,ci,已經貼了的座標。
1
≤
r
i
≤
2
,
1
≤
c
i
≤
n
1\leq r_i\leq 2, 1\leq c_i\leq n
1≤ri≤2,1≤ci≤n.
Example:
Input:
3
5 2
2 2
1 4
3 2
2 1
2 3
6 4
2 1
2 3
2 4
2 6
Output:
YES
NO
NO
比如第一個例子如圖所示:
當時Div3做完E題剩下F和G,看到G人少就去做G了,最後也沒能調過。但F讓我做我估計也過不了。其實大體思路很容易想,知道肯定要貪心。從最左邊開始,比如上面的例子,第一列是空的,那就放一個豎的。第二列上面有一個,所以下面的就只能放一個橫的瓷磚。這個操作影響到第三列,那此時第三列也只能放一個橫的,直到一直操作到最後一列。那麼什麼時候得到結果呢?操作過程中,如果發現一個橫的瓷磚不得不放的地方,已經有瓷磚了,或者放了就出界了,那就只能宣佈no。如果放到最後都沒衝突,那就是yes。
這麼寫的話,要注意到比如只有第一列第一行有一個瓷磚,那麼每一次都放一個橫的瓷磚一直迴圈下去,會重複 1 0 9 10^9 109級,顯然會超時。注意到m的範圍是明顯的 n log n n\log n nlogn級別不超時的,所以可以從m入手。
對於所有全空的列,我們就直接不考慮,因為一定不會衝突。用map記錄所有出現過瓷磚的列。關鍵是從map中上一列到下一個出現瓷磚的列,中間隔著若干空列,怎麼判斷是否衝突?不難發現,比如在(k,1)不得不放了一個橫瓷磚,那麼接下來就得在(k+1,2)放一個,接下來是(k+2,1),是一個2次的迴圈。開始我也一直沒想通程式碼裡為什麼color要加上x,其實就是用來處理這一點的。lastColor是結合了橫座標和瓷磚位置的結果。
- 用狀壓表示一列的狀態,可以是0-3
- hasLast表明有沒有之前某個橫瓷磚延伸過來影響到這一列
- 要注意要加一個m[2e9],代表右邊界。很容易忘記。
//
// main.cpp
//
//
// Created by ji luyang on 2020/12/22.
//
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <time.h>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
using namespace std;
int t;
int main() {
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
map<int, int> mp;
for (int i = 1; i <= m; i++) {
int r, c;
scanf("%d %d", &r, &c);
mp[c] |= (1 << (r - 1));
}
mp[2e9] = 3;
int flag = 1;
int hasLast = 0, lastColor = 0;
for (auto pi: mp) {
int x = pi.first, mask = pi.second;
if (mask == 3) {
if (hasLast) {
flag = 0;
break;
}
} else {
if (hasLast) {
int color = (x + mask) % 2;
if (color == lastColor) {
flag = 0;
break;
} else {
hasLast = 0;
}
} else {
hasLast = 1;
lastColor = (x + mask) % 2;
}
}
}
printf("%s", flag ? "YES\n" : "NO\n");
}
return 0;
}
G Moving to the Capital
有向圖,n個頂點,m條邊,1是首都。m和n都是
1
0
5
10^5
105量級。設1到達城市i的經過的最少邊數是
d
i
d_i
di。現在要城市
i
(
1
≤
i
≤
n
)
i(1\leq i \leq n)
i(1≤i≤n)出發,沿著邊走。我們只有一次機會從i走到j,其中
d
i
≥
d
j
d_i \geq d_j
di≥dj,其餘都只能
d
i
<
d
j
d_i < d_j
di<dj這麼走。返回n個城市,按這個規則能達到的最小的
d
d
d。
輸入第一行是樣例數t,每個樣例裡是n,m,然後是m條有向邊。
樣例輸入:
3
6 7
1 2
1 3
2 5
2 4
5 1
3 6
6 2
2 2
1 2
2 1
6 8
1 2
1 5
2 6
6 1
2 3
3 4
4 2
5 4
樣例輸出:
0 0 1 2 0 1
0 0
0 0 2 1 1 0
思路也不難,想的也差不多。肯定先bfs一遍預處理出所有的 d i d_i di。既然只有一次返回走的機會,那麼我們就是要讓這次往回走返回到的d最小,因為接下來d只可能越來越大。換言之,對於城市i,我們要返回所有d比i更大且從i不斷增大d可達的點中,能走到的d最小的邊。
可以預處理出一個tomin[i]陣列,表示每個頂點直接能走到的d最小值。然後因為 n n n的大小,肯定不能從每個頂點都遍歷一遍,肯定是要邊遍歷邊更新,按某種順序進行遍歷。比賽的時候寫了增加反向邊,然後從所有d最大的點,往首都走,邊走邊更新到達的點的tomin。這是不對的,比如有的點d不是最大的,卻並不能再繼續往d增大的方向走了。其實根本不需要增加反向邊,直接利用之前bfs得到的拓撲序列入手,從後往前遍歷,遍歷到城市i,對於所有連著的城市v,如果d[v]>d[i],更新tomin[i]。此時tomin[v]也已經更新過。
//
// main.cpp
//
//
// Created by ji luyang on 2020/12/22.
//
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <time.h>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int t;
vector<int> g[200010];
vector<int> topo;
int d[200010], tomin[200010];
int main(int argc, char* argv[]) {
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
memset(g, 0, sizeof(g));
topo.clear();
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d %d", &u, &v);
g[u].push_back(v);
}
for (int i = 1; i <= n; i++) d[i] = 1e9;
d[1] = 0;
queue<int> q;
q.push(1);
while (!q.empty()) {
int tmp = q.front();
topo.push_back(tmp);
q.pop();
for (auto v: g[tmp]) {
if (d[v] == 1e9) {
d[v] = d[tmp] + 1;
q.push(v);
}
}
}
for (int i = 1; i <= n; i++) tomin[i] = d[i];
for (int i = 1; i <= n; i++) {
for (int v: g[i]) {
if (d[v] <= d[i]) {
tomin[i] = min(tomin[i], d[v]);
}
}
}
for (int i = n - 1; i >= 0; i--) {
int u = topo[i];
for (int v: g[u]) {
if (d[v] > d[u]) {
tomin[u] = min(tomin[u], tomin[v]);
}
}
}
for (int i = 1; i <= n; i++) {
printf("%d ", tomin[i]);
}
printf("\n");
}
return 0;
}