Cross the Wall
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 327680/327680 K (Java/Others)
Total Submission(s): 4479 Accepted Submission(s): 812
Problem Description
“Across the Great Wall, we can reach every corner in the world!” Now the citizens of Rectland want to cross the Great Wall.
The Great Wall is a huge wall with infinite width and height, so the only way to cross is to dig holes in it. All people in Rectland can be considered as rectangles with varying width and height, and they can only dig rectangle holes in the wall. A person can pass through a hole, if and only if the person’s width and height is no more than the hole’s width and height both. To dig a hole with width W and height H, the people should pay W * H dollars. Please note that it is only permitted to dig at most K holes for security consideration, and different holes cannot overlap each other in the Great Wall. Remember when they pass through the wall, they must have their feet landed on the ground.
Given all the persons’ width and height, you are requested to find out the minimum cost for digging holes to make all the persons pass through the wall.
Input
There are several test cases. The first line of each case contains two numbers, N (1 <= N <= 50000) and K (1 <= K <= 100), indicating the number of people and the maximum holes allowed to dig. Then N lines followed, each contains two integers wi and hi (1 <= wi, hi <= 1000000), indicating the width and height of each person.
Output
Output one line for each test case, indicates the minimum cost.
Sample Input
2 1 1 100 100 1 2 2 1 100 100 1
Sample Output
10000 200
Source
Recommend
lcy | We have carefully selected several similar problems for you: 3662 3661 3664 3665 3666
题目描述:
给你n个人,每个人的宽度和高度已知,最多可以修k个门,修门的花费为门的面积,求最少花费。
d[i][j]=min(d[k][j-1]+p[k+1].w*p[i].h);时间复杂度为5*10^12,可以采用斜率优化或者四边形优化。
采取斜率优化:
设k1>k2,k1优于k2,dp[k1][j-1]+p[k1+1].w*p[i].h<=dp[k2][j-1]+p[k2+1].w*p[i].h;
化简得:dp[k1][j-1]-dp[k2][j-1]<p[i].h*(p[k2+1].w-p[k1+1].w)
预处理d[i][0]=inf,d[0][i]=0;
还需注意i和j的枚举顺序,先枚举j,再枚举i,每次枚举j的时候都需要重新初始化i的解集
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define maxn 50100 #define LL long long #define inf 0x3f3f3f3f3f3f3f3f3f3f using namespace std; LL dp[maxn][200]; int que[maxn]; int n,k; int head,tail; struct node { LL w; LL h; }; node p[maxn]; bool cmp(node a,node b) { if(a.w==b.w) return a.h<b.h; else return a.w>b.w; } LL getdp(int i,int j,int k) { //return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]); return dp[k][j-1]+p[k+1].w*p[i].h; } LL getup(int j,int k1,int k2) //yj-yk部分 k1>k2 { //return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]); return dp[k1][j-1]-dp[k2][j-1]; } LL getdown(int j,int k1,int k2) { //return 2*(sum[j]-sum[k]); return p[k2+1].w-p[k1+1].w; } void solve() { head=0; tail=0; que[tail++]=0; dp[0][0]=0; for(int i=1;i<=n;i++) { dp[i][0]=inf; dp[0][i]=0; } for(int j=1;j<=k;j++) { head=tail=0; que[tail++]=0; for(int i=1;i<=n;i++) { //从头开始找当前状态的最优决策,g[que[head+1],que[head]] < sum[i],说明que[head+1]比que[head]更优,删除que[head] while(head+1 < tail && getup(j,que[head+1],que[head]) <= getdown(j,que[head+1],que[head]) * p[i].h ) head++; //注意写成相乘,不然要考虑除数是否为负数 dp[i][j]=getdp(i,j,que[head]); //从尾往前,加入当前状态,如果g[i,que[tail]] < g[que[tail],que[tail-1]] ,可以排除que[tail] while(head+1<tail && getup(j,i,que[tail-1])*getdown(j,que[tail-1],que[tail-2])<=getup(j,que[tail-1],que[tail-2])*getdown(j,i,que[tail-1])) tail--; que[tail++]=i; } } /*for(int j=1;j<=k;j++) { for(int i=1;i<=n;i++) printf("%lld ",dp[i][j]); printf("\n"); }*/ printf("%lld\n",dp[n][k]); } int main() { while(~scanf("%d%d",&n,&k)) { //init(); for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].w,&p[i].h); sort(p+1,p+n+1,cmp); int j=1; for(int i=2;i<=n;i++) { if(p[i].h<p[j].h) continue; else { p[++j]=p[i]; } } n=j; //for(int i=1;i<=n;i++) // printf("%lld %lld\n",p[i].w,p[i].h); solve(); } return 0; }
四边形优化:
目前位置接触到两种形式的方程可以采用四边形优化:
1
a d[i][j]=min(d[i-1][k]+p[k+1].w*p[i].h)
写成这种形式,而不是上面那种,是因为四边形优化的s[i][j]的递推顺序好写
b s[i-1][j]<s[i][j]<s[i][j+1];
观察a和b式,i从小到大,j从大到小.然后初始化第一行和第n+1列,进行递推。
2 (类似于石子合并)
d[i][j]=d[i][k]+d[k+1][j]+w[i][j]
s[i][j-1]<s[i][j]<s[i+1][j]
这时候的递推顺序,是通过先枚举长度,再枚举起点,然后就可以现在要算的状态之前都算过了。
(超时代码,目前只能写到这了)
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define maxn 50100 #define LL long long #define inf 0x3f3f3f3f3f3f3f3f3f3f using namespace std; LL d[maxn][200]; int w[maxn][200]; int n,k; struct node { LL w; LL h; }; node p[maxn]; bool cmp(node a,node b) { if(a.w==b.w) return a.h<b.h; else return a.w>b.w; } void solve() { for(int i=1;i<=n;i++) { d[1][i]=p[1].w*p[i].h; w[1][i]=0; } for(int i=2;i<=k;i++) { w[i][n+1]=n; for(int j=n;j>=i;j--) { d[i][j]=inf; for(int s=w[i-1][j];s<=w[i][j+1];s++) { if(d[i][j]>(d[i-1][s]+p[s+1].w*p[j].h) ) { d[i][j]=(d[i-1][s]+p[s+1].w*p[j].h); w[i][j]=s; } } } } /*for(int i=0;i<=k;i++) { for(int j=0;j<=n+1;j++) printf("%d ",w[i][j]); printf("\n"); } printf("\n"); for(int i=0;i<=k;i++) { for(int j=0;j<=n;j++) printf("%lld ",d[i][j]); printf("\n"); }*/ printf("%lld\n",d[k][n]); } int main() { while(~scanf("%d%d",&n,&k)) { //init(); memset(d,0,sizeof(d)); for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].w,&p[i].h); sort(p+1,p+n+1,cmp); int j=1; for(int i=2;i<=n;i++) { if(p[i].h<p[j].h) continue; else { p[++j]=p[i]; } } n=j; solve(); } return 0; }