ZR864 杜老师的拷问 (构造)
Description
dls 在心中生成了 n n n 个正整数,给出了两两数字的和共 n × ( n − 1 ) 2 \frac{n \times (n-1)} {2} 2n×(n−1) 个,求所有可能的方案。
1 ≤ n ≤ 300 , 1 ≤ a i + a j ≤ 2 × 1 0 8 1 \leq n \leq 300, 1 \leq a_i + a_j \leq 2 \times 10^8 1≤n≤300,1≤ai+aj≤2×108
Solution
构造。令 n n n 个数从小到大为 a 1 , a 2 , . . , a n a_1,a_2,..,a_n a1,a2,..,an。
用 set 维护所有的和, 最小的和一定是 a 1 + a 2 a_1 + a_2 a1+a2,次小的一定是 a 1 + a 3 a_1 + a_3 a1+a3,但是不知道 a 2 + a 3 a_2 + a_3 a2+a3 是哪一个。所有枚举 a 2 + a 3 a_2 + a_3 a2+a3 这个数是哪一个。只在前 n + 1 n + 1 n+1 个位置枚举,因为只可能 a 1 + a i a_1 + a _ i a1+ai 比它小,然后能解方程求出 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3,把这个 3 3 3 个数删掉,剩下最小的数一定为 a 1 + a 4 a_1 + a_4 a1+a4,以此类推可以把所有的数都求出来。
在枚举中判断 a 2 + a 3 a_2 + a_3 a2+a3 是否合法,只要判断和是否存在,而且这个和还有没被用的。所以用掉一个和让和减少一个。
Code
#include <bits/stdc++.h>
using namespace std;
map<int, int> all;
const int N = 500 + 5;
int a[N * N], ans[N][N * N], tot = 0, n, m;
bool add(int i, int x){
ans[tot][i] = x;
for (int j = 1; j <= i - 1; j++)
if (all[x + ans[tot][j]] == 0) return 1; // 生成的数与其他的数的和不存在
else if (--all[x + ans[tot][j]] == 0) all.erase(x + ans[tot][j]); // 和减少一个
return 0;
}
void solve(int a12, int a13, int a23){
if ((a12 + a13 + a23) % 2 == 1) return; // 如果分出的数不能被整除就不合法
all.clear();
for (int i = 1; i <= m; i++) all[a[i]]++;
add(1, (a12 + a13 - a23) / 2); // a1 + a2 + a1 + a3 - a2 - a3 = 2 * a1 2 * a1 / 2 = 1
add(2, (a12 + a23 - a13) / 2); // a2
add(3, (a13 + a23 - a12) / 2); // a3
for (int i = 4; i <= n; i++) if (add(i, all.begin() -> first - ans[tot][1])) return ;
tot++;
}
int main(){
cin >> n; m = n * (n - 1) / 2;
for (int i = 1; i <= m; i++) cin >> a[i]; // 读入
sort(a + 1, a + m + 1); // 排序
for (int i = 1; i <= n + 2; i++)
if (a[i] != a[i - 1]) solve(a[1], a[2], a[i]);
cout << tot << endl; // tot : 答案的可能数
for (int i = 0; i < tot; i++) for (int j = 1; j <= n; j++) printf("%d%c", ans[i][j], j == n ? '\n' : ' ');
return 0;
}

浙公网安备 33010602011771号