HOJ1035都市(2019.9.19模拟赛A组T2)
\(\color{blue}{\text{404nofind}}\)
题意
有n个数,给出两两之间的和,求这n个数的所有可能值。
怎么做
这是智商题
考虑一个事情,如果给定三个数\(a_1, a_2, a_3\)分别相加的和,怎样求出这三个数呢?
显然,我们可以算出来这三个数的和,即\(\frac{(a_1 + a_2)+(a_1+a_3)+(a_2+a_3)}{2} = a_1+a_2+a_3\),并依次相减,即可得到\(a_1, a_2, a_3\)。
那么对于\(n\)个数{\(a_1, a_2...a_n\)}, 若我们钦定数列是有序的,我们可以判断出\(a_1+a_2\)以及\(a_1+a_3\),因为这两个分别是最小的以及第二小的数。
证明:
假设\(a_1+a_2>a_x+a_y(x, y \neq 1, 2)\)
\(\because a_2<a_x\)
\(\therefore a_1+a_2>a_2+a_y\)
\(\qquad \quad a_1 > a_y\)
这与{\(a_1, a_2...a_n\)}有序不符,所以\(a_1+a_2\)是最小的,同理可证\(a_1+a_3\)第二小。
此时我们已经知道了\(a_1+a_2\)以及\(a_1+a_3\),在剩下的情况中枚举\(a_2+a_3\),就可以求出\(a_1, a_2, a_3\)这三个数。
在求得\(a_1\)后,我们便可枚举\(a_1+a_k\)是哪个数,并且判断该情况是否合法。以上两个枚举复杂度都是O(\(n\)).
关于如何判断是否合法,可以通过已知的数,进行求和,并判断这个情况下的和是否符合题目给出的值。这个复杂度是O(\(n\))的。
整体复杂度O(\(n^3\))。
放代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 305;
int n, m, cnt;
int a[45005];
bool use[45005];
int num[MAXN];
struct node
{
int ans[MAXN];
inline void print()
{
for (register int i = 1; i <= n; ++i)
printf("%d ", ans[i]);
putchar('\n');
}
void operator=(int *s)
{
memcpy(ans, s, sizeof(ans));
}
} ans[MAXN];
inline int find(const int &x)
{
int l = 1, r = m, mid, res;
while (l <= r)
{
mid = (l + r) >> 1;
if (a[mid] <= x)
{
res = mid, l = mid + 1;
}
else
{
r = mid - 1;
}
}
return res;
}
inline void check(int x)
{
memset(use, 0, sizeof(use));
int sum = a[1] + a[2] + a[x];
if (sum & 1)
return;
sum >>= 1;
num[1] = sum - a[x];
num[2] = sum - a[2];
num[3] = sum - a[1];
use[1] = use[2] = use[x] = true;
for (register int i = 3, k = 4; k <= n; ++k) //枚举a1 + ak
{
while (i <= m && use[i]) //判重
++i;
if (i > m)
return;
num[k] = a[i] - num[1];
use[i] = true;
for (register int j = 2; j < k; ++j) //枚举a[x] + a[k]
{
if (num[j] > num[k]) //保证顺序
return;
sum = num[j] + num[k];
register int p = find(sum);
if (a[p] != sum)
return;
register int px = p;
while (px && a[px] == a[p]) //找到相同数值中最前面的一个
--px;
++px;
while (px <= m && use[px] && a[px] == a[p])
++px;
if (a[px] != a[p] || use[px])
return;
p = px;
use[p] = true;
}
}
++cnt;
ans[cnt] = num;
}
int main()
{
scanf("%d", &n);
m = (n * (n - 1)) / 2;
for (register int i = 1; i <= m; ++i)
{
scanf("%d", &a[i]);
}
sort(a + 1, a + m + 1);
for (register int i = 3; i <= m;) //枚举a2 + a3;
{
check(i);
register int ii = i;
while (a[ii] == a[i]) //去重
++ii;
i = ii;
}
printf("%d\n", cnt);
for (register int i = 1; i <= cnt; ++i)
ans[i].print();
}
小结
考试的时候这个题只想到了通过三个数的和求出这三个数,应该考虑将其扩展进行枚举。