1. 程式人生 > >CH2401 送禮物(雙向搜尋)

CH2401 送禮物(雙向搜尋)

題目

作為懲罰,GY被遣送去幫助某神牛給女生送禮物(GY:貌似是個好差事)但是在GY看到禮物之後,他就不這麼認為了。某神牛有N個禮物,且異常沉重,但是GY的力氣也異常的大(-_-b),他一次可以搬動重量和在w(w<=2^31-1)以下的任意多個物品。GY希望一次搬掉儘量重的一些物品,請你告訴他在他的力氣範圍內一次效能搬動的最大重量是多少。

題解

雙向搜尋 如果從一個方向搜尋,會不斷的產生分支,搜尋樹呈△,在深層子樹上浪費的時間相當多。 如果可以從兩個方向同時開始搜尋,那麼每次搜尋的任務只有一半,搜尋樹呈◇。 雙向搜尋把兩棵搜尋樹合併在一起,產生兩棵深度減半的搜尋樹,在中間交匯出現答案。因為這樣的可以搜尋覆蓋整個狀態空間,故答案的正確性很有保障。

雙向搜尋需要題目給出明確的“初態”“終態”,這題完全符合。 第一次搜尋列舉用1~n/2號的物品能產生的重量。第二次列舉用n/2+1~n號物品能產生的重量。在中間交匯的地方,用過尋找前驅的方法,使得搜尋2得到的重量+搜尋1中的狀態 最接近w。

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=60;

int w,n,ans=0;
int a[maxn];

bool cmp(int a1,int a2)
{
    return a2>a1;
}

int m=0,b[(1<<23)+10];
long long now=0;
void dfs_1(int k,int ed)//1~mid
{
    if(now>w) return ;
    if(k>ed)
    {
        b[++m]=now;
        return ;
    }
    dfs_1(k+1,ed);
    
    now+=a[k];
    dfs_1(k+1,ed);
    now-=a[k];
}

void dfs_2(int k,int ed)
{
    if(now>w) return ;
    if(k>ed)//找前驅 
    {
        if(w-now<b[1]) return ;
        int tmp=upper_bound(b+1,b+m+1,w-now)-b-1;
        tmp=now+b[tmp];
        if(tmp>ans) ans=tmp;
        return ;
    }
    dfs_2(k+1,ed);
    
    now+=a[k];
    dfs_2(k+1,ed);
    now-=a[k];
}

int main()
{
    scanf("%d%d",&w,&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+n+1,cmp);
    dfs_1(1,n/2);
    sort(b+1,b+m+1);
    dfs_2(n/2+1,n);
    printf("%d\n",ans);
    return 0;
}