2019牛客暑期多校训练营(第十场)(斜率优化dp)

先推荐一个大佬的博客:https://blog.csdn.net/lxc779760807/article/details/51366552

J题链接:https://ac.nowcoder.com/acm/contest/890/J

题意:给 n 个木材,求制造 k 个木板浪费的木材的最小值,木材可以随意组合,制造木板浪费的木材:将 m 块木材连在一起,将所有的木材砍成一样的高度,砍掉的就是浪费量。

分析:

有分治优化,斜率优化,wqs 二分,本文用的斜率优化dp
先按高度从大到小排序

dp_i_j表示前 i 个木材制成j个木板的最小浪费量

sum[i]表示前 i 个木材总面积

len[i]表示前 i 个木材的宽度

状态转移:dp[i][j]=dp[k][j-1]+ sum[i]-sum[k]-h[i]*(len[i]-len[k])

j-1->j可以滚动数组

设x

dp[k][j-1]+sum[i]-sum[k]-h[i]*(len[i]-len[k])-(dp[x][j-1]+sum[i]-sum[x]-h[i]*(len[i]-len[x]))<=0

化简得:

\frac{dp[k][j-1]-sum[k]-(dp[x][j-1]-sum[x])}{len[k]-len[x]}<=-h[i]

Y[i]=dp[i][j-1]-sum[i]X[i]=len[i]

则:\frac{Y[k]-Y[x]}{X[k]-X[x]}<=-h[i]

说明如果 k为解,x,k,i 应该是上凸的,则解集是下凸的,用一个单调队列维护解集

Ac code:

#include
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=slope(q[r],i))///维护一个上凸的解集
                --r;
            q[++r]=i;
        }
    }
    printf("%lld\n",dp[n][k%2]);
    return 0;
}

方法二:分治优化dp

Ac code:

#include
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

你可能感兴趣的:(动态规划,比赛补题)