题意:
有n个矩形,随意分成几组,使得花费最小,每组的花费是该组最大的宽度×最大的长度。长和宽不能交换位置。
思路:
先贪心地想一下,如果一个长和宽都很大的需要选的话,那么长度和宽度比它都要小的都可以 放到这组里面,不会有任何的额外花费。且分组是随便分,没有相邻之类的限制,那么可以先排序一下,同时可以删去那些不会有额外贡献的矩形。
然后先考虑一下
O
n
2
On^2
On2的做法,很容易可以得出
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
j
]
+
h
[
i
]
∗
w
[
j
+
1
]
)
dp[i]=min(dp[i],dp[j]+h[i]*w[j+1])
dp[i]=min(dp[i],dp[j]+h[i]∗w[j+1]) ,h[i]*w[j+1]是j+i到i这段区间的总花费,我这边是优先长小的,然后宽小的,排序且删除了没有贡献的之后,长递增,宽递减。
On^2代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+5;
pair<ll,ll>p[N],st[N];
ll dp[N];
int top;
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&p[i].first,&p[i].second);
}
sort(p+1,p+1+n);
p[0]={-1,-1};
top=0;
for(int i = 1;i <= n;i++){
while(top && p[i].second >= st[top].second) top--;//只保留会产生贡献的
st[++top] = p[i];
}
memset(dp,125,sizeof dp);
dp[0]=0;
for(int i=1;i<=top;i++){
for(int j=0;j<i;j++){
dp[i]=min(dp[i],dp[j]+st[i].first*st[j+1].second);
}
}
printf("%lld\n",dp[top]);
return 0;
}
对于数据范围大一些就需要用上斜优化了,转移方程还是dp[i]=min(dp[i],dp[j]+h[i]*w[j+1])
如果dp[i]可以从dp[j]和dp[k]两个位置转移,那么就有
d
p
[
i
]
=
d
p
[
j
]
+
h
[
i
]
∗
w
[
j
+
1
]
dp[i]=dp[j]+h[i]*w[j+1]
dp[i]=dp[j]+h[i]∗w[j+1] 和
d
p
[
i
]
=
d
p
[
k
]
+
h
[
i
]
∗
w
[
k
+
1
]
dp[i]=dp[k]+h[i]*w[k+1]
dp[i]=dp[k]+h[i]∗w[k+1]
如果通过dp[k]转移会更优,那么就要有
d
p
[
j
]
+
h
[
i
]
∗
w
[
j
+
1
]
>
d
p
[
k
]
+
h
[
i
]
∗
w
[
k
+
1
]
dp[j]+h[i]*w[j+1]>dp[k]+h[i]*w[k+1]
dp[j]+h[i]∗w[j+1]>dp[k]+h[i]∗w[k+1]
移项后就是
d
p
[
j
]
−
d
p
[
k
]
w
[
k
+
1
]
−
w
[
j
+
1
]
>
h
[
i
]
\frac{dp[j]-dp[k]}{w[k+1]-w[j+1]}>h[i]
w[k+1]−w[j+1]dp[j]−dp[k]>h[i]
然后利用这个关系以及单调队列维护一下
优化代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
pair<ll,ll>p[N],st[N];
ll dp[N];
int top;
int tb[N],fr,bk;
double cal(int j,int k){
return 1.0*(dp[k]-dp[j])/(st[j+1].second-st[k+1].second);
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&p[i].first,&p[i].second);
}
sort(p+1,p+1+n);
top=0;
for(int i = 1;i <= n;i++){
while(top && p[i].second >= st[top].second) top--;
st[++top] = p[i];
}
fill(dp,dp+N,1e18);
dp[0]=0ll;
for(int i=1;i<=top;i++){
for(int j=0;j<i;j++){
dp[i]=min(dp[i],dp[j]+st[i].first*st[j+1].second);
}
}
fr=bk=0;
for(int i=1;i<=top;i++){
while (fr<bk&&cal(tb[fr],tb[fr+1])<=st[i].first)fr++;//弹出队列前面不合法的状态
dp[i]=dp[tb[fr]]+st[tb[fr]+1].second*st[i].first;
while (fr<bk&&cal(tb[bk-1],tb[bk])>=cal(tb[bk-1],i))bk--;//加入当前的点需要维护单调性
tb[++bk]=i;
}
printf("%lld\n",dp[top]);
return 0;
}
//b = dp[i]
//k = l[i]
//x = r[j+1]
//y = dp[j]
//y = -k * x + b