把地圖著色,使得相鄰的國家有不同的顏色。
至少需要哪幾種顏色?要如何著色?
已知地圖大小為 \(30\times30\),
每個點可能為編號\(1 \sim 99\)的國家或者海洋(編號\(0\))。
Note : 不保證沒有飛地的存在,也就是四色定理不成立 QAQ
直接偷以前上課的slides
直接偷別人以前上課的slides
這跟我們的問題又有什麼關聯呢?
因為顯然地圖是圖的一種
在這個問題中
我們在意的似乎並不在於每個點的實際位置
對於每個國家,我們只需要知道他跟哪些國家相連即可
=> 直接建圖
我們將每個國家作為一個節點
相連的國家連上一條邊
就形成了一張圖
可以想像對於每個地圖上的點,我們需要建立往四個方向的邊
那運算次數大約是\(4 \times Size^2\) (\(Size\)為地圖的大小)
也就是\(O(Size^2)\)
Note:這樣的估計或許有點粗糙
因為我們其實可以把同樣的邊留下一個就好
但這並不會太大影響整體的複雜度
對於每個點我們對其著色
並且相連的兩個點的顏色不得相同
似乎沒有很好的解法欸
於是我們便嘗試窮舉。
利用深度優先搜尋(dfs)
窮舉所有可能的著色方法。
也就是在dfs途中不停檢查現在的著色是否合法
如果全部的點都著色完成
那這個答案便是一種可能的著色。
假設圖上節點數(地圖上的國家總數)為\(n\),圖上總邊數為\(m\)
我們考慮窮舉每種可能性
也就是每個點都嘗試著編號\(1 \sim n\)的顏色
總共有\(n\)個點,每個點有\(n\)種可能,每跑一種約需要檢查\(m\)條邊
我們得到粗糙的複雜度\(O(mn^n)\)
這是一個非常差的複雜度
數字增長的太快,例如\(n = 10\)就可以達到\(10^{10}\)
就算在實務上我們通常會提早結束
但如果我們能改進這個複雜度,甚至只是前面的\(m\)
解決整個問題的時間就能夠大幅下降
除了剛剛說到的同樣的邊只需要留下一條外
(我使用std::set)
我們會希望越左上的點的顏色編號越小
不然看起來很反人類,雖然我原本沒改
我們在dfs的時候通常會從編號小的開始
而編號會根據國家的編號而定
那如果我們嘗試改變它編號的順序呢?
我們把這種作法稱作離散化
我們考慮限制dfs的顏色數量
假設最後的解的顏色數為\(c\)
那我們的複雜度可以很好的下降到
好像不算太重要(?
所以我等等講code的時候一次講
#include <iostream>
#include <ctime>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
// some constants
const int MAXC = 105;
const int N = 30;
const int MAXN = 32;
const int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0}; // used to check the block around it
vector<int> countries;// used to discrete the countrie numbers
set<int> graph[MAXC]; // use set to get rid of the same number
int color[MAXC]; // save each country's color
int mat[MAXN][MAXN]; // save the map
int c = -1; // current number of colors
bool ans; // check if find the answer yet
int firstseen[MAXC];
bool comp(int x, int y) {
return firstseen[x] < firstseen[y];
}
void build() {
// input with 1-based
for(int i = 1; i <= N; ++i) {
for(int j = 1; j <= N; ++j) {
scanf("%2d", &mat[i][j]);
// save the countries
if(mat[i][j]) {
int u = mat[i][j];
countries.push_back(u);
if(!firstseen[u]) firstseen[u] = j + i * N; // used for coloring order
}
}
}
// discrete
sort(countries.begin(), countries.end(), comp);
countries.erase(unique(countries.begin(), countries.end()), countries.end());
for(auto u : countries) {
cerr << u << ' ';
}
cerr << '\n';
for(int i = 1; i <= N; ++i) {
for(int j = 1; j <= N; ++j) {
if(mat[i][j]) mat[i][j] = lower_bound(countries.begin(), countries.end(), mat[i][j], comp) - countries.begin() + 1;
}
}
// build an adjacentlist
for(int i = 1; i <= N; ++i) {
for(int j = 1; j <= N; ++j) {
int u = mat[i][j];
if(u) {
for(int k = 0; k < 4; ++k) { // different directions
int v = mat[i + dx[k]][j + dy[k]];
if(v && v < u) { // save edges to node with its number less
graph[u].insert(v);
}
}
}
}
}
// for test only
for(int i = 1; i <= countries.size(); ++i) {
cerr << i << ':';
for(auto p : graph[i]) {
cerr << p << ' ';
}
cerr << '\n';
}
}
// u : current country, cmx : the greatest number of color now
void dfs(int u = 1, int cmx = 0) {
// check if it ends
if(u > countries.size()) {
ans = 1;
return;
}
// enumerate the possibilities
for(int i = 1; i <= min(c, cmx + 1); ++i) {
// check if there is any color the same
bool flag = 1;
for(auto it = graph[u].begin(); it != graph[u].end(); ++it) {
if(color[*it] == i) {
flag = 0;
break;
}
}
// continue the recursion
if(flag) {
color[u] = i;
dfs(u + 1, max(i, cmx));
if(ans) return;
}
}
}
void solve() {
int sttime, edtime;
// record the start time period
sttime = time(NULL);
// redirect the in/out stream
freopen("./input.txt", "r", stdin);
freopen("./output.txt", "w", stdout);
// input and build the graph
build();
// searching for answer
while(!ans) {
// check if c colors can satisfy the condition
cerr << "Checking number of color: "<< (++c) << '\n';
dfs();
}
// output
cout << "My name is lemonilemon.\n\n";
cout << "Colored with " << c << " colors.\n\n";
for(int i = 1; i <= N; ++i) {
for(int j = 1; j <= N; ++j) {
int u = mat[i][j];
cout << (char)(color[u] + 'a' - 1) << " \n"[j == 30];
}
}
// record the end time period
edtime = time(NULL);
// compute time
cout << "Finish in " << edtime - sttime << " second(s).\n";
}
int main() {
solve();
return 0;
}