Codeforces 797 F Mice and Holes
One day Masha came home and noticed n mice in the corridor of her flat. Of course, she shouted loudly, so scared mice started to run to the holes in the corridor.
The corridor can be represeted as a numeric axis with n mice and m holes on it. ith mouse is at the coordinate xi, and jth hole — at coordinate pj. jth hole has enough room for cj mice, so not more than cj mice can enter this hole.
What is the minimum sum of distances that mice have to go through so that they all can hide in the holes? If ith mouse goes to the hole j, then its distance is |xi - pj|.
Print the minimum sum of distances.
The first line contains two integer numbers n, m (1 ≤ n, m ≤ 5000) — the number of mice and the number of holes, respectively.
The second line contains n integers x1, x2, ..., xn ( - 109 ≤ xi ≤ 109), where xi is the coordinate of ith mouse.
Next m lines contain pairs of integer numbers pj, cj ( - 109 ≤ pj ≤ 109, 1 ≤ cj ≤ 5000), where pj is the coordinate of jth hole, and cj is the maximum number of mice that can hide in the hole j.
Print one integer number — the minimum sum of distances. If there is no solution, print -1 instead.
4 5
6 2 8 9
3 6
2 1
3 6
4 7
4 7
11
7 2
10 20 30 40 50 45 35
-1000000000 10
1000000000 1
7000000130
题意:n个老鼠m个洞在数轴上,给出老鼠坐标,再给出每个洞的坐标和最多容纳的老鼠数量,问所有的老鼠到洞里所需要的最短距离,不能则输出-1
分析题目可得,最优方案一定是 某一个洞里容纳了坐标临近的老鼠
即将洞按坐标排序,老鼠按坐标排序,第i个洞容纳的是第k——s只老鼠,第i+1个洞容纳的是第s+1——t只老鼠
令f[j][i]表示前j个洞,容纳了前i只老鼠的最短距离
s[j][i]表示第1——i只老鼠全到第j个洞的距离和,即前缀和
那么f[j][i]可以由f[j-1][i-1],f[j-1][i-2]……f[j-1][i-容量]转移过来
动态转移方程:f[j][i]=min(f[j-1][k]+s[j][i]-s[j][k])
事件复杂度:n²
可以用单调队列优化
对于每一个j和i,s[j][i]是定值,f[j][i]=min(f[j-1][k]-s[j][k])+s[j][i]
对于每一个j,维护一个f[j-1][k]-s[j][k]的单调递增队列
f[j][i]就等于 队首+s[j][i]
然后滚动数组滚起来~~
(虽然不用滚动数组也能A)
不用滚动数组版:
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; int n,m; int mouse[5001],que[5005]; ll sum,f[5001][5001],s[5001]; struct HOLE { int pos,capa; }hole[5001]; bool cmp(HOLE p,HOLE k) { return p.pos<k.pos; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&mouse[i]); for(int i=1;i<=m;i++) scanf("%d%d",&hole[i].pos,&hole[i].capa),sum+=hole[i].capa; if(n>sum) { printf("-1"); return 0; } sort(mouse+1,mouse+n+1); sort(hole+1,hole+m+1,cmp); memset(f,127,sizeof(f)); f[0][0]=0; for(int j=1;j<=m;j++) { int head=0,tail=0; que[0]=0; for(int i=1;i<=n;i++) s[i]=s[i-1]+abs(mouse[i]-hole[j].pos); for(int i=0;i<=n;i++) { while(head<tail&&que[head]<i-hole[j].capa) head++; while(head<tail&&f[j-1][i]-s[i]<=f[j-1][que[tail-1]]-s[que[tail-1]]) tail--; que[tail++]=i; f[j][i]=f[j-1][que[head]]+s[i]-s[que[head]]; } } printf("%lld",f[m][n]); }
滚动数组:
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; int n,m; int mouse[5001],que[5005]; ll sum,f[2][5001],s[5001]; struct HOLE { int pos,capa; }hole[5001]; bool cmp(HOLE p,HOLE k) { return p.pos<k.pos; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&mouse[i]); for(int i=1;i<=m;i++) scanf("%d%d",&hole[i].pos,&hole[i].capa),sum+=hole[i].capa; if(n>sum) { printf("-1"); return 0; } sort(mouse+1,mouse+n+1); sort(hole+1,hole+m+1,cmp); memset(f,127,sizeof(f)); f[0][0]=0; int j,now=1,pre=0; for(j=1;j<=m;j++,swap(now,pre)) { int head=0,tail=0; que[0]=0; for(int i=1;i<=n;i++) s[i]=s[i-1]+abs(mouse[i]-hole[j].pos); for(int i=0;i<=n;i++) { while(head<tail&&que[head]<i-hole[j].capa) head++; while(head<tail&&f[pre][i]-s[i]<=f[pre][que[tail-1]]-s[que[tail-1]]) tail--; que[tail++]=i; f[now][i]=f[pre][que[head]]+s[i]-s[que[head]]; } } printf("%lld",f[pre][n]); }
while(head<tail&&que[head]<i-hole[j].capa) 不能加等号,因为这一次的范围是以i-hole[j].capa开始
while(head<tail&&f[pre][i]-s[i]<=f[pre][que[tail-1]]-s[que[tail-1]]) 要加等号,可以更新队列中的老鼠的标号,使其更大,更有利于后面的转移
刚开始想的是三维dp,f[j][i][k] 其中k表示最后一个洞容纳了几只老鼠,这一维是冗余的,且难以设计方程
这一维起的作用是转移状态的上界,完全可以在枚举i时只枚举合法的