鄰國不同色問題

21035 蔡孟衡

建國中學 - 高二機器人專題課程

題目

把地圖著色,使得相鄰的國家有不同的顏色。

至少需要哪幾種顏色?要如何著色?

已知地圖大小為 \(30\times30\),

每個點可能為編號\(1 \sim 99\)的國家或者海洋(編號\(0\))。

Note : 不保證沒有飛地的存在,也就是四色定理不成立 QAQ

給個栗子

仙貝知識 - 複雜度

直接偷以前上課的slides

這個在等一下會一直用到

因為這個問題顯然是個NP-complete問題

複雜度的控制十分重要

仙貝知識 - 圖論

直接偷別人以前上課的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優化 - IDDFS

我們考慮限制dfs的顏色數量

假設最後的解的顏色數為\(c\)

那我們的複雜度可以很好的下降到

O(\sum^{c}_{i = 0}mn^i) \in O(mn^c)

莫名其妙的小優化

好像不算太重要(?

所以我等等講code的時候一次講

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

好累ㄛ

Made with Slides.com