題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。

🔗 🟢 CF1774D. Same Count One

Problem Statement

題目簡述

給定 nn 個長度為 mm 的二進位陣列。每次操作可以選擇兩個陣列 x,yx, y 和一個位置 pospos,交換 ax,posa_{x,pos}ay,posa_{y,pos}

求使所有陣列中 11 的個數相同所需的最少操作次數,並輸出交換方案。若無法達成則輸出 1-1

Constraints

約束條件

  • 2n,m1052 \le n, m \le 10^5
  • ai,j{0,1}a_{i,j} \in \{0, 1\}
  • nm106\sum n \cdot m \le 10^6

思路:貪心逐位置配對交換

可行性判斷

首先計算所有陣列的 11 的總數 tot=i=1ncnti\text{tot} = \sum_{i=1}^{n} \text{cnt}_i。若 totmodn0\text{tot} \mod n \neq 0,則無法使每個陣列的 11 數量相等,輸出 1-1

否則,目標是使每個陣列的 11 數量等於 avg=tot/n\text{avg} = \text{tot} / n

貪心策略

核心觀察

交換操作只在不同陣列的同一個位置進行,因此我們可以 按照位置獨立處理

對於每一個位置 jj

  1. 找出所有「多餘」的陣列:當前位置為 11 且該陣列 11 的總數超過 avg\text{avg}
  2. 找出所有「缺少」的陣列:當前位置為 00 且該陣列 11 的總數低於 avg\text{avg}
  3. 將這兩類陣列 一一配對進行交換
正確性證明

我們將所有陣列根據初始 11 的數量分為三類:

  1. 多餘陣列 (S>S_{>}):初始 11 的數量 >avg> \text{avg}。這類陣列在過程中只會減少 11,直到數量等於 avg\text{avg}
  2. 缺少陣列 (S<S_{<}):初始 11 的數量 <avg< \text{avg}。這類陣列在過程中只會增加 11,直到數量等於 avg\text{avg}
  3. 平衡陣列 (S=S_{=}):初始 11 的數量 =avg= \text{avg}。這類陣列全程不參與交換。

根據反證法,演算法結束後,不可能存在某個陣列 AS>A \in S_{>} 未達到目標,反之亦然。

證明細節

反證法:假設演算法結束後,仍有某個陣列 AS>A \in S_{>} 未達到目標(即最終 cnt[A]>avgcnt[A] > \text{avg})。
根據總數守恆,必然存在某個陣列 BS<B \in S_{<} 也未達到目標(即最終 cnt[B]<avgcnt[B] < \text{avg})。

由於最終 cnt[A]>avg>cnt[B]cnt[A] > \text{avg} > cnt[B],必然存在某一個位置 jj,使得 grid[A][j]=1grid[A][j] = 1grid[B][j]=0grid[B][j] = 0

回顧演算法在處理位置 jj 時的狀態:

  • 陣列 AA 屬於 S>S_{>}cntcnt 只減不增,且最终 >avg> \text{avg},故處理位置 jjcnt[A]>avgcnt[A] > \text{avg}。均滿足條件,應被加入候選名單 arr1
  • 陣列 BB 屬於 S<S_{<}cntcnt 只增不減,且最终 <avg< \text{avg},故處理位置 jjcnt[B]<avgcnt[B] < \text{avg}。均滿足條件,應被加入候選名單 arr2

演算法會將 arr1arr2 盡可能配對。

  • AA 未被交換,意味著 arr2 空了(BB 被配對走了)。
  • BB 未被交換,意味著 arr1 空了(AA 被配對走了)。

這產生了矛盾:AABB 不可能在同一位置 jj 都「剩下」且未被處理。因此假設不成立,演算法必能使所有陣列達到平均值 avg\text{avg}

複雜度分析

  • 時間複雜度:O(nm)\mathcal{O}(nm)
  • 空間複雜度:O(nm)\mathcal{O}(nm)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def solve():
n, m = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(n)]

cnt = [sum(row) for row in grid]
tot = sum(cnt)
if tot % n:
print(-1)
return -1
avg = tot // n

ops = []
for j in range(m):
arr1 = []
arr2 = []
for i in range(n):
if grid[i][j] == 1 and cnt[i] > avg:
arr1.append(i)
elif grid[i][j] == 0 and cnt[i] < avg:
arr2.append(i)
for i1, i2 in zip(arr1, arr2):
ops.append((i1 + 1, i2 + 1, j + 1))
grid[i1][j] = 0
grid[i2][j] = 1
cnt[i1] -= 1
cnt[i2] += 1

print(len(ops))
for i1, i2, j in ops:
print(i1, i2, j)
assert all(sum(row) == avg for row in grid)

if __name__ == "__main__":
t = int(input())
for _ in range(t):
solve()