P1064 [NOIP2006 提高组] 金明的预算方案

原题链接

考察:分组背包dp

错误思路:
       设置f[i][j]前i组选j个的集合,但这样划分集合没有考虑钱数,求不到最优解.pass

       设置f[i][j][k]表示前i组选j个体积为k的三维数组,先不说有MLE的风险,而且枚举几个的时候很难计算体积

正确思路:

       由上面应该要联想到状压dp,在能枚举出几个的情况下,还能计算相应的体积与价值.将每个无父节点的结点分别看成一组,因为子节点很少所以可以用二进制枚举选择的情况.每组的每个情况都是互斥,最后融合的体积为m,因此是分组背包模型.

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <vector>
 5 using namespace std;
 6 const int N = 32010,M = 65;
 7 int n,m,v[M],w[M],f[N];
 8 vector<int> b[M],tmp[M],val[M];
 9 void init()
10 {
11     for(int i=0;i<b[0].size();i++)
12     {
13         int sz = b[b[0][i]].size(),t =b[0][i];
14         tmp[i+1].push_back(0);
15         val[i+1].push_back(0);
16         for(int j=0;j<1<<sz;j++)
17         {
18             int res = v[t]*w[t],price = v[t];
19             for(int k=0;k<sz;k++)
20               if(j>>k&1)
21               {
22                   res+=v[b[t][k]]*w[b[t][k]];
23                   price+=v[b[t][k]];
24               }
25             tmp[i+1].push_back(res);
26             val[i+1].push_back(price);
27         }
28     }
29 }
30 int main()
31 {
32     scanf("%d%d",&m,&n);
33     for(int i=1;i<=n;i++)
34     {
35         int q;
36         scanf("%d%d%d",&v[i],&w[i],&q);
37         b[q].push_back(i);
38     }
39     init();
40     for(int i=1;i<=b[0].size();i++)
41       for(int j=m;j>=0;j--)
42         for(int k=0;k<tmp[i].size();k++)
43          if(j>=val[i][k]) f[j] = max(f[j],f[j-val[i][k]]+tmp[i][k]);
44     printf("%d\n",f[m]);
45     return 0;
46 }
方法一

      以上只适用于子节点很少的情况,一旦子节点个数多了就会TLE到难以直视.这道题本质是求选父节点和不选父节点的最大值.在已经确定选择父节点的情况下,可以将二进制枚举方案数换成01背包,这样时间复杂度就从O(2m)变成O(m).

     先计算只有父节点的情况,在此情况下计算01背包,最后将选父节点与不选父节点进行比较.(一定要计算完父子节点01背包的情况再与选不选父节点比较)

 1 #include <iostream>
 2 #include <cstring>
 3 #include <vector>
 4 using namespace std;
 5 const int N = 32010,M = 61;
 6 typedef pair<int,int> PII;
 7 int w[N],v[N],n,m,f[M][N];
 8 vector<PII> son[M];
 9 PII fa[M];
10 int main()
11 {
12     scanf("%d%d",&m,&n);
13     for(int i=1;i<=n;i++)
14     {
15         int p;
16         scanf("%d%d%d",&v[i],&w[i],&p);
17         if(p) son[p].push_back({v[i],v[i]*w[i]});
18         else fa[i] = {v[i],v[i]*w[i]};
19     }
20     for(int i=1;i<=n;i++)
21     {
22         if(fa[i].first)
23         {
24             for(int j=fa[i].first;j<=m;j++)//加上父节点
25               f[i][j] = f[i-1][j-fa[i].first]+fa[i].second;
26             for(int k=0;k<son[i].size();k++)
27              for(int q = m;q>=son[i][k].first;q--)
28               if(f[i][q-son[i][k].first]) 
29                f[i][q] = max(f[i][q-son[i][k].first]+son[i][k].second,f[i][q]);
30         }
31         for(int j=0;j<=m;j++)//没选父节点传递下去.
32           f[i][j] = max(f[i-1][j],f[i][j]);
33     }
34     printf("%d\n",f[n][m]);
35     return 0;
36 }
优化代码

 

posted @ 2021-03-30 09:35  acmloser  阅读(115)  评论(0编辑  收藏  举报