codeforces 1461D,離線查詢是什麼神仙方法,為什麼快這麼多?

2021-03-02 TechFlow

大家好,歡迎來到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表示請求的數量(

接下來的q行每行有一個整數,表示我們查詢的數字k(

對於每一個請求我們輸出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最多情況下會有

為了解決這個問題,我們引入了離線機制

這裡的離線在線很好理解,所謂的在線查詢,也就是我們每次獲得一個請求,查詢一次,然後返回結果。而離線呢則相反,我們先把所有的請求查詢完,然後再一個一個地返回。很多同學可能會覺得很詫異,這兩者不是一樣的麼?只不過順序不同而已。

大多數情況下的確是一樣的,但有的時候,我們離線查詢是可以批量進行的。比如這道題,我們可以一次性把所有可以構成的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 202011 月賽
    零、背景上周日,上午我沒吃早飯參加了 10:30 ~ 12:00 的 leetcode 周賽,下午又沒吃午飯參加了 12:00 ~ 17:00 codeforces 的 202011 月賽。晚上 19:00 的牛客練習賽實在是做不動了,遂放棄。這次 codeforces 的月賽相當殘暴,出了 13 道題。
  • Codeforces44E
    https://codeforces.com/problemset/problem/44/E *Difficulty:1400
  • Codeforces1443D
    https://codeforces.com/contest/1443/problem/D *Difficulty:
  • codeforces 2020年12月月賽算法比賽
    零、背景這周日在 codeforces 上參加了某高校 12 月份的校賽,涉及到不少算法,分享給大家。比賽原始碼:https://github.com/tiankonguse/ACM/tree/master/codeforces/gym/306556A. 暴打出題人題意:給一個數組 n 個數字,挑選若干個數字的和可以組成一個數字。
  • [洛穀日報第19期]Codeforces遊玩攻略
    網址Codeforces 在線評測系統的網站為 https://www.codeforces.com/) 。現在,您可以在瀏覽器中輸入該網址或單擊左側連結進入Codeforces在線評測系統。3.比賽種類Codeforces上舉行的比賽一般有4種,分別是Div.1,Div.2,Div.3和Educational Round。先講講Educational Round,Educational Codeforces Round一般題目較多,採用擴展ACM-ICPC賽制,即提交代碼立即出結果,錯誤一次計10分鐘罰時。
  • 經緯度編碼方法推薦-plus code簡介
    加上國家的法律因素,連通過經緯度導航都不是一個可行的方法。世界上確實有無法使用門址表示的地方,而經緯度的數值也超出了常人的可記憶範疇,所以Google希望通過一種編碼方法,簡單明了地表示世界上任一位置。使用字符串編碼來表示經緯度,其實有多種編碼方案,但plus code有什麼優勢?我們後面再講。
  • [洛穀日報第38期]淺談如何在 Codeforces 下分
    在 Codeforces 中下分需要極高的策略與技巧,同時,也需要持之以恆的耐心。本文中筆者將結合一些具體例子,講述一些在 Codeforces 中下分的必要條件和技巧。0.下分有什麼用div.1 選手下分以作為正式選手參加 div.2,從而實現阿克或虐場等裝弱;爭頂 rating 倒數榜,成為傳奇佳話。
  • Codeforces Lonely day 題解
    Codeforces Lonely day 題解題目解讀:這道題的信息量比較大,咱們慢慢捋一下。給定N x M的網格,上面有起始位置S和結束位置E,S和E均為乾淨點可以穿行,中間有乾淨的點.我們知道求起點到終點的最短路徑和求終點到起點的最短路徑是相同的,那麼我們可以先求出終點到各個乾淨點的距離存在數組d[MAX_VAL]中,當我們再從起點往下一個點走的時候,假設起點為u,下一個點為v,我們只要判斷這個起點到終點的距離比下一個點到終點的距離多1,那麼就代表下一個點肯定在最短路徑上(因為圖中每兩個能通行的點的距離為1),因此我們可以先反向廣搜
  • Codeforces Magic spells 題解
    Codeforces Magic spells 題解題目描述題目解讀
  • Java面試題之為什麼要重寫hashcode( )和equals( )?
    好了,請看下題:為什麼要重寫hashcode( )和equals( )?打個比方,一個名叫張三的人去住酒店,在前臺登記完名字就去了99層100號房間,此時警察來前臺找叫張三的這個人住在哪間房,經過查詢,該酒店住宿的有50個叫張三的,需要遍歷查詢,查詢起來很不方便。
  • 淺談Java中的hashcode方法
    在Java的Object類中有一個方法:public native int hashCode();根據這個方法的聲明可知,該方法返回一個int類型的數值,並且是本地方法,因此在Object類中並沒有給出具體的實現。為何Object類需要這樣一個方法?它有什麼作用呢?今天我們就來具體探討一下hashCode方法。
  • 經典面試題:重寫equals方法時,為什麼必須重寫hashCode方法?
    今天給大家來個經典面試題:重寫equals方法時,為什麼必須重新hashCode方法?這2個方法位於Object類中,Object類是所有類的父類,所以如果你的類中沒有重寫這2個方法時,默認將使用Object中的這2個方法。
  • 淺談 Java 中的 hashcode 方法
    在Java的Object類中有一個方法:public native int hashCode();根據這個方法的聲明可知,該方法返回一個int類型的數值,並且是本地方法,因此在Object類中並沒有給出具體的實現。為何Object類需要這樣一個方法?它有什麼作用呢?今天我們就來具體探討一下hashCode方法。
  • 乾貨 | 名企高頻考點-Java hashCode() 方法 和 equals() 方法
    為什麼我們有時候要重寫hashCode() 方法 和 equals() 方法?1. 你知道hashCode() 方法 和 equals() 方法嗎?當被面試官問到這個問題的時候,我們首先需要回答清楚兩個方法在沒有重寫之前,他們是如何工作的?
  • 當下這麼熱門的三元店是個什麼神仙項目?三元店
    3元店3元店加盟3元店批發三元店3元店3元店加盟3元店批發三元店首先,三元店這樣的店,其實與10元店啦、5元店啦等這些快時尚百貨實體店相似,只不過與它們相比較,三元店內的所有商品是全場3元哦!3元店3元店加盟3元店批發三元店那麼三元店加盟這個項目為什麼會這麼熱門呢?因為它被越來越多的消費者所接受,且獲得消費者強烈的認可!為什麼呢?
  • 為什麼Elasticsearch查詢變得這麼慢了?
    2、開發維度—你的查詢有多慢?第一步是查看發送到群集的查詢所花費的時間。 在研究如何打開慢速日誌時,Elasticsearch文檔可能有點不清楚,因此我將在下面展示一些示例。默認情況下,所有版本的Elasticsearch都會關閉慢速日誌,因此您必須對群集設置和索引設置進行一些更新。
  • Python 打包程序做個天氣查詢軟體
    摘要:Python 打包程序,製作實用天氣查詢軟體。作者 | flywind來源 | Pyhton高效編程通常我們查詢天氣都是在 App 或者網頁中搜索,其實這點事兒 Python 也能幹,比如像下面這樣,簡單純粹。img今天就來學學怎麼製作出一個天氣查詢軟體。
  • 重寫equals()時為什麼要重寫hashcode
    重寫equals()方法時,為什麼要重寫hashcode方法?1.==和equals()的區別(1)== 是比較運算符,equals()是在Object中定義的一個方法。3.重寫equals()方法時,為什麼要重寫hashcode方法?
  • 莫隊新科技——二次離線莫隊入門
    緣起掌握莫隊核心科技,來入坑一下二次離線莫隊~ 本文的例題是 洛谷 P4887 模板 莫隊二次離線(第十四分塊(前體))分析珂朵莉給了你一個序列a,每次查詢給一個區間 [l,r]查詢 l<=i<j<
  • java工程師必知必會的 hashcode 和 hash 算法!
    String 類型的 hashcode 方法為什麼大部分 hashcode 方法使用 31HashMap 的 hash 算法的實現原理(為什麼右移 16 位,為什麼要使用 ^ 位異或)HashMap 為什麼使用 & 與運算代替模運算?HashMap 的容量為什麼建議是 2 的冪次方?