給定 n 和 m(1 ≤ n ≤ 1e5,2 ≤ m ≤ 10),判斷能否將 1 ~ n 分成 m 組,使每組數字之和相等。若可能,輸出任意一種分組方案;否則輸出 NO。測試組數 T ≤ 1000。
S = n(n+1)/2 必須能被 m 整除,否則無解。avg = S / m 必須 ≥ n(因為最大數 n 必須放入某組,且組和至少為 n),即 avg ≥ n,否則無解。當 n ≥ 4m - 1 時,可採用「首尾配對」構造法:
2m 個數:n-2m+1, n-2m+2, ..., n。(n-2m+1, n)、(n-2m+2, n-1) ... 每對和為 2n - 2m + 1。m 組,每組和相等。n' = n - 2m,遞歸處理。此構造保證每次減少 2m,最終將 n 縮減至 [2m, 4m-2] 區間。
當 時,n ≤ 4m-2 ≤ 38(因 m ≤ 10),可用狀態壓縮 + DFS 搜索:
board = n(剩餘最大數)、m(剩餘組數)、ave(目標和)、vs[](數字使用標記)。tot(當前組已選和)、id(當前組索引)、start(下一可選最小數,保證單調避免重複)。tot + i > ave 直接返回;dp[board][m] 記憶化結果(0=未訪問,1=可行,-1=不可行)。aux 中的分組複製到全局 ans。n >= 4*m - 1 而非 n > 4*m - 1,避免多一次遞歸導致 TLE(題目常數較緊)。start 參數保證單調選擇,避免排列重複。dp[50][11]、方案存儲 rec[50][11][11][50] 覆蓋所有小規模狀態。bool dfs(int tot, int id, int start);
bool solve(int top); // 返回是否可行,並填充 ans/def
int main() {
int T; read(T);
while (T--) {
read(n, m);
ll sum = 1LL * n * (n + 1) / 2;
if (sum % m || sum / m < n || !solve(n)) {
puts("NO");
} else {
puts("YES");
// 輸出 ans + def 合併的分組
}
}
}
O(n/m) 步驟,每步 O(m),總 O(n)。40 * 10 = 400,每狀態搜索分支極少,實際極快。O(T * n),滿足 1000 組、n=1e5 限制。本題結合「大規模構造縮減」與「小規模記憶化搜索」,巧妙利用數學性質將問題規模壓縮到可暴力搜索範圍,是構造類問題中「分治 + 兜底搜索」的經典范式。