题目大意
直线上有$N$头老鼠要走入洞,一共$M$个洞,每个洞最多能容纳$r_i$头老鼠。
现在你要让每个老鼠洞进洞,求所有老鼠进洞的距离之和最小值。
原题$n,mleq 5000$,先加强版$n,mleq 10^6$。
题解
贪心
将所有的洞和坐标横坐标排序,从左往右依次考虑。
若当前为位于$pos$的老鼠,那么它一定会去匹配之前的洞(如果有的话)
用堆维护每个洞的坐标和容量,找到坐标之前最大的洞,计算它的距离加入贡献,同时记录一下它到之前的距离$rg$。
再用一个堆维护每一个$rg+pos$,如果有一个洞出现在$kin [pos,pos+rg)$,那么一定会让位于$pos$的老鼠进入$k$的洞,因为这样会更优。
若当前为位于$k$洞,那么它一定会去找之前的老鼠,对于$k<pos+rg$的所有老鼠,尽量地钦定它们进入这个洞,这样减少的代价恰好为$dt=pos+rg-k$。但是有的时候需要让这些老鼠“反悔”,因为后面的老鼠可能更需要这些洞,所以每以$k<pos+rg$的方式更新一个老鼠,就要多存一个坐标为$k-dt$,容量为$1$的洞,因为后面的老鼠需要进入这个洞的话必须让选择它的老鼠再找回那减少的代价$dt$,所以干脆直接让距离比之前更远$dt$即可。
#include<bits/stdc++.h> #define P pair<LL,int> #define fs first #define sc second #define LL long long #define M 2000010 #define INF 200000100000000ll using namespace std; namespace IO{ const int BS=(1<<20)+5; int Top=0; char Buffer[BS],OT[BS],*OS=OT,*HD,*TL,SS[20]; const char *fin=OT+BS-1; char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} void flush(){fwrite(OT,1,OS-OT,stdout);} void Putchar(char c){*OS++ =c;if(OS==fin)flush(),OS=OT;} void write(int x){ if(!x){Putchar('0');return;} if(x<0) x=-x,Putchar('-'); while(x) SS[++Top]=x%10,x/=10; while(Top) Putchar(SS[Top]+'0'),--Top; } int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } }using namespace IO; int n,m; LL tot,ans; P p[M]; priority_queue<LL> K; priority_queue<P> W; void ins(LL pos){ LL rg=INF; if(!W.empty()){ P now=W.top(); W.pop(); rg=pos-now.fs,now.sc--; if(now.sc) W.push(now); }K.push(pos+rg),ans+=rg; } void pvd(P now){ while(now.sc&&!K.empty()&&K.top()>now.fs){ LL dis=K.top()-now.fs; ans-=dis; now.sc--,K.pop(),W.push(P(now.fs-dis,1)); }if(now.sc) W.push(now); } int main(){ n=read(),m=read(); for(int i=1;i<=n;i++) p[i].fs=read(),p[i].sc=-1; for(int i=n+1;i<=n+m;i++) p[i].fs=read(),tot+=(p[i].sc=read()); if(tot<n){puts("-1");return 0;}sort(p+1,p+n+m+1); for(int i=1;i<=n+m;i++) p[i].sc==-1?ins(p[i].fs):pvd(p[i]); printf("%lld ",ans); return 0; }