Educational Codeforces Round 19 F(dp+队列优化)

58 篇文章 1 订阅
19 篇文章 0 订阅

网址:http://codeforces.com/contest/797/problem/F

显然,要先排序。

我们可以先处理出所有老鼠到所有洞的距离,p[i][j]表示第j只老鼠到第i个洞的距离。由于我们每次求的时候都是一段区间的老鼠到一个洞中,所以我们可以通过求前缀和优化计算。

对于p[i][j],其前缀和为d[i][j],表示前j只老鼠都进入i洞中所需要的花费。在实际运算中,因为我是每更换一个i,就从新求一次d[j],所以d用一维数组存储。

显然,本题我们可以用dp[i][j]表示前i个洞放前n只耗子的最小花费。则dp[i][j] = min{dp[i - 1][k]  + d[i][j] - d[i][k-1]},其中 j - b[i] <= k <= j,(b[i]为第i个洞的容量,此方程就是说枚举进入当前洞的老鼠的数量,找出最优解。

记住!只有形如 dp[i]=max/min (f[k]) + g[i]  k<i && g[i]是与k无关的变量)才能用到单调队列进行优化。

优化的对象就是f[k]


但是,现在我们的复杂度为O(n^2 * m),复杂度过高,故需使用队列优化。

我们注意到,将dp方程进行一些变化后,可以得到dp[i][j] = min{dp[i - 1][k]  - d[i][k - 1]} +d[i][j],显然,min中的部分是一个关于k的函数,并且每次要求的是k在一定范围内的最小值,而且区间范围是单调变化的(区间起点终点一直在增加)。那么我们就可以对这个函数进行队列优化。

设置一个双端队列,保存当前区间的最小值。显然,对于两个点p,q,p < q,若p处的值比q还要大,那么p处的值永远都不可能被使用了,所以就将这个值剔除。每次区间延长时,将新加入的值与原队列末尾的值进行比较,如果新值较小,就将原队尾的的值剔除,再比较,直到不再满足条件,再将新值插入到队列尾端。而每次求当前区间的最小值,就是队列的第一个值。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = 5000 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n,m;
ll dp[maxn][maxn];
ll d[maxn];
deque<ll> deq;
struct Hole{
	ll a;
	ll b;
	bool operator <(const Hole & p) const {return a < p.a;}
}holes[maxn];
ll p[maxn];
int main()
{
	scanf("%I64d%I64d",&n,&m);
	for(int i = 1;i <= n;i++){
		scanf("%I64d",&p[i]);
	}
	for(int i = 1;i <= m;i++) {scanf("%I64d%I64d",&holes[i].a,&holes[i].b);}
	sort(p + 1,p +n +1);
	sort(holes + 1,holes + m + 1);
	memset(dp,127,sizeof(dp));
	for(int i = 0;i <= m;i++) dp[i][0] = 0;
	for(int i = 1;i <= m;i++){
		d[0] = 0;for(int j = 1;j <= n;j++) d[j] = d[j - 1] + llabs(holes[i].a - p[j]);
		deq.clear();
		for(int j = 0;j <= n;j++){
			int temp = j - holes[i].b - 1;
			if(temp >= 0 && dp[i - 1][temp] - d[temp] == deq.front()) deq.pop_front();
			while(!deq.empty() && deq.back() > dp[i - 1][j] - d[j]) deq.pop_back();
			deq.push_back(dp[i- 1][j] - d[j]);
			dp[i][j] = deq.front() + d[j];
		}
	}
	if(dp[m][n] >= INF) printf("-1\n");
	else printf("%I64d\n",dp[m][n]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值