#include using namespace std; static constexpr int MAXN = 50; static constexpr int MAXV = MAXN * MAXN; static constexpr int SAMPLES = 100; static constexpr int CUT_STEPS = 230; static constexpr int RESTORE_STEPS = 130; int N, K, V; vector d; vector trees; static unsigned char dry[SAMPLES][MAXV]; static unsigned char cutCell[MAXV]; static int ord_[MAXV], low_[MAXV], sub_[MAXV], parent_[MAXV]; static int sepSize_[MAXV], compId_[MAXV], compSize_[MAXV]; static long long sepSq_[MAXV]; static long long totalValue[MAXV]; static int timer_, compCnt_; static int deg_[MAXV]; static int nbr_[MAXV][4]; static int ufParent[MAXV], ufSize[MAXV]; static long long restoreValue[MAXV]; inline int getD(int v) { return d[v / N][v % N] - '0'; } void dfsLow(int v, int s, int cid) { ord_[v] = low_[v] = timer_++; sub_[v] = 1; compId_[v] = cid; for (int z = 0; z < deg_[v]; ++z) { int to = nbr_[v][z]; if (cutCell[to] || !dry[s][to]) continue; if (ord_[to] == -1) { parent_[to] = v; dfsLow(to, s, cid); sub_[v] += sub_[to]; low_[v] = min(low_[v], low_[to]); if (low_[to] >= ord_[v]) { sepSize_[v] += sub_[to]; sepSq_[v] += 1LL * sub_[to] * sub_[to]; } } else if (to != parent_[v]) { low_[v] = min(low_[v], ord_[to]); } } } inline void addCutValuesForSample(int s) { fill(ord_, ord_ + V, -1); fill(parent_, parent_ + V, -1); fill(sepSize_, sepSize_ + V, 0); fill(sepSq_, sepSq_ + V, 0LL); timer_ = 0; compCnt_ = 0; for (int start : trees) { if (cutCell[start]) continue; if (!dry[s][start]) continue; if (ord_[start] != -1) continue; int cid = compCnt_++; dfsLow(start, s, cid); compSize_[cid] = sub_[start]; } for (int v : trees) { if (cutCell[v]) continue; if (ord_[v] == -1) continue; int S = compSize_[compId_[v]]; int remain = S - 1 - sepSize_[v]; long long after = sepSq_[v] + 1LL * remain * remain; totalValue[v] += 1LL * S * S - after; } } inline int ufFind(int x) { while (ufParent[x] != x) { ufParent[x] = ufParent[ufParent[x]]; x = ufParent[x]; } return x; } inline void ufUnite(int a, int b) { a = ufFind(a); b = ufFind(b); if (a == b) return; if (ufSize[a] < ufSize[b]) { swap(a, b); } ufParent[b] = a; ufSize[a] += ufSize[b]; } inline void addRestoreValuesForSample(int s) { for (int v : trees) { if (!cutCell[v] && dry[s][v]) { ufParent[v] = v; ufSize[v] = 1; } else { ufParent[v] = -1; ufSize[v] = 0; } } for (int v : trees) { if (ufParent[v] == -1) continue; int r = v / N; int c = v % N; if (r + 1 < N) { int to = v + N; if (ufParent[to] != -1) { ufUnite(v, to); } } if (c + 1 < N) { int to = v + 1; if (ufParent[to] != -1) { ufUnite(v, to); } } } for (int v : trees) { if (!cutCell[v]) continue; if (!dry[s][v]) continue; int roots[4]; int rootCount = 0; long long mergedSize = 1; long long before = 0; for (int z = 0; z < deg_[v]; ++z) { int to = nbr_[v][z]; if (ufParent[to] == -1) continue; int root = ufFind(to); bool duplicate = false; for (int k = 0; k < rootCount; ++k) { if (roots[k] == root) { duplicate = true; break; } } if (duplicate) continue; roots[rootCount++] = root; long long sz = ufSize[root]; mergedSize += sz; before += sz * sz; } long long after = mergedSize * mergedSize; restoreValue[v] += after - before; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> N >> K; V = N * N; d.resize(N); for (int i = 0; i < N; ++i) { cin >> d[i]; } trees.reserve(V); for (int r = 0; r < N; ++r) { for (int c = 0; c < N; ++c) { int v = r * N + c; if (d[r][c] != '0') { trees.push_back(v); } if (r > 0) { nbr_[v][deg_[v]++] = v - N; } if (r + 1 < N) { nbr_[v][deg_[v]++] = v + N; } if (c > 0) { nbr_[v][deg_[v]++] = v - 1; } if (c + 1 < N) { nbr_[v][deg_[v]++] = v + 1; } } } mt19937 rng(123456789); for (int s = 0; s < SAMPLES; ++s) { for (int v : trees) { dry[s][v] = ((int)(rng() % 10) < getD(v)); } } vector cutList; cutList.reserve(CUT_STEPS); for (int step = 0; step < CUT_STEPS && step < (int)trees.size(); ++step) { fill( totalValue, totalValue + V, 0LL); for (int s = 0; s < SAMPLES; ++s) { addCutValuesForSample(s); } int best = -1; for (int v : trees) { if (cutCell[v]) continue; if (best == -1 || totalValue[v] > totalValue[best] || ( totalValue[v] == totalValue[best] && ( getD(v) > getD(best) || ( getD(v) == getD(best) && v < best)))) { best = v; } } if (best == -1) break; cutCell[best] = 1; cutList.push_back(best); } int restoreSteps = min( RESTORE_STEPS, max( 0, (int)cutList.size() - K)); for (int step = 0; step < restoreSteps; ++step) { fill( restoreValue, restoreValue + V, 0LL); for (int s = 0; s < SAMPLES; ++s) { addRestoreValuesForSample(s); } int best = -1; for (int v : cutList) { if (!cutCell[v]) continue; if (best == -1 || restoreValue[v] < restoreValue[best] || ( restoreValue[v] == restoreValue[best] && ( getD(v) < getD(best) || ( getD(v) == getD(best) && v < best)))) { best = v; } } if (best == -1) break; cutCell[best] = 0; } vector answer; answer.reserve(K); for (int v : trees) { if (cutCell[v]) { answer.push_back(v); } } cout << answer.size() << '\n'; for (int v : answer) { cout << v / N << ' ' << v % N << '\n'; } return 0; }