HOJ1035都市(2019.9.19模拟赛A组T2)

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();
}

小结

考试的时候这个题只想到了通过三个数的和求出这三个数,应该考虑将其扩展进行枚举。

转载于:https://www.cnblogs.com/Shiina-Rikka/p/11558547.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值