NeFut Logo NeFut
Admin Login

Grid-Based State Compression DP: A Deep Dive into No-Retreat Properties and Bitwise Operations

Published at: 2026-05-27 09:17 Last updated: 2026-06-06 13:04
#C++ #Tutorial

Core Logic and Mathematical Principles

The essence of Grid-based State Compression Dynamic Programming is to utilize spatial locality dependencies to reduce the high-dimensional constraints of a two-dimensional grid topology into a one-dimensional state vector interwoven between states.

In Section 17.1, we explored point set compression using the "vertex set" as the compression scale. However, when dealing with grid problems (such as king placement and non-attacking problems), the constraints typically only exist between adjacent rows and columns.

1. Physical Isolation of No-Retreat Property

Assuming a grid of size $N \times M$, when placing pieces, if we advance in a topological order of "top-down, row by row decision-making," then when placing pieces on the $i^{th}$ row:

Therefore, we do not need to record the overall appearance of the chessboard; we only need to compress the placement configuration of the "current row" into a binary mask $S$, which perfectly satisfies the no-retreat property.

2. Bitwise Operation Conflict Check Matrix

Assuming a grid with $M$ columns in a row, we use the binary state $S$ to represent the placement of pieces in that row (1 means occupied, 0 means empty).


State Design and Algorithm Derivation

Taking the classic "Non-Attacking (King)" problem as an example: In an $N \times N$ chessboard, place $K$ kings such that they do not attack each other. The task is to find the total number of valid configurations to place $K$ kings.

1. State Space Definition

Let the state $f[i][j][S]$ represent the number of valid configurations when the first $i$ rows have been decided, with $j$ kings already placed on the board, and the current placement mask of kings in the $i^{th}$ row is $S$.

2. State Transition Equation

For the current row state $S$, we must first ensure its legality within the row. Next, we need to obtain the number of kings contributed by the current row using the operator __builtin_popcount(S), denoted as $c$.

$$\forall S_{pre} \text{ compatible with } S, \quad f[i][j][S] = \sum f[i-1][j - c][S_{pre}]$$

Where "$S_{pre} \text{ compatible with } S$" must strictly pass the three cross-row bitwise checks described above.


C++ Standard Source Code (NOIP Style)

The following code is an industrial-grade standard implementation for the "Non-Attacking/King Placement" problem. The code includes legal state space preprocessing optimizations to significantly reduce constants, ensuring a runtime of 0.00s under Linux g++ -O2.

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

const int MAXN = 10;
const int MAXK = 100;

long long f[MAXN][MAXK][1 << MAXN];
vector<int> valid_states; // Preprocess to store all valid row states to avoid invalid enumeration
int state_weights[1 << MAXN]; // Preprocess the count of 1's in each binary state (i.e., number of kings)

int n, k_total;

// Preprocessing function: Eliminate states with horizontal conflicts and calculate weights
void preprocess() {
    int max_state = 1 << n;
    for (int s = 0; s < max_state; ++s) {
        // No adjacent kings horizontally
        if (!(s & (s << 1))) {
            valid_states.push_back(s);
            state_weights[s] = __builtin_popcount(s); // Quickly calculate the number of 1's in binary
        }
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    if (!(cin >> n >> k_total)) return 0;

    preprocess();

    // Boundary initialization: 0 pieces in the 0th row, 0 kings, mask 0 has 1 configuration
    f[0][0][0] = 1;

    // Topological axis: advance row by row
    for (int i = 1; i <= n; ++i) {
        // Enumerate current row state S (must be from preprocessed valid states)
        for (int s : valid_states) {
            int c = state_weights[s]; // Number of kings placed in the current row

            // Enumerate previous row state S_pre (also from valid set)
            for (int s_pre : valid_states) {
                // Comprehensive check for cross-row conflicts: vertical, left diagonal, right diagonal
                if (s & s_pre) continue;
                if (s & (s_pre << 1)) continue;
                if (s & (s_pre >> 1)) continue;

                // Enumerate total number of kings placed up to the previous row j
                for (int j = c; j <= k_total; ++j) {
                    f[i][j][s] += f[i - 1][j - c][s_pre];
                }
            }
        }
    }

    // Global result collection: horizontally accumulate the number of configurations for all possible states in the last row
    long long ans = 0;
    for (int s : valid_states) {
        ans += f[n][k_total][s];
    }

    cout << ans << "\n";

    return 0;
}

NOIP Practical Pitfall Guide

1. Blindly Using Brute Force Enumeration Causes Implicit Explosion in Time Complexity

2. Bit Shift Boundary Overflow Risks


Classic NOIP/Luogu Problems

1. Luogu P1896 [SCOI2005] Non-Attacking

2. Luogu P2704 [NOI2001] Artillery Position

$$f[i][S][S_{pre}] = \max \{ f[i-1][S_{pre Visited}][S_{pre\_pre}] \} + \text{count}(S)$$

When checking, in addition to performing the row-internal spacing check (S & (S << 1)) == 0 && (S & (S << 2)) == 0, it must also satisfy (S & S_pre) == 0 and (S & S_pre_pre) == 0. By adding an extra state dimension, it successfully decouples the second-order spatial no-retreat property, making it a must-solve problem for advanced grid compression.

Original Source: local://17.2

[h] Back to Home