先推荐一个大佬的博客:https://blog.csdn.net/lxc779760807/article/details/51366552
J题链接:https://ac.nowcoder.com/acm/contest/890/J
题意:给 n 个木材,求制造 k 个木板浪费的木材的最小值,木材可以随意组合,制造木板浪费的木材:将 m 块木材连在一起,将所有的木材砍成一样的高度,砍掉的就是浪费量。
分析:
有分治优化,斜率优化,wqs 二分,本文用的斜率优化dp
先按高度从大到小排序
表示前 i 个木材制成j个木板的最小浪费量
sum[i]表示前 i 个木材总面积
len[i]表示前 i 个木材的宽度
状态转移:
j-1->j可以滚动数组
设x<k<i且k比x更优,则有
化简得:
设,
则:
说明如果 k为解,x,k,i 应该是上凸的,则解集是下凸的,用一个单调队列维护解集
Ac code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
typedef long long ll;
ll sum[maxn],len[maxn];
ll dp[maxn][2];
int q[maxn];
struct Node
{
ll w,h;
bool operator<(const Node& a)const
{
if(h==a.h) return w>a.w;
return h>a.h;
}
} a[maxn];
bool sta;
ll Y(int i)
{
return dp[i][!sta]-sum[i];
}
ll X(int i)
{
return len[i];
}
double slope(int i,int j)
{
return 1.0*(Y(j)-Y(i))/(X(j)-X(i));
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1; i<=n; i++)
scanf("%lld%lld",&a[i].w,&a[i].h);
sort(a+1,a+n+1);
for(int i=1; i<=n; i++)
{
len[i]=len[i-1]+a[i].w;
sum[i]=sum[i-1]+a[i].w*a[i].h;
dp[i][1]=sum[i]-a[i].h*len[i];
}
sta=1;
for(int j=2; j<=k; j++)
{
sta=!sta;
int l=1,r=1;
q[1]=0;///模拟单调队列
for(int i=1; i<=n; i++)
{
while(l<r&&slope(q[l],q[l+1])<=-a[i].h)///找最大的满足条件的下标
++l;
dp[i][sta]=dp[q[l]][!sta]+sum[i]-sum[q[l]]-a[i].h*(len[i]-len[q[l]]);///根据找到的最优点转移
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i))///维护一个上凸的解集
--r;
q[++r]=i;
}
}
printf("%lld\n",dp[n][k%2]);
return 0;
}
方法二:分治优化dp
Ac code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
typedef long long ll;
ll sum[maxn],len[maxn];
ll dp[maxn][2];
int q[maxn];
const ll INF=1e18;
struct Node
{
ll w,h;
bool operator<(const Node& a)const
{
if(h==a.h) return w>a.w;
return h>a.h;
}
} a[maxn];
bool sta;
void solve(int l,int r,int pl,int pr)
{
if(l>r) return;
int mid=(l+r)>>1;
int pm=pl;
for(int i=pl; i<=pr; i++){
ll tmp=dp[i][!sta]+sum[mid]-sum[i]-a[mid].h*(len[mid]-len[i]);///寻找最优的可更新dp[mid]的点
if(dp[mid][sta]>tmp)///更新dp[mid]
{
dp[mid][sta]=tmp;
pm=i;///记录更新的dp[mid]的最优点
}
}
solve(l,mid-1,pl,pm);///mid点已被更新,故只用更新[l,mid-1],[mid+1,r]
solve(mid+1,r,pm,pr);
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1; i<=n; i++)
scanf("%lld%lld",&a[i].w,&a[i].h);
sort(a+1,a+n+1);
for(int i=1; i<=n; i++)
{
len[i]=len[i-1]+a[i].w;
sum[i]=sum[i-1]+a[i].w*a[i].h;
}
sta=0;
for(int i=1;i<=n;i++) dp[i][0]=INF;
for(int j=1; j<=k; j++)
{
sta=!sta;
for(int i=1;i<=n;i++) dp[i][sta]=INF;
solve(0,n,0,n);
}
printf("%lld\n",dp[n][sta]);
return 0;
}
注意以上使用的优化都必须满足决策单调性,也就是例如:
ans[i] = max(a[j] + sqrt(i-j)) (j < i)
如果在一个i满足a[j] + sqrt(i-j) < a[k] + sqrt(i - k)且j<k,那么可以发现在i变大的时候j也一定会比k劣,没有优于k的可能。