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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章