网址: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]
优化的对象就是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;
}