大家好,欢迎来到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. SpringBoot整合Shiro权限框架实战

    什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...

  2. Java 安全之Weblogic 2017-3248分析

    Java 安全之Weblogic 2017-3248分析 0x00 前言 在开头先来谈谈前面的绕过方式,前面的绕过方式分别使用了streamMessageImpl 和MarshalledObject对 ...

  3. LeetCode226 翻转二叉树

    翻转一棵二叉树. 示例: 输入: 4 / \ 2 7 / \ / \ 1 3 6 9 输出: 4 / \ 7 2 / \ / \ 9 6 3 1 备注:这个问题是受到 Max Howell的 原问题  ...

  4. 【Spring】Spring中的Bean - 5、Bean的装配方式(XML、注解(Annotation)、自动装配)

    Bean的装配方式 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 文章目录 Bean的装配方式 基于XML的装配 基于注解 ...

  5. binlog-do-db

    如果只是对一个数据库设置,其实没有效果的,其他数据还是会记录binlog的 binlog-ignore-db =database b  binlog日志里面将不会记录database b的所有相关的操 ...

  6. [翻译]Azure 网关迁移至 .NET Core 3.1 性能提升一倍

    原文:[Azure Active Directory's gateway is on .NET Core 3.1!] Azure Active Directory 的网关服务是一个反向代理,它为构成 ...

  7. 【ASM】介绍Oracle自带的一些ASM维护工具 (kfod/kfed/amdu)

    转自:http://blog.csdn.net/wenzhongyan/article/details/47043253 非常感谢作者的文章,很有价值!至此转载,非常感谢 1.前言 ASM(Autom ...

  8. leetcode 1593. 拆分字符串使唯一子字符串的数目最大(DFS,剪枝)

    题目链接 leetcode 1593. 拆分字符串使唯一子字符串的数目最大 题意: 给你一个字符串 s ,请你拆分该字符串,并返回拆分后唯一子字符串的最大数目. 字符串 s 拆分后可以得到若干 非空子 ...

  9. CTFshow-萌新赛杂项_劝退警告

    下载附件 https://www.lanzous.com/i9wocah 下载后得到一个劝退警告.zip 解压得到一张gif图片 使用binwalk分析发现包含zip 于是拿到了一个压缩包 打开后发现 ...

  10. linux在终端中按下键盘立马反应

    想在终端中做个小应用,按下上下左右键能立刻作出反应. 测试程序见下: 1 #include <stdio.h> 2 #include <unistd.h> 3 #include ...