一道普通的DP题。。写这道题的题解只是因为它是这个专题最后一题了_(:зゝ∠)_
时间限制比较大方10000MS,内存限制也拉到最大了,没啥特别要注意的条件。
题意是某国得到可能遭受核打击的情报【夭寿啦 侦测到在途核打击啦 _(:зゝ∠)_ 】,需要将人群撤离进避难所中,给予n只队伍的位置,以及m个避难所的位置,然后要求每个队伍都要进入避难所中,且每个避难所中至少有一只队伍,求最小的油耗和为多少(油耗为队伍走过的距离)。
一开始读来读去总感觉是个贪心就完了的事情,后来想想又不对,验证了有些情况是单纯的贪心处理不掉的,那就又是用DP上了。
将这些队伍和避难所的位置都从小到大排好,然后就是决定这些队伍的去向了:1.最简单明了的跑到最近的避难所中。(逃命要紧)2.做点贡献跑远点到一个没人的避难所中(题目要求每个避难所中至少有一只队伍,因为要人把门给关上,防止避难所被炸了)。第一个选择可以说是确定跑哪里去的,主要看第二个选择,如果选择进入一个无人避难所中,那这个避难所应该是最左边那个无人避难所,(我这里是从左到右处理的,反之就是最右边的无人避难所)。对于这个选择只要举个例子验证下就好,如果队伍和避难所的前后关系为:a-A-B-b(大写字母为避难所,小写字母为队伍),从左到右处理,如果a选择避难所B,那么b就只能选择避难所A,而他们走过的路线出现了反向交叉,即A-B这段路多跑了两次,所以我们要防止反向交叉路线出现(同向走没问题),那么就只要让避难所从左到右入户就行了。
那么递推公式就出来了dp [ i ] [ t ] = min { dp [ i-1 ] [ t ] + best (选项1), dp [ i-1 ] [ t-1 ] + Abs(Team [ i ] - Shelt [ t ])(选项2) },dp [ i ] [ t ]代表的是前i只队伍入户了前t个避难所的最优油耗,而其中的best是在前t个避难所中,离队伍i最近的避难所的距离,这个值只要在循环中跟着更新就行。
关于最后答案要求输出的每个队伍的去向,我只想表示,出题人你真的太闲了吗_(:зゝ∠)_ 为什么要这样为难我们
滚动数组(2*4000)求值+完整二维数组Map(4000*4000)记录路径,因为只有两种选择,我们可以从最终的答案Map [ n ] [ m ] 往回推出上一个队伍的去向,如果Map [ i ] [ t ]的值大于INF(0x3f3f3f3f)那么就跑到 Map [ i-1 ] [ t-1 ] 中,否则跑到 Map [ i-1 ] [ t ]中。
代码如下:
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef __int64 LL;
const int MAXM = 4002;
const int INF = 0x3f3f3f3f;
int Hav;
int Num;
int Ans[MAXM];
struct P
{
int id;
int val;
bool operator < (const P &a)const
{
return val<a.val;
}
}Team[MAXM],Shelt[MAXM];
LL A[MAXM],B[MAXM];
int Map[MAXM][MAXM];
int Abs(int a)
{
if(a>0)return a;
return -a;
}
void Read_Case()
{
scanf("%d",&Hav);
for(int i=1;i<=Hav;i++)
{
scanf("%d",&Team[i].val);
Team[i].id=i;
}
scanf("%d",&Num);
for(int i=1;i<=Num;i++)
{
scanf("%d",&Shelt[i].val);
Shelt[i].id=i;
}
sort(Team+1,Team+Hav+1);
sort(Shelt+1,Shelt+Num+1);
}
void Deal()
{
int best,pos,dis;
LL inf=(LL)INF*MAXM;
LL *Ago=A,*Now=B,*G;
for(int i=0;i<=Num;i++)Ago[i]=inf;
Ago[0]=0;
for(int i=1;i<=Hav;i++)
{
best=INF;
if(i<=Num)Ago[i]=inf;
for(int t=1;t<=i&&t<=Num;t++)
{
dis=Abs(Team[i].val-Shelt[t].val);
if(dis<best)
{
best=dis;
pos=t;
}
if(Ago[t-1]+dis>Ago[t]+best)
{
Now[t]=Ago[t]+best;
Map[i][t]=pos;
}
else
{
Now[t]=Ago[t-1]+dis;
Map[i][t]=t+INF;
}
}
Now[0]=Ago[0]=inf;
G=Now;Now=Ago;Ago=G;
}
pos=Num;
for(int i=Hav;i>0;i--)
{
if(Map[i][pos]>INF)
{
Ans[Team[i].id]=Shelt[Map[i][pos]-INF].id;
pos--;
}
else
{
Ans[Team[i].id]=Shelt[Map[i][pos]].id;
}
}
printf("%I64d\n",Ago[Num]);
for(int i=1;i<Hav;i++)
{
printf("%d ",Ans[i]);
}
printf("%d\n",Ans[Hav]);
}
int main()
{
Read_Case();
Deal();
}
其实第一发交后WA了,第二发才AC (* ̄ω ̄*)
因为输出的顺序要按原来的队伍顺序,咱快排后完全忘了这茬