HDU5808(Bestcoder Round86)Price List Strike Back

题目戳这

假如我们已知第i天可以购买的所有商品,那么剩下就是01揹包的问题了.能够购买的所有商品由两个条件决定:编号下标和距离.我们可以减少一个维度使问题更简单.

假如把询问和点一起按照距离d排序,那么就能保证询问到i时,所有考虑到的点x一定满足dis[x]<=di 这一条件.

现在问题就转化成了:

询问:在[Li,Ri] 区间中能否找到一些点满足其价值之和=sumi .

更新:点x的价值为val[x] .

对于区间问题,可以考虑线段树求解.既然询问价值和的可能性,而且sumi100 ,那么我们可以用一个bool数组h,记录这个区间所有能够取到的价值和,作为一个区间的状态.

对於单点更新x操作:

对每个经过x的区间用O(sum) 的复杂度更新h数组.

对区间查询[l,r] 操作:

O(sum2) 的复杂度对两个区间进行合并.

最终的复杂度为:O(mlogn10000+nlogn100) ,你会发现它和暴力没有什么差别T T.

这里有一个大优化!
对於单点更新x操作:
对于更新前 每个的h[j]=1 可以保证更新后 得到:h[j]=1 ,h[j+val[x]]=1 .
假如我们把h数组看成一个01串,那么h[j]=1 => h[j+val[x]]=1 可以把答案看作把这个串整体移动val[x] 位再按位或上更新前的01串的结果.

对于区间合并:

假设当前区间的01串为a1,两个儿子的01串分别为a2,a3.

a1|=a2.

a1|=a3.

对于a3的每个值为1的位i: a1|=a2<<i

那有什么能够高效地完成以上二进制的操作?!

bitset!!

它相当于把64位的数字接在一起,形成一个01串,并且支持二进制的所有运算,比如按位或,位移等.

复杂度:O(len/64) .len表示01串的长度.

用bitset完成操作后,时间复杂度为:

O(mlognsumsum/64+nlognsum/64) .

当然这种思路有一个神奇的解法:

可用BIT求一个区间内每个商品的个数.

商品的种类最多只有100种,而每种商品的数量却很多,这种情况可以选择多重揹包进行优化.把物品总数减少到100logn .在dp时依然可以用bitset优化,效果显著!!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
const int M=105;
int n,m;
int ans[100005];
int id=0,l,r,c,sum;
bitset<M>dp;
inline void rd(int &res){
    char c;res=0;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),c>47);
}
struct node{
    int v,d,id;
}A[20005];
struct LZ{
    int l,r,d,sum,id;
}Q[100005];
int bit[20005][105];
void add(int x,int y){
    while(x<=n){
        bit[x][y]++;
        x+=x&-x;
    }
}
int query(int x,int y){
    int sum=0;
    while(x){
        sum+=bit[x][y];
        x^=x&-x;
    }
    return sum;
}
bool cmp(node a,node b){return a.d<b.d;}
bool cmp1(LZ a,LZ b){return a.d<b.d;}
int val[20005];
int main(){
    int cas;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)rd(A[i].v);
        for(int i=1;i<=n;i++)rd(A[i].d),A[i].id=i;
        sort(A+1,A+n+1,cmp);
        for(int i=1;i<=m;i++){
            ans[i]=0;
            rd(Q[i].l);
            rd(Q[i].r);
            rd(Q[i].d);
            rd(Q[i].sum);Q[i].id=i;
        }
        sort(Q+1,Q+m+1,cmp1);
        int x=1;
        for(int i=1;i<=m;i++){
            while(x<=n&&A[x].d<=Q[i].d){
                add(A[x].id,A[x].v);
                x++;
            }
            int can=query(Q[i].r,Q[i].sum)-query(Q[i].l-1,Q[i].sum);
            if(can){ans[Q[i].id]=0;}
            else{
                dp.reset();
                dp[0]=1;
                int cnt=0;
                for(int j=1;j<Q[i].sum;j++){
                    int s=query(Q[i].r,j)-query(Q[i].l-1,j),p=1;
                    while(p<s){
                        val[++cnt]=p*j;
                        s-=p;
                        p<<=1;
                    }
                    if(s)val[++cnt]=s*j;
                }
                for(int j=1;j<=cnt;j++){
                    dp|=dp<<val[j];
                    if(dp[Q[i].sum])break;
                }
                if(!dp[Q[i].sum])ans[Q[i].id]=1;
            }
        }
        for(int i=1;i<=m;i++)putchar(ans[i]^48);
    return 0;
}

