POJ 3963 Evacuation Plan dp

一道普通的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  (* ̄ω ̄*)

 因为输出的顺序要按原来的队伍顺序,咱快排后完全忘了这茬




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值