http://acm.hdu.edu.cn/showproblem.php?pid=3810
Magina
Time Limit: 60000/30000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 441 Accepted Submission(s): 149
Problem Description
Magina, also known as Anti-Mage, is a very cool hero in DotA (Defense of the Ancient).
If you have no idea of him, here is some brief introduction of his legendary antecedents:
Twin sons to the great Prophet, Terrorblade and Magina were blessed with divine powers: Terrorblade granted with an unnatural affinity with life forces; Magina gifted with energy manipulation. Magina's eventual overexposure to the magic gradually augmented his elemental resistances and bestowed him the unique ability to move faster than light itself. Now, broken by Terrorblade's fall to the dark side, Magina answers the Sentinel's call in a desperate bid to redeem his brother. Every bitter strike turns the Scourge's evil essences upon themselves, culminating in a finale that forces his enemy to awaken to the void within and spontaneously implode.
Magina has a very IMBA (imbalanced) skill – Blink, yes, move from one place to another place in a wink. Our problem begins at there.
As a formidable hero in the later stage, Magina always farm with the wild monsters for a long time. To make the farming more efficient, Magina use Blink frequently to jump here and there. Here we assume Blink skill has no CD, that is, we can use this skill at any time we want.
There are N spots of the wild monsters, and Magina can choose any one to begin. For every spots, Magina may use Ti time to kill the monsters and gain Gi units money, or he choose blink to other spots, which is known to our brilliant Magina. If the monsters in a spot were killed, it won’t appear any more.
Now Magina want to get M units money to but some excellent equipment, say Battle Fury for example. As a hero to save the world, there is no much time left for Magina, he wonders the minimum time for him to gain at least M units money.

