CF797F Mice and Holes

Solution

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

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

[dp_{i,j}=minlimits_{j-c_ileq k leq j}{dp_{i-1,k}+sum_{k<rleq 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}+minlimits_{j-c_ileq 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);
}
原文地址:https://www.cnblogs.com/wwlwQWQ/p/14191828.html