1. 程式人生 > >Greetings!(列舉子集+dp)

Greetings!(列舉子集+dp)

題意:

給n個信(信封有長寬和數目),問在找最多k種信封型別的時候,最少浪費多少紙

思路:

看了看題解,深以為然,這個思路很巧妙,也許是我太菜,沒細想。

我們首先二進位制列舉計算出,每種狀態下,這些信合用同一種信封時的浪費數目。

然後通過列舉子集,dp[ i ] [ j ] 代表 i 種信封,現在已經裝了 j 集合的信封的最小浪費數。

很顯然,對於每種ij    我們從j的子集種去獲取最小值,那麼就是列舉  j 的子集,然後從之前已經退出的 dp[i-1] [ prej ] 中獲得最小答案。

具體實現看程式碼,感覺思路很清晰很巧妙。

狀壓果然優秀。

程式碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#define ll long long
using namespace std;
const int maxn = (1<<15)+20;
ll dp[20][maxn];
int W[maxn],H[maxn];
ll Q[maxn],C[maxn],cost[maxn];
int w[maxn],h[maxn],q[maxn];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0; i<n; i++)
    {
        scanf("%d%d%d",&w[i],&h[i],&q[i]);
    }
    for(int i=0; i<(1<<n); i++)
    {
        for(int j=0; j<n; j++)
        {
            if(i&(1<<j))
            {
                W[i] = max(W[i],w[j]);
                H[i] = max(H[i],h[j]);
                Q[i] +=q[j];
                C[i] +=((long long)w[j]*h[j]*q[j]);
            }
        }
        cost[i] = (long long)W[i]*H[i]*Q[i] - C[i];
      //  cout<<cost[i]<<" "<<W[i]<<" "<<C[i]<<endl;
    }
    const ll inf = 1e12*20;
    for(int i=0; i<=k;i++)
        for(int j=0; j<(1<<n); j++)
               dp[i][j] = inf;
    dp[0][0] = 0;
    for(int i=1; i<=k; i++)
        for(int j=0; j<(1<<n); j++)
    {
        ll cnt = inf;
        for(int k=j; k>0; k = ((k-1)&j))
        {
            cnt = min(cnt,dp[i-1][j-k]+cost[k]);
        }
//        if(j==(1<<n)-1)
//            cout<<cnt<<endl;
        dp[i][j] = cnt;
    }
    ll ans = inf;
    int d = (1<<n)-1;
    for(int i=1; i<=k; i++)
        ans = min(ans,dp[i][d]);
    printf("%lld\n",ans);

    return 0;
}