題目描述
在Byteland一共有nn家商店,編號依次爲11到nn。每家商店只會賣一種物品,其中第ii家商店的物品單價爲
Byteasar每天都會進行一次購物,第ii天他會選擇一個區間[
Byteasar的數學不好,他可能會把花的錢記錯。
請寫一個程序,幫助Byteasar判斷每條記錄是否一定是錯的。
注意:記多或者記少都算記錯。
解法
離線處理查詢,使用整體二分。
官方題解寫的比較清楚:
考慮對序列進行分治,設當前分治區間爲[l,r],取mid=(l+r)/2,那麼所有在[l,mid)的詢問和(mid,r]的詢問可以遞歸分治求解,故只需考慮必然經過mid的詢問。
設f[i][j]表示考慮了[i,mid],目前選出的物品和爲j時,所選商店到家的距離的最大值最小是多少;g[i][j]表示考慮了(mid,i],目前選出的物品和爲j時,所選商店到家的距離的最大值最小是多少,f和g都能在O(100(r-l+1))的複雜度內求出。那麼對於一個詢問l,r,s,cl,r,s,c,只需要求出
min(max(fi,gs−i)) ,然後和c比較一下大小就好了。時間複雜度
O(100(nlogn+m)+mlogn) 。
在這裏f和g可以合併到一個數組裏面
#include<bits/stdc++.h>
using namespace std;
const int QSIZE = 100005;
const int SIZE = 20005;
int n, m;
struct node {
int L, R, i, c, sum;
void read(int _i) {
i = _i;
scanf("%d%d%d%d",&L,&R,&c,&sum);
}
} q[QSIZE], temp[QSIZE];
int res[QSIZE];
int v[SIZE];
int d[SIZE];
int f[SIZE][105];
const int INF = 0x7fffffff;
void updatef(int l, int r) {
for(int i = r; i >= l; i--) {
for(int j = 0; j <= 100; j++) {
if(j - v[i]>= 0) f[i][j] = min(f[i+1][j], max(f[i+1][j-v[i]], d[i]));
else f[i][j] = f[i+1][j];
}
}
}
void updateg(int l, int r) {
for(int i = l; i <= r; i++) {
for(int j = 0; j <= 100; j++) {
if(j - v[i] >= 0) f[i][j] = min(f[i-1][j], max(f[i-1][j-v[i]], d[i]));
else f[i][j] = f[i-1][j];
}
}
}
void solve(int l, int r, int ql, int qr) {
if(ql > qr) return;
if(l == r) {
for(int i = ql; i <= qr; i++) {
res[q[i].i] = v[r] == q[i].sum && d[r] <= q[i].c;
}
return;
}
int mid = (l + r) >> 1, w1 = ql, w2 = qr, w3 = 0; //三組不同查詢的遊標
for(int i = ql; i <= qr; i++)
{
if (q[i].R <= mid) q[w1++] = q[i];
else if (q[i].L > mid) temp[w2--] = q[i];
else temp[w3++] = q[i];
}
for(int i = w2 + 1; i <= qr; i++) q[i] = temp[i];
if(w3) {
for(int i = 1; i <= 100; i++) f[mid][i] = f[mid + 1][i] = INF;
f[mid][0] = f[mid+1][0] = 0;
f[mid][v[mid]] = d[mid];
f[mid+1][v[mid+1]] = d[mid+1];
updatef(l, mid-1);
updateg(mid+2, r);
}
for(int i = 0; i < w3; i++) {
for(int j = 0; j <= temp[i].sum; j++)
res[temp[i].i] |= max(f[temp[i].L][j], f[temp[i].R][temp[i].sum - j]) <= temp[i].c;
}
solve(l, mid, ql, w1 - 1);
solve(mid + 1, r, w2 + 1, qr);
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) scanf("%d",&v[i]);
for(int i = 1; i <= n; i++) scanf("%d",&d[i]);
for(int i = 1; i <= m; i++) q[i].read(i);
memset(res, 0, sizeof(res));
solve(1, n, 1, m);
for(int i = 1; i <= m; i++) printf("%d", res[i] ^ 1);
puts("");
}
//scanf("%d",&n);
return 0;
}
整體二分查詢區間分割
自己寫的比較挫,借鑑了別人的寫法,學到了。
int mid = (l + r) >> 1, w1 = ql, w2 = qr, w3 = 0; //三組不同查詢的遊標
for(int i = ql; i <= qr; i++)
{
if (q[i].R <= mid) q[w1++] = q[i];
else if (q[i].L > mid) temp[w2--] = q[i];
else temp[w3++] = q[i];
}
for(int i = w2 + 1; i <= qr; i++) q[i] = temp[i];