题目链接:
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: ", ++cases); 72 printf("%d ", dp[(1<<n) - 1]); 73 74 } 75 }