Codeforces Round #696 (Div. 2) C. Array Destruction

本文详细解析了Codeforces上的CArrayDestruction问题,介绍了如何通过递归和散列优化解决该问题。首先,通过分析题意,确定了寻找最大值并进行匹配的策略。接着,由于原始解决方案导致的时间复杂度过高,引入了散列思想,将数组元素计数存储在散列表中,从而将时间复杂度降低至O(n^2),避免了超时。最后,给出了AC代码实现,并强调了在处理大规模数据时散列的重要性。

C Array Destruction

题目链接https://codeforces.com/contest/1474/problem/C

题意:

给出一个长度为2n的数组,判断数组内的任意三个数是否满足a + b = c,若满足,删除a,b;再判断数组内是否存在另外的两个数是否满足d + e = max(a, b),若满足,就删除d,e;一直循环做这个操作,直到数组内的数字被删除完毕为止并输出最初的c和之后两个数的组合即(a, b);(c, d)…(最初提到的c就是第一组a, b的和而已)

所以这个题就和明确了,每次找到当前数组的最大值,然后找到一个值与之匹配相加,如果其和等于上一个匹配的最大值,则继续匹配,一直匹配到数组所以的数字都被匹配为止。那没办法全部匹配的呢?那就是输出no的情况了。

很明显这就是个递归问题,但是递归开始是怎样的呢?第一个匹配是怎样找到的呢,回想题意,我们不难看出这就有些像是分蛋糕,每次都将当前最大的蛋糕分成大小不同的两份,然后扔掉小的,再分大的那一份,重复这个工作。所以不难想到第一个匹配的第一个值必然是数组的最大值,那第二个值是哪一个值呢,我们无法求出,所以都试试,看看能不能让后面剩余的值全部匹配就行了,所以我们需要枚举从1~2n - 1之间的值为第一个匹配的第二个值就好了。

那么之后怎么写呢?如果数组长度为2的话,那不论如何都是可以匹配的,因为c可以直接变成这两个数字的和,那比2长的话,第二个匹配的和肯定就是数组最大值,那按照分蛋糕的思想,第二个匹配的第一个值肯定就是当前未被匹配过的最大值咯,那第二个值呢?两个数的和与其中的一个数都知道了,另一个数不久减一下就好了嘛?那不就是判断这两个数的差是不是存在于数组就好了,等等,你说是不是想到了遍历数组?欧?这就 O ( n 3 ) O(n^{3}) O(n3)的时间复杂度了(因为你任何一个匹配的第一个值都是需要用循环一个一个判断是否能找到其匹配值的,在这里就有 n 2 n^{2} n2了,再加一个遍历的话就是 n 3 n^{3} n3),你就会超时(比如我之前超时的代码:(嘤 嘤 嘤

bool solve(int n, int num, int &cnt){//num为当前遍历的第一次匹配的第二个值在数组的位置
    if(n == 2) return true;
    sort(a + 1, a + n + 1);
    fill(b + 1, b + n + 1, false);
    b[n] = true;
    b[num] = true;
    int pre = a[n];
    for(int i = n - 1; i > 0; i --){
        if(b[i]) continue;
        b[i] = true;
        for(int j = 1; j < i; j ++){
            if(b[j]) continue;
            if(a[i] + a[j] == pre) {
                ans[cnt][0] = a[i];
                ans[cnt][1] = a[j];
                b[j] = true;
                pre = a[i];
                cnt ++;
                if(cnt == n / 2 - 1) return true;
                break;
            }
        }
    }
    return false;
}

在这里插入图片描述
我们在看一下数组内数字的范围:1e6????这不直接散列???妈妈我会散列思想我又出息了,嘤 嘤 嘤。。(确实,都知道判断是否被匹配过了,还不用散列的思想,我当时太蠢了。)
好吧,直接将数组内的某数字的个数存在一个散列表里面就行了,被匹配了就减一个,减到只剩0了就是这个数再数组内全部被匹配过了,或者数组内根本没这个数。这样就直接将遍历的那一步省了,时间复杂度变成了 O ( 1 ) O(1) O(1),所以在这里的时间复杂度为 O ( n 2 ) O(n^{2}) O(n2), 就不会超时了。

AC代码

#include <bits/stdc++.h>
 
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int a[maxn];
int vis[maxn];
int ans[maxn][5];
 
bool solve(int n, int num, int &cnt){
    if(n == 2) return true;
    sort(a + 1, a + n + 1);
    fill(vis, vis + maxn, 0);//初始化
    for(int i = 1; i <= n; i ++){
        vis[a[i]] ++;
    }
    vis[a[n]] --;
    vis[a[num]] --;
    int pre = a[n];
    for(int i = n - 1; i > 0; i --){
        if(!vis[a[i]]) continue;
        vis[a[i]] --;
        if(vis[pre - a[i]] > 0){
            vis[pre - a[i]] --;
            ans[cnt][0] = a[i];
            ans[cnt][1] = pre - a[i];
            cnt ++;
            pre = max(a[i], pre - a[i]);
            if(cnt == n / 2 - 1) return true;
        }
        else return false;
    }
    return false;
}
 
int main(){
    ios::sync_with_stdio(0);
    cin.tie(); cout.tie(0);
 
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif//提交的时候可以不用注释
    int t; cin >> t;
    while(t--){
        int n; cin >> n;
        for(int i = 1; i <= 2 * n; i ++){
            cin >> a[i];
        }
        int i = 1;
        for(; i < 2 * n; i ++){
            int cnt = 0;
            if(solve(2 * n, i, cnt)){
                cout << "YES\n" << a[i] + a[2 * n] << "\n";
                cout << a[i] << " " << a[2 * n] << "\n";
                for(int j = 0; j < cnt; j ++){
                    cout << ans[j][0] << " " << ans[j][1] << "\n";
                }
                break;
            }
        }
        if(i == 2 * n) cout << "NO\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值