当然最稳(qi)定(pa)的正解的思路是分治!

官方题解传送门: http://bestcoder.hdu.edu.cn/

对于[L,R] 区间内的所有询问,我们可以分为[L,mid][mid+1,R] 两个区间,对于左右端点都在左区间或者都在右区间的询问递归求解.

那么现在只要回答左端点在左区间,右端点在右区间的询问.

假设dpl[i][j] 表示[I,mid] 达到价值和为j的最大距离最小值.

dpr[i][j] 表示[mid+1,r] 达到价值为j的最大距离最小值.

可以用(rl+1)sum 的复杂度处理出以上两个数组,对于每个询问,找到[li,ri] 区间内到达sumi的最大距离的最小值a ,再与di 进行判断就可以得到答案.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1e5+5;
const int N=2e4+5;
const int S=105;
const int oo=1e9+5;
struct node{
    int id,l,r,sum,d;
}Q[M];
inline void rd(int &res){
    res=0; char c;
    while (c=getchar(),c<48);
    do{
        res=(res<<3)+(res<<1)+(c^48);
    }while (c=getchar(),c>=48);
}
bool cmp(node a,node b){
    if(a.l!=b.l)return a.l<b.l;
    return a.r<b.r;
}
int dp[S][N],res[M],val[N],n,m,dis[N],q[M],st[N];
void solve(int L,int R){//Q.l<=mid;Q.r>=mid
    int mid=L+R>>1,i,j,tot=0;
    if(L==R){
        for(i=st[L];i<=st[R+1]-1;i++){
            if(Q[i].r==Q[i].l&&Q[i].l==L)q[++tot]=i;
        }
        for(i=1;i<=tot;i++){
            int id=q[i];
            if(Q[id].d>=dis[L]&&Q[id].sum==val[L])res[Q[id].id]=0;
            else res[Q[id].id]=1;
        }
        return ;
    }
    for(i=st[L];i<=st[mid+1]-1;i++){
        if(Q[i].r>mid&&Q[i].r<=R)q[++tot]=i;
    }
    if(tot){
        for(i=L;i<=R;++i){
            for(j=1;j<S;++j)dp[j][i]=oo;
            dp[0][i]=0;
        }
        dp[val[mid]][mid]=dis[mid];
        dp[val[mid+1]][mid+1]=dis[mid+1];
        for(i=mid-1;i>=L;--i){//dp[i][j]表示[i,mid]区间中,得到j 最远距离的最小值 
            for(j=0;j<S;++j){    
                dp[j][i]=dp[j][i+1];
                if(j>=val[i])dp[j][i]=min(dp[j][i],max(dp[j-val[i]][i+1],dis[i]));
            }
        }
        for(i=mid+2;i<=R;++i){
            for(j=0;j<S;++j){
                dp[j][i]=dp[j][i-1];
                if(j>=val[i])dp[j][i]=min(dp[j][i],max(dp[j-val[i]][i-1],dis[i]));
            }
        }
        for(i=1;i<=tot;++i){//横跨mid的询问 
            int id=q[i];
            int mi=oo,l=Q[id].l,r=Q[id].r,sum=Q[id].sum;
            for(j=0;j<=Q[id].sum;++j){
                mi=min(mi,max(dp[j][l],dp[sum-j][r]));
            }
            if(mi<=Q[id].d)res[Q[id].id]=0;
            else res[Q[id].id]=1;
        }
    }
    solve(L,mid);
    solve(mid+1,R);
}
int find(int x){//Q[i].l>=x 
    int l=1,r=m,res=m+1;
    while(l<=r){
        int mid=l+r>>1;
        if(Q[mid].l>=x){
            r=mid-1;
            res=mid;
        }else l=mid+1;
    }return res;
}
int main(){
    int i,j,k,cas,a,b,c;
    rd(n);rd(m);
    for(i=1;i<=n;i++)rd(val[i]);
    for(i=1;i<=n;i++)rd(dis[i]);
    for(i=1;i<=m;i++){
        rd(Q[i].l);rd(Q[i].r);rd(Q[i].d);rd(Q[i].sum);
        Q[i].id=i;
    }
    sort(Q+1,Q+1+m,cmp);
    for(i=1;i<=n;i++){//st[i]表示L>=i的第一个询问的下标 
        st[i]=find(i);
    }
    st[n+1]=m+1;
    solve(1,n);
    for(i=1;i<=m;i++){
        if(res[i])putchar('1');
        else putchar('0');
    }
    puts("");

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章