codeforces 1461D,离线查询是什么神仙方法,为何快这么多?

你们好,欢迎来到codeforces专题。node

今天咱们选择的题目是1461场次的D题,这题全场经过了3702人,从难度上来讲比较适中。既没有很难,也很适合同窗们练手。另外它用到了一种全新的思想是在咱们以前的文章当中没有出现过的,相信对你们会有一些启发。ios

连接:https://codeforces.com/contest/1461/problem/Dweb

废话很少说了,让咱们开始吧。算法

题意

咱们给定包含n个正整数的数组,咱们能够对这个数组执行一些操做以后,能够让数组内元素的和成为咱们想要的数数组

咱们对数组的执行操做一共分为三个步骤,第一个步骤是咱们首先计算出数组的中间值mid。这里mid的定义不是中位数也不是均值,而是最大值和最小值的均值。也就是mid = (min + max) / 2。编辑器

得出了mid以后,咱们根据数组当中元素的大小将数组分红两个部分。将小于等于mid的元素分为第一个部分,将大于mid的元素分为第二个部分。这样至关于咱们把原来的大数组转化成了两个不一样的小数组。svg

如今咱们一共有q个请求,每一个请求包含一个整数k。咱们但愿程序给出咱们可否经过上述的操做使得最终获得的数组内的元素和等于k。测试

若是能够输出Yes,不然输出No。flex

样例

首先输入一个整数t,表示测试数据的组数( )。ui

对于每一组数据输入两个整数n和q,n表示数组内元素的数量,q表示请求的数量( )。接着第二行输入一行n个整数,其中的每个数 ,都有

接下来的q行每行有一个整数,表示咱们查询的数字k( ),保证全部的n和q的总和不超过

对于每个请求咱们输出Yes或No表示是否能够达成。

对于第一个样例,咱们一开始获得的数组是[1, 2, 3, 4, 5]。咱们第一次执行操做,能够获得mid = (1 + 5) / 2 = 3。因而数组被分为[1, 2, 3][4, 5]。对于[1, 2, 3]继续操做,咱们能够获得mid = (1 + 3) / 2 = 2,因此数组能够分红[1, 2][3][1, 2]最终又能够拆分红[1][2]

咱们能够发现可以查找到的k为:[1, 2, 3, 4, 5, 6, 9, 15]

题解

这道题并不算很复杂,解法仍是比较清晰的。

咱们很容易发现对于数组的操做实际上是固定的,由于数组当中的最大值和最小值都是肯定的。咱们只须要对数组进行排序以后,经过二分查找就能够很容易完成数组的拆分。一样,对于数组的求和咱们也不用使用循环进行累加运算,经过前缀和很容易搞定。

因此本题惟一的难度就只剩下了如何判断咱们要的k能不能找到,其实这也不复杂,咱们只须要把它当成搜索问题,去搜索一下全部能够达到的k便可。这个是基本的深搜,也没有太大的难度。

bool examine(int l, int r, int k) {
    if (l == r) return (tot[r] - tot[l-1] == k);
    // 若是[l, r]的区间和已经小于k了,那么就不必去考虑继续拆分了
    if (l > r || tot[r] - tot[l-1] < k) {
        return false;
    }
    if (tot[r] - tot[l-1] == k) {
        return true;
    }
    // 中间值就是首尾的均值
    int m = (nums[l] + nums[r]) / 2;
    // 二分查找到下标
    int md = binary_search(l, r+1, m);
    if (md == r) return false;
    return examine(l, md, k) | examine(md+1, r, k);
}

这段逻辑自己并不难写,可是当咱们写出来以后,发现仍然不能AC,会超时。我当时思考了好久,终于才想明白问题出在哪里。

问题并不是咱们这里搜索的复杂度过高,而是搜索的次数太多了。q最多状况下会有 ,而每次搜索的复杂度是 。由于咱们的搜索层数是 ,加上咱们每次使用二分带来的 ,因此极端的复杂度是 ,在n是 的时候,这个值大概是 ,再加上一些杂七杂八的开销,因此被卡了。

为了解决这个问题,咱们引入了离线机制

这里的离线在线很好理解,所谓的在线查询,也就是咱们每次得到一个请求,查询一次,而后返回结果。而离线呢则相反,咱们先把全部的请求查询完,而后再一个一个地返回。不少同窗可能会以为很诧异,这二者不是同样的么?只不过顺序不一样而已。

大多数状况下的确是同样的,但有的时候,咱们离线查询是能够批量进行的。好比这道题,咱们能够一次性把全部能够构成的k经过一次递归所有查出来,而后存放在set中。以后咱们只须要根据输入的请求去set当中查询是否存在就能够了,因为查询set的速度要比咱们经过递归来搜索快得多。这样就至关于将q次查询压缩成了一次,从而节约了运算的时间,某种程度上来讲也是一种空间换时间的算法。

咱们来看代码,获取更多细节:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include "time.h"
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
const int N=100050;
const long long Mod=1000000007;
 
using namespace std;

int nums[N];
long long tot[N];
set<long long> ans;

int binary_search(int l, int r, int val) {
    while (r - l > 1) {
        if (nums[mid] <= val) {
            l = mid;
        }else {
            r = mid;
        }
    }
    return l;
}

// 离线查询,一次把全部能构成的k放入set当中
void prepare_ans(int l, int r) {
    if (l > r) return ;
    if (l == r) {
        ans.insert(nums[l]);
        return ;
    }
    ans.insert(tot[r] - tot[l-1]);
    int m = (nums[l] + nums[r]) / 2;
    int md = binary_search(l, r+1, m);
    if (md == r) return ;
    prepare_ans(l, md);
    prepare_ans(md+1, r);
}


int main() {
    int t;
    scanf("%d", &t);
    rep(z, 0, t) {
        ans.clear();
        MEM(tot, 0);
        int n, q;
        scanf("%d %d", &n, &q);
        rep(i, 1, n+1) {
            scanf("%d", &nums[i]);
        }
        sort(nums+1, nums+n+1);
        rep(i, 1, n+1) {
            tot[i] = tot[i-1] + nums[i];
        }
        prepare_ans(1, n);
        rep(i, 0, q) {
            int k;
            scanf("%d", &k);
            // 真正请求起来的时候,咱们只须要在set里找便可
            if (ans.find(k) != ans.end()) {
                puts("Yes");
            }else {
                puts("No");
            }
        }
    }

    return 0;
}

在线变离线是竞赛题当中很是经常使用的技巧,常常被用来解决一些查询量很是大的问题。说穿了其实并不难,可是若是不知道想要凭本身干想出来则有些麻烦。你们有时间,最好本身亲自用代码实现体会一下。

今天的算法题就聊到这里,衷心祝愿你们天天都有所收获。若是还喜欢今天的内容的话,请来一个三连支持吧~(点赞、关注、转发

相关文章
相关标签/搜索