题目描述
在Byteland一共有nn家商店,编号依次为11到nn。每家商店只会卖一种物品,其中第ii家商店的物品单价为 vi ,且它到Byteasar的家的距离为 di 。
Byteasar每天都会进行一次购物,第ii天他会选择一个区间[ li , ri ],并给自己设定一个距离上限 ci ,然后他会在编号在该区间内每家到自己家的距离不超过 ci 的商店购买最多一件物品,当然他也可以选择什么都不买。回家之后,Byteasar会把今天购物所花的钱的总数 sumi 记录在账本上。
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];