Vertex colouring is easy for (bipartite) and NP-complete for , even on planar graphs. Exact chromatic number for is by inclusion–exclusion; the chromatic polynomial for is ; structured classes (chordal, interval, perfect) are polynomial (Special Graphs). Edge colouring needs colours on bipartite graphs (König, constructive) and or on simple graphs (Vizing, constructive ).

Bounds

BoundStatement
clique
independence
greedy in any order
degeneracy, = max over subgraphs of the min degree (colour in reverse smallest-last order)
Brooks unless is a complete graph or an odd cycle
planar (5 constructively in linear time)
perfect graphs

Heuristics: Welsh–Powell (sort vertices by degree descending once, then greedy-colour in that fixed order) is the simplest named heuristic and already realises the degeneracy-style bound in practice on many graphs, though it can be beaten by adversarial orderings since the order is static. DSATUR (colour next the vertex with the most distinct colours among its neighbours, ties by degree) re-picks the order dynamically as colours are assigned, is exact on bipartite graphs, and is strictly stronger in practice; combined with a clique lower bound and backtracking it solves sparse instances.

Complexity by class: deciding is (BFS bipartiteness check); deciding is NP-complete even restricted to planar graphs — the same 2-vs-3 cliff as 2-SAT vs 3-SAT; computing exactly is NP-hard and inapproximable within for any unless P = NP; (or 1 if edgeless); exactly on interval and chordal graphs (greedy by left endpoint / perfect elimination order respectively).

Interval graphs are the class that appears most in contests, almost always disguised as “minimum number of rooms/machines/platforms” rather than named as colouring — recognise it and the problem collapses to maximum simultaneous overlap, swept in (Interval Problems).

Exact chromatic number in

is -colourable iff the number of ordered -tuples of independent sets covering is nonzero, and by inclusion–exclusion that number is , where = number of independent subsets of (including ).

int chromaticNumber(const vector<int>& adj) {               // adj[v] = neighbour bitmask, n <= ~23
    int n = sz(adj), N = 1 << n; if (!n) return 0;
    const ull P = (1ULL << 61) - 1;                            // exact count mod a large prime; false "0" is negligible
    auto mulmod = [&](ull a, ull b) { __uint128_t c = (__uint128_t)a * b; ull r = (ull)(c & P) + (ull)(c >> 61); return r >= P ? r - P : r; };
    vector<ull> I(N), pw(N, 1); I[0] = 1;
    for (int S = 1; S < N; S++) { int v = __builtin_ctz(S); I[S] = I[S ^ 1 << v] + I[(S ^ 1 << v) & ~adj[v]]; }
    for (int k = 1; k <= n; k++) {
        ull tot = 0;
        for (int S = 0; S < N; S++) {
            pw[S] = mulmod(pw[S], I[S] % P);
            tot = ((n - __builtin_popcount(S)) & 1) ? (tot + P - pw[S]) % P : (tot + pw[S]) % P;
        }
        if (tot) return k;
    }
    return n;
}
MethodTimePractical
inclusion–exclusion above20–25, the best known exact asymptotic
bitmask DP: dp[mask] = min colours to cover mask, transitioning over maximal independent submasks, or with [[06-bit-manipulation/03-submasks-and-sos-dpSOS]] preprocessing of which subsets are independent
try with DSATUR + clique lower bound + backtrackingexponential, no clean bound100+ on sparse instances

Chromatic polynomial

= number of proper colourings with at most colours. Deletion–contraction: . Closed forms: trees , cycles , : , chordal graphs . counts acyclic orientations (Stanley). For general graphs with : where = number of ordered partitions of into nonempty independent sets, computed by a ranked (subset-convolution style) inclusion–exclusion in .

struct ChromaticPolynomial {                                 // mint = modular integer type
    int n; vector<mint> E;                                   // E[j]: ordered covers by j disjoint nonempty independent sets
    ChromaticPolynomial(const vector<int>& g) : n(sz(g)), E(n + 1) {
        int full = 1 << n; vector<char> indep(full, 1);
        for (int s = 1; s < full; s++) { int v = __builtin_ctz(s), r = s & (s - 1); indep[s] = indep[r] && !(g[v] & r); }
        vector<vector<mint>> f(n + 1, vector<mint>(full));    // f[r][T] = # independent r-subsets of T
        for (int s = 0; s < full; s++) if (indep[s]) f[__builtin_popcount(s)][s] = 1;
        for (int r = 0; r <= n; r++) for (int i = 0; i < n; i++) for (int s = 0; s < full; s++) if (s >> i & 1) f[r][s] += f[r][s ^ 1 << i];
        vector<vector<mint>> q(n + 1);
        for (int s = 0; s < full; s++) {                      // [y^j] q_n = [z^n] (sum_{r>=1} f[r][s] z^r)^j
            int t = __builtin_popcount(s); q[0] = {mint(1)};
            for (int m = 1; m <= n; m++) {
                q[m].assign(m + 1, mint(0));
                for (int r = 1; r <= min(m, t); r++) { mint a = f[r][s]; if (a == mint(0)) continue; for (int j = 0; j < sz(q[m - r]); j++) q[m][j + 1] += a * q[m - r][j]; }
            }
            for (int j = 0; j <= n; j++) { mint e = j < sz(q[n]) ? q[n][j] : mint(0); if ((n - t) & 1) E[j] -= e; else E[j] += e; }
        }
    }
    mint operator()(ll x) const { mint res = 0, b = 1; for (int j = 0; j <= n; j++) { res += E[j] * b; b = b * (mint(x) - mint(j)) / mint(j + 1); } return res; }
};

