大家好,欢迎来到codeforces专题。

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

链接:https://codeforces.com/contest/1461/problem/D

废话不多说了,让我们开始吧。

题意

我们给定包含n个正整数的数组,我们可以对这个数组执行一些操作之后,可以让数组内元素的和成为我们想要的数

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

得出了mid之后,我们根据数组当中元素的大小将数组分成两个部分。将小于等于mid的元素分为第一个部分,将大于mid的元素分为第二个部分。这样相当于我们把原来的大数组转化成了两个不同的小数组。

现在我们一共有q个请求,每个请求包含一个整数k。我们希望程序给出我们能否通过上述的操作使得最终得到的数组内的元素和等于k。

如果可以输出Yes,否则输出No。

样例

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

对于每一组数据输入两个整数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;
}

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

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

codeforces 1461D,离线查询是什么神仙方法,为什么快这么多?的更多相关文章

  1. Hibernate的几种查询方式-HQL,QBC,QBE,离线查询,复合查询,分页查询

    HQL查询方式 这一种我最常用,也是最喜欢用的,因为它写起来灵活直观,而且与所熟悉的SQL的语法差不太多.条件查询.分页查询.连接查询.嵌套查询,写起来与SQL语法基本一致,唯一不同的就是把表名换成了 ...

  2. hibernate离线查询DetachedCriteria清除上次的查询条件

    1 原例概述 别名重复问题之后,我们还需要解决的问题就是: 如何清除hibernate的上次查询条件,如果不清除,将会导致上次的查询条件和下次的查询条件合并到了一起. 上次的查询条件和本次的查询条件合 ...

  3. Hibernate——离线查询

    1.Criteria查询方式: (1)一般方式: 缺点:每一次查询dao层都需要书写对应的方法,离线查询可以解决这个问题. (2)离线方式: 2.离线查询 用DetachedCriteria来构造查询 ...

  4. C# 数据操作系列 - 3. ADO.NET 离线查询

    0. 前言 在上一篇中,我故意留下了查询的示范没讲.虽然说可以通过以下代码获取一个DataReader: IDataReader reader = command.ExecuteReader(); 然 ...

  5. ASP.NET MVC 5 - 查询Details和Delete方法

    在这部分教程中,接下来我们将讨论自动生成的Details和Delete方法. 查询Details和Delete方法 打开Movie控制器并查看Details方法. public ActionResul ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-7  在别的LINQ查询操作中使用Include()方法 问题 你有一个LINQ ...

  7. 【SQL】Oracle分页查询的三种方法

    [SQL]Oracle分页查询的三种方法 采用伪列 rownum 查询前10条记录 ? 1 2 3 4 5 6 7 8 9 10 11 [sql] select * from t_user t whe ...

  8. Thinkphp中的volist标签(查询数据集(select方法)的结果输出)用法简介

    参考网址:http://camnpr.com/archives/1515.html 通常volist标签多用于查询数据集(select方法)的结果输出,通常模型的select方法返回的结果是一个二维数 ...

  9. MVC 5 - 查询Details和Delete方法

    MVC 5 - 查询Details和Delete方法 在这部分教程中,接下来我们将讨论自动生成的Details和Delete方法. 查询Details和Delete方法 打开Movie控制器并查看De ...

随机推荐

  1. 项目实战--JSON.toJSONString()

    需求说明:公司使用Swagger(接口文档在线生成工具),为了让前端同事更好的了解传入参数的详细情况,应用项目中接口(eg:分页查询接口)中使用dto对象来接受前端传入的参数,但是后面中心项目中接口是 ...

  2. Sentinel限流之快速失败和漏桶算法

    距离上次总结Sentinel的滑动窗口算法已经有些时间了,原本想着一口气将它的core模块全部总结完,但是中间一懒就又松懈下来了,这几天在工作之余又重新整理了一下,在这里做一个学习总结. 上篇滑动窗口 ...

  3. golang遍历时修改被遍历对象

    目录 前言 遍历切片 遍历map 总结 前言 很多时候需要将遍历对象中去掉某些元素,或者往遍历对象中添加元素,这时候就需要小心操作了. 对于go语言中的一些注意事项我做了总结和示例,留下点笔记. 遍历 ...

  4. http-请求和响应报文的构成

    请求的构成: 1)请求方法URI协议/版本 2)请求头(Request Header) 3)请求正文 1)请求方法URI协议/版本 Request URL: http://localhost:8080 ...

  5. Linux监控工具vmstat命令

    当linux服务器的发生告警,我们要查看当前系统的状态值,包括CPU使用率,内存使用情况,虚拟内存交换情况,IO读写情况等. top与vmstat这两个监控工具都满足要求,当然top还可以看到各个进程 ...

  6. 【Problem】前端项目运行:Module build failed:Error Node Sass does not yet support my current environmen

    我在运行renren-fast-vue前端项目时,安装完依赖cnpm install 启动服务npm run dev 出现问题. Module build failed: Error: Node Sa ...

  7. 【Linux】Linux介绍和安装 - 测试题

    第一部分测试题 Linux介绍和安装 测试题 做点练习题,巩固一下咯~ ~ _ 10 个选择题. 1.让我们选择开机时进哪个操作系统的软件叫什么? A. booter B. bootloader C. ...

  8. 【Oracle】静默安装oracle 11.2.0.4 超详细

    安装oracle 1.执行脚本完成初始化oracle环境 2.解压缩oracle的压缩包,单实例1个,rac是2两个压缩包 3.修改response下的db_install.rsp 修改内容如下: - ...

  9. kubernets之statefulset资源

    一  了解Statefulset 1.1  对比statefulset与RS以及RC的区别以及相同点 Statefulset是有状态的,而RC以及RS等是没有状态的 Statefulset是有序的,拥 ...

  10. pandas 写csv 操作

    pandas 写csv 操作 def show_history(self): df = pd.DataFrame() df['Time'] = pd.Series(self.time_hist) df ...