If you have no idea of him, here is some brief introduction of his legendary antecedents:
Twin sons to the great Prophet, Terrorblade and Magina were blessed with divine powers: Terrorblade granted with an unnatural affinity with life forces; Magina gifted with energy manipulation. Magina's eventual overexposure to the magic gradually augmented his elemental resistances and bestowed him the unique ability to move faster than light itself. Now, broken by Terrorblade's fall to the dark side, Magina answers the Sentinel's call in a desperate bid to redeem his brother. Every bitter strike turns the Scourge's evil essences upon themselves, culminating in a finale that forces his enemy to awaken to the void within and spontaneously implode.
Magina has a very IMBA (imbalanced) skill – Blink, yes, move from one place to another place in a wink. Our problem begins at there.
As a formidable hero in the later stage, Magina always farm with the wild monsters for a long time. To make the farming more efficient, Magina use Blink frequently to jump here and there. Here we assume Blink skill has no CD, that is, we can use this skill at any time we want.
There are N spots of the wild monsters, and Magina can choose any one to begin. For every spots, Magina may use Ti time to kill the monsters and gain Gi units money, or he choose blink to other spots, which is known to our brilliant Magina. If the monsters in a spot were killed, it won’t appear any more.
Now Magina want to get M units money to but some excellent equipment, say Battle Fury for example. As a hero to save the world, there is no much time left for Magina, he wonders the minimum time for him to gain at least M units money.
Input
The first line contains a single integer T, indicating the number of test cases.
Each test case begins with two integers N, M. Their meanings are the same as the description.
Then N blocks follow, each one describes a spot of wild monsters.
The first line of each block is there integers Ti, Gi and Ki. Ti is the time, Gi is the units of money, Ki is the number of spots Magina can blink to from here.
Then Ki integer Cij follow, indicating the spots’ ID Magina can blink to. You may assume no ID would appear twice.
The spots are described with ID increasing from 1 to N. Input ensure if you can blink from i to j, you can also blink from j to i.
Technical Specification
1. 1 <= T <= 50
2. 1 <= N <= 50
3. 1 <= Ti <= 10000000
4. 1 <= M, Gi <= 1000000000
5. 1 <= Ki < N
6. 1 <= Cij <= N
Each test case begins with two integers N, M. Their meanings are the same as the description.
Then N blocks follow, each one describes a spot of wild monsters.
The first line of each block is there integers Ti, Gi and Ki. Ti is the time, Gi is the units of money, Ki is the number of spots Magina can blink to from here.
Then Ki integer Cij follow, indicating the spots’ ID Magina can blink to. You may assume no ID would appear twice.
The spots are described with ID increasing from 1 to N. Input ensure if you can blink from i to j, you can also blink from j to i.
Technical Specification
1. 1 <= T <= 50
2. 1 <= N <= 50
3. 1 <= Ti <= 10000000
4. 1 <= M, Gi <= 1000000000
5. 1 <= Ki < N
6. 1 <= Cij <= N
Output
For each test case, output the case number first, then the minimum time for Magina to gain at least M units money, if can’t, output “
Poor Magina, you can't save the world all the time!”.
Sample Input
3 1 4 2 5 0 1 5 1 4 0 4 10 1 9 0 3 3 1 3 3 3 2 2 4 4 4 1 3
Sample Output
Case 1: 2 Case 2: Poor Magina, you can't save the world all the time! Case 3: 10
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define nn 510
#define inff 0x3fffffff
using namespace std;
typedef long long LL;
int n,m;
int money[nn],sj[nn];
bool tu[nn][nn];
bool use[nn];
int lian;
LL sum[nn][nn];
vector<int>ve[nn];
int ans;
int l;
void dfs(int id)
{
use[id]=true;
ve[lian].push_back(id);
int i;
for(i=1;i<=n;i++)
{
if(tu[id][i]&&!use[i])
dfs(i);
}
}
void liantong()
{
lian=0;
int i;
for(i=1;i<=n;i++)
ve[i].clear();
for(i=1;i<=n;i++)
{
if(!use[i])
{
lian++;
dfs(i);
}
}
}
void go(int x,int id,int q,int s)
{
if(q>=m)//最优性剪枝
{
ans=min(ans,s);
return ;
}
if(s>=ans)//最优性剪枝
return ;
if(id==l)
{
return ;
}
if(sum[x][l]-sum[x][id]+q<m)//可行性剪枝,往后搜已经搜不到可行解了。
return ;
go(x,id+1,q,s);
go(x,id+1,q+money[ve[x][id]],s+sj[ve[x][id]]);
}
void slove(int x)
{
l=ve[x].size();
go(x,0,0,0);
}
bool cmp(int x,int y)
{
if(money[x]!=money[y])
return money[x]<money[y];
return sj[x]<sj[y];
}
int main()
{
int t,i,j;
scanf("%d",&t);
int cas=1;
while(t--)
{
memset(tu,false,sizeof(tu));
memset(use,false,sizeof(use));
scanf("%d%d",&n,&m);
int ix,u;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&sj[i],&money[i],&ix);
for(j=1;j<=ix;j++)
{
scanf("%d",&u);
tu[i][u]=tu[u][i]=true;
}
}
liantong();//求联通块
ans=inff;
for(i=1;i<=lian;i++)
{
sum[i][0]=0;
sort(ve[i].begin(),ve[i].end(),cmp);
for(j=0;j<(int)ve[i].size();j++)
{
sum[i][j+1]=sum[i][j]+money[ve[i][j]];
}
}
for(i=1;i<=lian;i++)
{
slove(i);
}
printf("Case %d: ",cas++);
if(ans==inff)
{
puts("Poor Magina, you can't save the world all the time!");
continue;
}
printf("%d\n",ans);
}
return 0;
}
这份代码跑了
14750 MS,如果将每个联通块的物品按金钱的进行排序大的在前,就能将时间优化到1687 MS,这样优化能在搜索中尽快找到可行解后进行剪枝,能让一些不必要的枝在层数相对浅一些的时候减掉,不过能优化10倍的时间确实没想到,具体为什么能优化这么多我也不太清楚。。。代码如下:
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define nn 510
#define inff 0x3fffffff
#define mod 20120427
using namespace std;
typedef long long LL;
int n,m;
int money[nn],sj[nn];
bool tu[nn][nn];
bool use[nn];
int lian;
LL sum[nn][nn];
vector<int>ve[nn];
int ans;
int l;
void dfs(int id)
{
use[id]=true;
ve[lian].push_back(id);
int i;
for(i=1;i<=n;i++)
{
if(tu[id][i]&&!use[i])
dfs(i);
}
}
void liantong()
{
lian=0;
int i;
for(i=1;i<=n;i++)
ve[i].clear();
for(i=1;i<=n;i++)
{
if(!use[i])
{
lian++;
dfs(i);
}
}
}
void go(int x,int id,int q,int s)
{
if(q>=m)
{
ans=min(ans,s);
return ;
}
if(s>=ans)
return ;
if(id==l)
{
return ;
}
if(sum[x][l]-sum[x][id]+q<m)
return ;
go(x,id+1,q,s);
go(x,id+1,q+money[ve[x][id]],s+sj[ve[x][id]]);
}
void slove(int x)
{
l=ve[x].size();
go(x,0,0,0);
}
bool cmp(int x,int y)
{
if(money[x]!=money[y])
return money[x]>money[y];
return sj[x]<sj[y];
}
int main()
{
int t,i,j;
scanf("%d",&t);
int cas=1;
while(t--)
{
memset(tu,false,sizeof(tu));
memset(use,false,sizeof(use));
scanf("%d%d",&n,&m);
int ix,u;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&sj[i],&money[i],&ix);
for(j=1;j<=ix;j++)
{
scanf("%d",&u);
tu[i][u]=tu[u][i]=true;
}
}
liantong();
ans=inff;
for(i=1;i<=lian;i++)
{
sum[i][0]=0;
sort(ve[i].begin(),ve[i].end(),cmp);
for(j=0;j<(int)ve[i].size();j++)
{
sum[i][j+1]=sum[i][j]+money[ve[i][j]];
}
}
for(i=1;i<=lian;i++)
{
slove(i);
}
printf("Case %d: ",cas++);
if(ans==inff)
{
puts("Poor Magina, you can't save the world all the time!");
continue;
}
printf("%d\n",ans);
}
return 0;
}
参见大牛们的解题报告,这个题竟然能用队列模拟背包做,膜拜啊!!!!由于在背包转移过程中很多无效的空间都是浪费的,比如说dp[ i ] [ j ] ,当i=0时,只有dp[ 0 ][ 0 ]有效,而其它空间都是无效的。所以我们只将有效的状态加入队列,用两个队列(类似滚动数组)来模拟背包过程,当然要用优先队列才能来将不是最优的状态减掉。如果单是按照背包的思路来进行剪枝,将相同花费,价值低的减掉,还是要MLE,因为N=50,能达到的状态还是太多。由于这个题求的是得到M或大于M价值的最短时间,所以对于价值为X的状态,我们要保留花费为最少为的,而如果得到的价值更少,花费更多,这样的状态肯定没有前一种状态更优,可以减掉,加上这个剪枝以后,神奇的0ms就A了,代码如下:
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<vector>
#include<algorithm>
#include<queue>
#include<stack>
#define nn 1100
#define inff 0x3fffffff
#define mod 1000000007
using namespace std;
typedef long long LL;
int n,m;
int w[55],g[55];
bool tu[55][55];
bool use[55];
int lian;
vector<int>ve[55];
struct node
{
int money,tm;
friend bool operator < (node aa,node bb)
{
if(aa.money!=bb.money)
return aa.money<bb.money;
return aa.tm>bb.tm;
}
};
priority_queue<node>que[2];
int slove(int x)
{
while(que[0].size())
que[0].pop();
while(que[1].size())
que[1].pop();
node ix,tem;
ix.money=0,ix.tm=0;
que[0].push(ix);
int i,fc;
fc=0;
int wy;
int re=inff;
for(i=0;i<(int)ve[x].size();i++)
{
// cout<<ve[x][i]<<endl;
while(que[fc].size())
{
tem=que[fc].top();
que[1-fc].push(tem);
que[fc].pop();
ix.money=tem.money+g[ve[x][i]];
ix.tm=tem.tm+w[ve[x][i]];
if(ix.money>=m)
{
re=min(re,ix.tm);
continue;
}
if(ix.tm>re)
continue;
que[1-fc].push(ix);
}
wy=inff;
while(que[1-fc].size())
{
tem=que[1-fc].top();
que[1-fc].pop();
if(tem.tm<wy)
{
wy=tem.tm;
que[fc].push(tem);
}
}
}
return re;
}
void dfs(int id)
{
use[id]=true;
ve[lian].push_back(id);
int i;
for(i=1;i<=n;i++)
{
if(tu[id][i]&&!use[i])
dfs(i);
}
}
void liantong()
{
memset(use,false,sizeof(use));
int i;
for(i=1;i<=n;i++)
ve[i].clear();
lian=0;
for(i=1;i<=n;i++)
{
if(!use[i])
{
lian++;
dfs(i);
}
}
}
int main()
{
int i,j,t;
scanf("%d",&t);
int cas=1;
while(t--)
{
memset(tu,false,sizeof(tu));
scanf("%d%d",&n,&m);
int ix,u;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&w[i],&g[i],&ix);
for(j=1;j<=ix;j++)
{
scanf("%d",&u);
tu[i][u]=true;
tu[u][i]=true;
}
}
printf("Case %d: ",cas++);
liantong();
int ans=inff;
for(i=1;i<=lian;i++)
{
// cout<<i<<"fadfasdf"<<endl;
ans=min(ans,slove(i));
}
if(ans==inff)
{
puts("Poor Magina, you can't save the world all the time!");
continue;
}
else
printf("%d\n",ans);
}
return 0;
}