hdu5808 整体二分

题目描述

在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,gsi)) ,然后和c比较一下大小就好了。

时间复杂度 O(100(nlogn+m)+mlogn)

在这里f和g可以合并到一个数组里面

#include
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];

你可能感兴趣的:(整体二分)