POJ-2923 Relocation---01背包+状态压缩

题目链接:

https://vjudge.net/problem/POJ-2923

题目大意:

有n个货物,给出每个货物的重量,每次用容量为c1,c2的火车运输,问最少需要运送多少次可以将货物运完

思路:

第一次做状态压缩(状态压缩基础知识传送门

本题的解题思路是先枚举选择若干个时的状态,总状态量为1<<n,判断这些状态集合里的那些物品能否一次就运走,如果能运走,那就把这个状态看成一个物品。预处理完能从枚举中找到tot个物品,再用这tol个物品中没有交集(也就是两个状态不能同时含有一个物品)的物品进行01背包,每个物品的体积是state[i](state[i]表示一次可以运完状态i的物品,i的二进制表示i这个状态的物品),价值是1,求包含n个物品的最少价值也就是dp[(1<<n)-1](dp[i]表示状态i需要运的最少次数)。

状态转移方程:dp[j|k] = min(dp[j|k],dp[k]+1) (k为state[i],1<=j<=(1<<n)-1])。

 1 #include<iostream>
 2 #include<vector>
 3 #include<queue>
 4 #include<algorithm>
 5 #include<cstring>
 6 #include<cstdio>
 7 #include<set>
 8 #include<cmath>
 9 using namespace std;
10 typedef pair<int, int> Pair;
11 typedef long long ll;
12 const int INF = 0x3f3f3f3f;
13 const int maxn = 2000+10;
14 int T, n, m1, m2;
15 int a[20], cnt[maxn], dp[maxn], tot, cases;
16 bool vis[maxn];
17 bool judge(int x)
18 {
19     int sum = 0;
20     memset(vis, 0, sizeof(vis));//vis[i]=1表示m1的车子中可以凑出体积为i的物品
21     vis[0] = 1;
22     for(int i = 0; i < n; i++)
23     {
24         if(x & (1 << i))//第i件物品存在
25         {
26             sum += a[i];
27             for(int j = m1; j >= a[i]; j--)
28                 if(vis[j - a[i]])vis[j] = 1;//此处必须是逆序,因为更新vis[j]的时候要用到vis[j-a[i]],和01背包是一样的
29         }
30     }
31     for(int i = 0; i <= m1; i++)
32     {
33         if(vis[i] && sum - i <= m2)//确保全部物品可以一次性放在两个车子里面
34             return true;
35     }
36     return false;
37 }
38 void init()
39 {
40     memset(dp, INF, sizeof(dp));
41     dp[0] = 0;
42     for(int i = 1; i < (1 << n); i++)
43     {
44         if(judge(i))
45         {
46             cnt[tot++] = i;
47         }
48     }
49 }
50 int main()
51 {
52     cin >> T;
53     while(T--)
54     {
55         cin >> n >> m1 >> m2;
56         tot = 0;
57         for(int i = 0; i < n; i++)cin >> a[i];
58         init();/*
59         for(int i = 0; i < tot; i++)
60             cout<<cnt[i]<<endl;*/
61         for(int i = 0; i < tot; i++)//枚举物品
62         {
63             for(int j = (1 << n) - 1; j >= 0; j--)//逆序枚举状态也是因为dp[j]的更新需要先用到dp[j-***]
64             {
65                 if(dp[j] == INF)continue;
66                 if((j & cnt[i]) == 0)//两者无交集
67                     dp[j | cnt[i]] = min(dp[j | cnt[i]], dp[j] + 1);
68                 //dp[j | cnt[i]]表示j这个状态加上第i件物品的值,可以从dp[j]+1推过去
69             }
70         }
71         printf("Scenario #%d:\n", ++cases);
72         printf("%d\n\n", dp[(1<<n) - 1]);
73 
74     }
75 }

 

posted @ 2018-04-12 19:44  _努力努力再努力x  阅读(434)  评论(0编辑  收藏  举报