首先(sum c)有些大,考虑将其缩小降低难度
考虑一个贪心:第一次所有老鼠都进入其左边第一个容量未满的洞(如果左边没有就进入右边第一个未满的洞),第二次所有老鼠都进入其右边第一个容量未满的洞(如果右边没有就进入左边第一个未满的洞),我们只保留这(2N)个洞,答案也不会变,因为在最优情况下老鼠最多只会进入这些洞。通过这一步,我们的(sum c)降低到了(2N)级别。
考虑将容量为(c)的洞拆分为(c)个容量为(1)的洞,将老鼠和洞放在一起按照(dis)即位置从小到大排序。然后不难想到一个(DP):设(f_{i,j})表示已经考虑前(i)个物体,其中老鼠的数量减去强制选定需要容纳一只老鼠的洞的数量为(j)时的最小距离,转移分当前为老鼠还是洞。
但是如果直接转移很难找到优化点,考虑简化DP过程。
如果当前是一只老鼠,那么我们的转移是固定的:(j geq 0)则(f_{i,j} - dis_i ightarrow f_{i+1,j+1}),(j<0)则(f_{i,j} + dis_i ightarrow f_{i+1,j+1})
如果当前是一个洞,对于(j>0)的情况,因为之前的老鼠一定要往右边走,而当前的洞是右边最近的一个,所以它一定会容纳一只老鼠,也就是(f_{i,j} + dis_i ightarrow f_{i+1,j-1})
对于(j<0)的情况,之前有若干个洞还没有容纳老鼠,那么右边出现的老鼠一定会往左走,而这一个洞比它左边的洞相比于右边的老鼠更近,所以如果不选这一个洞,可以不选之前的某一个洞而改选这一个洞获得更优策略。这意味着当(j<0)是这一个洞是必须要选的,转移会是(f_{i,j} - dis_i ightarrow f_{i+1,j-1})。
那么唯独需要决策的只有(j=0)且当前是一个洞的情况,你可以选择这一个洞不容纳老鼠,那么会有一个特殊的转移:(f_{i+1,0} = min{f_{i , 0} , f_{i , 1} + dis_i})。
发现除了(f_{i,0})的转移以外其他的转移都是平移数组和统一加上减去一个值的形式。我们通过两个栈维护(DP)数组,一个维护(j>0),一个维护(j<0),在栈外部打上整体加法标记,即可(O(n))维护(DP)。
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 1e5 + 7;
stack < long long > dp1 , dp2;
struct stg{
int a , b;
bool operator <(const stg t)const{
return a < t.a;
}
}now[MAXN];
int mice[MAXN] , hole[MAXN][2] , times[MAXN][2] , dp[MAXN * 3][2];
int N , M , cnt;
long long sum , tag1 , tag2 , dp0;
int main(){
N = read();
M = read();
for(int i = 1 ; i <= N ; ++i)
mice[i] = read();
sort(mice + 1 , mice + N + 1);
for(int i = 1 ; i <= M ; ++i){
now[i].a = read();
now[i].b = read();
sum += now[i].b;
}
sort(now + 1 , now + M + 1);
for(int i = 1 ; i <= M ; ++i){
hole[i][0] = now[i].a;
hole[i][1] = now[i].b;
}
if(sum < N){
puts("-1");
return 0;
}
int p1 = 1 , p2 = 1;
while(p1 <= N || p2 <= M)
if(p1 <= N && (p2 > M || hole[p2][0] > mice[p1])){
++p1;
if(!dp1.empty()){
int t = dp1.top();
dp1.pop();
if(++times[t][0] != hole[t][1])
dp1.push(t);
}
else
++cnt;
}
else
if(cnt < hole[p2][1]){
times[p2][0] = cnt;
cnt = 0;
dp1.push(p2++);
}
else{
times[p2][0] = hole[p2][1];
cnt -= hole[p2++][1];
}
p1 = N;
p2 = M;
cnt = 0;
dp1 = dp2;
while(p1 || p2)
if(p1 && (!p2 || hole[p2][0] < mice[p1])){
--p1;
if(!dp1.empty()){
int t = dp1.top();
dp1.pop();
if(++times[t][1] != hole[t][1])
dp1.push(t);
}
else
++cnt;
}
else
if(cnt < hole[p2][1]){
times[p2][1] = cnt;
cnt = 0;
dp1.push(p2--);
}
else{
times[p2][1] = hole[p2][1];
cnt -= hole[p2--][1];
}
p1 = 1;
p2 = 1;
cnt = 0;
while(p1 <= N || p2 <= M)
if(p1 <= N && (p2 > M || hole[p2][0] > mice[p1]))
dp[++cnt][0] = mice[p1++];
else{
for(int i = min(times[p2][0] + times[p2][1] , hole[p2][1]) ; i ; --i){
dp[++cnt][0] = hole[p2][0];
dp[cnt][1] = 1;
--times[p2][0];
}
++p2;
}
dp1 = dp2;
for(int i = 1 ; i <= cnt ; ++i)
if(!dp[i][1]){
dp1.push(dp0 - tag1);
tag1 -= dp[i][0];
tag2 += dp[i][0];
if(!dp2.empty()){
dp0 = dp2.top() + tag2;
dp2.pop();
}
else
dp0 = 1e15;
}
else{
dp2.push(dp0 - tag2);
tag1 += dp[i][0];
tag2 -= dp[i][0];
if(!dp1.empty()){
dp0 = min(dp0 , dp1.top() + tag1);
dp1.pop();
}
}
cout << dp0;
return 0;
}