Magina (超大容量的01背包)
题目大意:
有n堆野兽,每堆野兽屠杀完完需要花费ti时间,可以增加金钱gi,敌法师有瞬移技能,可以从某堆野兽移到另一堆野兽,题目有给定从哪堆可以移到哪堆。最后问在满足打的金钱多余m的情况下的最少时间。数据范围:1 <= T <= 50 , 1 <= N <= 50(怪物数量), 1 <= Ti <= 10000000(时间),1 <= M, Gi <= 1000000000(要求金钱/每组野怪金钱)。
解法:
看到题目很明显的0/1背包,但是看到数据范围发现是个超大容量的01背包,那么我们就肯定不能使用DP来解决这个问题了。我们想办法用别的数据结构模拟解01背包的过程,于是就用到 优先队列模拟01背包做DP
假定用q0队列表示上一行,q1队列表示当前行。那么对于当前物品i,我们就从上一行状态q0转移到q1,那么也就是枚举q0的每个状态然后对物品i有两种选择:选或者不选,想起来很简单。可惜这样会获得MLE/TLE。为什么?因为每次都选和不选就相当于暴搜了,时间复杂度是2^50次方肯定不行。我们得剪枝,最关键的是其实同一时间的状态有很多都是没有用的,钱比别人少时间却还比别人多,这样的状态没有用!!!于是我们要用到优先队列,金钱多的排前面然后同样多钱的时间少的排前面,那么我们筛选本次拓展出来的状态哪些是有用的?因为我们优先队列已经是按金钱多排前面,所以此时时间单调减的状态才有用!!!
于是加上上面这个大剪枝时候我们再加些容易想到的小细节就可以获得AC了。
#include<iostream> #include<cstdio> #include<queue> using namespace std; const int N=60; const int INF=0x7fffffff; int n,m,k,w[N],v[N],fa[N]; struct dat{ int x,y; //时间,金钱 bool operator < (const dat &rhs) const { return y<rhs.y || y==rhs.y && x>rhs.x; } }; priority_queue<dat> q1,q2; int getfa(int x) { return x==fa[x] ? x : fa[x]=getfa(fa[x]); } int main() { int T,kase=0; scanf("%d",&T); while (T--) { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) fa[i]=i; for (int i=1;i<=n;i++) { scanf("%d%d%d",&w[i],&v[i],&k); for (int j=1;j<=k;j++) { int x; scanf("%d",&x); fa[getfa(x)]=getfa(i); } } int ans=INF; for (int i=1;i<=n;i++) if (getfa(i)==i) { //一个联通块的物品 while (!q1.empty()) q1.pop(); while (!q2.empty()) q2.pop(); q1.push((dat){0,0}); for (int j=1;j<=n;j++) { //j个物品 if (getfa(j)!=i) continue; while (!q1.empty()) { dat u=q1.top(); q1.pop(); if (u.y>=m) { ans=min(ans,u.x); continue; } //剪枝1 if (u.x>=ans) continue; //剪枝2 q2.push(u); //不选 int x=u.x+w[j],y=u.y+v[j]; if (y>=m) { ans=min(ans,x); continue; } //剪枝1 if (x>=ans) continue; //剪枝2 q2.push((dat){x,y}); //选 } int Min=INF; while (!q2.empty()) { dat u=q2.top(); q2.pop(); if (u.x<Min) q1.push(u),Min=u.x; //剪枝3:钱比别人少时间却还比别人多,这样的状态没有用 } } } if (ans==INF) printf("Case %d: Poor Magina, you can't save the world all the time!\n",++kase); else printf("Case %d: %d\n",++kase,ans); } return 0; }