题意
有 N个人,挖K个洞,使得每个人能穿过去。每个人的面积为w*h。挖洞的花费为w*h。问最少花费
题解
首先需要对人进行排序,W从大到小进行排序。排序完成以后,从1到N进行扫描,移除无用元素(即H小于前面所有元素最大H的元素)。这样处理完成以后,整个序列W是单调递减的,H是单调递增的。于是列出状态转移方程,dp[i][j]=dp[i-1][a]+w[a+1]*h[j]。(其中A代表挖洞最后一个)
列出状态转移方程后可以发现,状态转移方程是二维的,因为数据量有5万,所以需要用斜率优化,将二维的状态转移方程降为一维。
其实列出来状态转移方程以后,直接用斜率DP的套路求解就可以了。化简一个W(a,b)的不等式就可以了(b为较优的方案)。不等式的化简结果为(dp[i-1][b]-dp[i-1][a])/(w[a+1]-w[b+1])<=h[i]。凡是满足这个不等式的a,b都存在b优于a,并且根据这个不等式就可以进行斜率优化,使得队列的斜率永远保持单增状态。完成上述两步操作以后,便可以将一个三维的循环转化为一个二维的循环,从而满足时间复杂度要求。
注意事项
需要注意DP的顺序,DP顺序是由状态转移方程决定的。因为dp[i][j]=dp[i-1][a]+w[a+1]*h[j],所以注定外层是i内层是j。比较坑的是内外层顺序错了也有可能过样例,并且在调试上较难调试。
另外的话,还需要注意并不一定要花费所有的挖洞机会,因此需要注意不能开滚动数组,必须要开一个二维数组,并且在程序末尾对最小值进行选择。
代码
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 100010
#define MOD 1000000009
#define EPS 1e-10
#define int LL
using namespace std;
struct Node{
int w,h;
bool operator < (const Node b) const{
if(w==b.w){
return h>b.h;
}
return w>b.w;
}
};
Node nodes[50010];
Node temp[50010];
int q[50010];
int dp[110][50010];
int getUP(int p,int i,int j){
return dp[p][j]-dp[p][i];
}
int getDown(int i,int j){
return nodes[i+1].w-nodes[j+1].w;
}
main(){
int n,k;
W(~scanf("%lld%lld",&n,&k)){
MEM(dp,0);
UP(i,1,n+1){
scanf("%lld%lld",&nodes[i].w,&nodes[i].h);
}
sort(nodes+1,nodes+n+1);
int pos=1;
Node mx=nodes[1];
temp[pos++]=nodes[1];
UP(i,2,n+1){
if(nodes[i].h>mx.h){
mx=nodes[i];
temp[pos++]=nodes[i];
}
}
k=min(k,pos-1);
memcpy(nodes,temp,sizeof(temp));
UP(j,1,pos){
dp[1][j]=nodes[1].w*nodes[j].h;
}
UP(i,2,k+1){
int st=0,ed=0;
q[ed++]=0;
UP(j,1,pos){
W(st+1<ed&&getUP(i-1,q[st],q[st+1])<=nodes[j].h*getDown(q[st],q[st+1])){
st++;
}
dp[i][j]=dp[i-1][q[st]]+nodes[q[st]+1].w*nodes[j].h;
W(st+1<ed&&getUP(i-1,q[ed-1],j)*getDown(q[ed-2],q[ed-1])<=getUP(i-1,q[ed-2],q[ed-1])*getDown(q[ed-1],j)){
ed--;
}
q[ed++]=j;
}
}
int mn=dp[1][pos-1];
UP(i,2,k+1){
mn=min(dp[i][pos-1],mn);
}
printf("%lld\n",mn);
}
}