Edge colouring

Bipartite (König): exactly colours. Simple constructive method in : for each edge take a colour free at and free at ; if , flip the alternating path starting at (it cannot reach in a bipartite graph), then colour with .

vector<int> bipartiteEdgeColoring(int L, int R, const vector<pair<int, int>>& E) {   // multigraph; colours in [0, Δ)
    int n = L + R, D = 0; vector<int> deg(n);
    for (auto [a, b] : E) D = max({D, ++deg[a], ++deg[L + b]});
    vector<vector<int>> at(n, vector<int>(D, -1));           // at[v][c] = edge id with colour c at v
    vector<int> col(sz(E), -1);
    auto other = [&](int e, int v) { return v == E[e].first ? L + E[e].second : E[e].first; };
    for (int e = 0; e < sz(E); e++) {
        int u = E[e].first, v = L + E[e].second, a = 0, b = 0;
        while (at[u][a] >= 0) a++;
        while (at[v][b] >= 0) b++;
        if (a != b) {                                         // flip the a/b path starting at v
            vector<int> path; int x = v, c = a;
            while (at[x][c] >= 0) { int f = at[x][c]; path.push_back(f); x = other(f, x); c ^= a ^ b; }
            for (int f : path) { int p = E[f].first, q = L + E[f].second; at[p][col[f]] = at[q][col[f]] = -1; }
            for (int f : path) { col[f] ^= a ^ b; int p = E[f].first, q = L + E[f].second; at[p][col[f]] = at[q][col[f]] = f; }
        }
        col[e] = a; at[u][a] = at[v][a] = e;
    }
    return col;
}

For up to – (Library Checker “Bipartite Edge Coloring”), make the graph -regular by merging low-degree vertices and adding dummy edges, then recursively split: an even-degree regular bipartite graph splits into two halves by alternating colours along Euler circuits (Euler Tours); an odd degree peels off one perfect matching (Hopcroft–Karp) first: about in practice.

Simple graphs (Vizing): colours suffice, found in by fan rotations and alternating-path flips; deciding whether suffices is NP-hard.

vector<int> vizing(int n, const vector<pair<int, int>>& E) {   // simple graph; colours in [0, Δ]
    vector<int> cc(n + 1), res(sz(E)), fan(n), freeC(n), loc;
    for (auto [u, v] : E) cc[u]++, cc[v]++;
    int K = *max_element(all(cc)) + 1;
    vector<vector<int>> adj(n, vector<int>(K, -1));           // adj[u][c] = neighbour joined by colour c
    for (auto [u, v0] : E) {
        int v = v0, at = u, end = u, d, c = freeC[u], ind = 0, i = 0;
        fan[0] = v; loc.assign(K, 0);
        while (d = freeC[v], !loc[d] && (v = adj[u][d]) != -1) loc[d] = ++ind, cc[ind] = d, fan[ind] = v;
        cc[loc[d]] = c;
        for (int cd = d; at != -1; cd ^= c ^ d, at = adj[at][cd]) swap(adj[at][cd], adj[at][cd ^ c ^ d]), end = at;
        while (adj[fan[i]][d] != -1) { int l = fan[i], r = fan[++i], e = cc[i]; adj[u][e] = l; adj[l][e] = u; adj[r][e] = -1; freeC[r] = e; }
        adj[u][d] = fan[i]; adj[fan[i]][d] = u;
        for (int y : {fan[0], u, end}) { int& z = freeC[y] = 0; while (adj[y][z] != -1) z++; }
    }
    for (int i = 0; i < sz(E); i++) { auto [u, v] = E[i]; while (adj[u][res[i]] != v) res[i]++; }
    return res;
}

Variants worth naming

VariantNote
List colouringeach vertex has its own allowed colour set — strictly harder than ordinary colouring
Equitable colouringcolour classes must be nearly equal in size
Fractional chromatic numberthe LP relaxation of colouring; polynomial for perfect graphs
Total colouringcolour vertices and edges together, no shared colour between adjacent/incident elements

Where this shows up beyond contests: register allocation in compilers (the classic application), scheduling under pairwise conflicts (exams, meetings, frequency assignment), map colouring (planar, hence ), and Sudoku (a colouring of a specific 81-vertex graph with 9 colours).