你們好,歡迎來到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;
}
在線變離線是競賽題當中很是經常使用的技巧,常常被用來解決一些查詢量很是大的問題。說穿了其實並不難,可是若是不知道想要憑本身幹想出來則有些麻煩。你們有時間,最好本身親自用代碼實現體會一下。
今天的算法題就聊到這裏,衷心祝願你們天天都有所收穫。若是還喜歡今天的內容的話,請來一個三連支持吧~(點贊、關注、轉發)