CF797F Mice and Holes

Solution

有一个非常显然的性质,一个洞最终容纳的一定是一段横坐标连续的老鼠。就是说不可能出现交叉的请况。数学证明也是非常的容易,懒得写了。

那么就预先将老鼠和洞分别按横坐标排序。这样之后,容易想到 dp 状态 \(dp[i][j]\) 表示用前 \(i\) 个洞容纳前 \(j\) 个老鼠的最小花费。方程也非常好想

\[dp_{i,j}=\min\limits_{j-c_i\leq k \leq j}\{dp_{i-1,k}+\sum_{k<r\leq j} {|P_i-p_r|}\} \]

其中 \(P\) 是洞的坐标,\(p\) 是老鼠的坐标,\(c\) 是洞的容量。特别注意,当前 \(i\) 个洞填满都不够 \(j\) 只老鼠时,就不存在方案,\(dp_{i,j}=\infty\)

\(i\) 确定时,后面的那个和式可以直接用前缀和优化掉,记为 \(S_{i,j}\)。之后发现 \(S_{i,j}\) 是与 \(k\) 无关的变量,所以直接从 \(\min\) 里面提取来。

\[dp_{i,j}=S_{i,j}+\min\limits_{j-c_i\leq k \leq j}\{dp_{i-1,k}-S_{i,k}\} \]

枚举一个 \(i\)\(j\) 后,后面的 \(\min\) 就只与 \(k\) 有关了,发现决策集合是连续的若干个区间,所以直接用单调队列优化掉就可以了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 5007
#define ll long long

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

struct Hole{
    int p,c;
    bool operator <(const Hole X) const{return p<X.p;}
}t[N];

ll S[N],dp[N][N],V[N];
int n,m,Q[N],hd=1,tl=0,ret,a[N];
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+1+n);
    for(int i=1;i<=m;i++)
        t[i].p=read(),t[i].c=read();
    sort(t+1,t+1+m);
    for(int j=1;j<=n;j++) dp[0][j]=(1LL<<60);
    for(int i=1;i<=m;i++){
        ret+=t[i].c;
        for(int j=1;j<=n;j++)
            S[j]=abs(t[i].p-a[j])+S[j-1];
        hd=1,tl=0;
        for(int j=0;j<=n;j++){
            while(hd<=tl&&Q[hd]<j-t[i].c) hd++;
            while(hd<=tl&&dp[i-1][j]-S[j]<=V[tl]) tl--;
            Q[++tl]=j,V[tl]=dp[i-1][j]-S[j];
            if(ret>=j) dp[i][j]=S[j]+V[hd];
            else dp[i][j]=(1LL<<60);
        }
    }
    printf("%lld",(dp[m][n]!=(1LL<<60))? dp[m][n]:-1);
}
posted @ 2020-12-26 11:00  Kreap  阅读(66)  评论(0编辑  收藏  举报