XJ_Day_29_dp优化_K - Cross the Wall

先附上上午码的,思路写在里面,我会在后面重新详细说明

题目

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAX=5e4+3;
struct milk
{
    LL h,w;
}a[MAX];
int st[MAX];
inline bool myru(milk x,milk y){if(x.h==y.h)return x.w>y.w;return x.h>y.h;}
LL rin()
{
    LL s=0;
    char c=getchar();
    bool bj=0;
    for(;(c>'9'||c<'0')&&c!='-';c=getchar());
    if(c=='-')c=getchar(),bj=true;
    for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
    if(bj)return -s;
    return s;
}
int main()
{
    //将人分成至多k组,每组的费用为max(hi)*max(wi),求最小的费用和
    //首先贪心:
    //1.对于任意1<=i,j<=n,如果满足h[i]>=h[j]&&w[i]>=w[j],则j是不需要考虑的
    //2.那么剩下的人中,均满足,if(h[i]>h[j])w[i]<w[j];(上面的删的条件保证不会出现相等的情况)
    //3.所以需要考虑的人中,最高的一定宽度最小,最宽的一定高度最小
    //4.按高度从大到小排序,满足宽度递增
    //5.显而易见,如果存在i<j<k(序号),k和j塞在同一组明显优于k和i塞在同一组,所以就按照这个排序之后的顺序,每组都是一段连续的区间
    //6.那么对于每个i,要么自己新开一组,要么接在前一组
    //7.问题在于怎么找到前一组的开头(即知道这组的洞的高度来更新答案),如果退化成O(N^2)是肯定不行的

    //奇怪的思路1:
    //枚举k个断点……,时间O(N^K),而且这题也并不是必须要k个,而是<=k,例子很好举,比方说,k=3;h1=100,w1=3;h2=99,w2=4;h3=98,w3=5;全放一组明显最优

    //8.基本上f[N][k]就是极限
    //先写一波假的复杂度的推导式吧
    //f[i][j]表示当前到i,分了j块,i为第j个块的终点 的最小花费
    //ans=max(f[N][j]),1<=j<=k;
    //f[i][j]=min(f[i][j],f[x][j-1]+w[i]*h[x+1]),x<i;时间复杂度O(k*(N^2))
    //分成若干组w[i]*h[x+1]!!!
    
    //奇怪的思路2:
    //if(h[i]*w[N]+h[last]*w[i-1]<h[last]*w[N])last=i;
    //实现了一下,WA了,因为这个zz贪心会导致后面的分块数不够
    // 10 4
    // 1 100
    // 2 99
    // 3 98
    // 4 97
    // 5 96
    // 6 95
    // 7 94
    // 8 93
    // 9 92
    // 10000 1
    //对于这组数据,最后一个人肯定是单独作为一块,但是由于前面错误的贪心导致最后输出:970592

    //奇怪的思路3:
    //设st[i]表示第i个块的开始位置
    //初始,st[1]=1;st[i]=N+1,i>1;
    //若当前st[i]往前移能使答案更优,则移动
    //初步估计时间复杂度为O(N*K)
    //h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]-1] 这个是当前这两段区间的和
    //h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1] 这个是st[i]向前移动一格后两端区间的和
    //if(h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1]<h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]]-1)st[i]--;
    //感觉是跑N次,每次看每个起始点能不能往前移
    //写出来之后仍然是被那个数据Hack了
    //因为这样附初值会导致st[2]跑到N就跑不上去了,后面的自然也上不去
    
    //奇怪的思路4:
    //续接奇怪的思路3,考虑外面枚举一层区间数,赋初值就根据这个赋
    //WA了 ,艹
    //反例:
    // 10 4
    // 1 100
    // 2 99
    // 3 98
    // 4 97
    // 5 96
    // 9995 6
    // 9996 5
    // 9997 4
    // 9998 3
    // 10000 1
    // Out put:1000000
    //很好推翻的想法,至少我赋初值的方式不对,从后往前赋,加上正确但不一定完整的判定,WA掉很正常

    //还是想想怎么优化DP式吧,别整花里胡哨的东西了
    //9.h递减,w递增,这个应该是关键
    int i,j;
    int n,k;
    for(;scanf("%d%d",&n,&k)>0;)
    {
        for(i=1;i<=n;i++)a[i].w=rin(),a[i].h=rin();
        sort(a+1,a+n+1,myru);
        int nam=0;
        int max_w=0;
        for(i=1;i<=n;i=j+1)
        {
            for(j=i;j<n&&a[j+1].h==a[i].h;j++);
            if(a[i].w>max_w)max_w=a[i].w,a[++nam]=a[i];
        }
        n=nam;
        // for(i=1;i<=nam;i++)printf("(%d,%d) ",a[i].h,a[i].w);
        //奇怪的思路2代码
        // int last=1,s=1;
        // LL ans=0;
        // for(i=2;i<=n;i++)
        // {
        //     if(s==k)break;
        //     // printf("last:%d,ans:%lld -->",last,ans);
        //     if(a[i].h*a[n].w+a[last].h*a[i-1].w<a[last].h*a[n].w)ans+=a[last].h*a[i-1].w,last=i,s++;
        //     // printf("last:%d,ans:%lld\n",last,ans);
        // }
        // ans+=a[last].h*a[n].w;
        // printf("%lld\n",ans);

        //奇怪的思路3:
        // st[1]=1;
        // for(i=2;i<=k+1;i++)st[i]=n+1;
        // for(j=1;j<=n;j++)
        // for(i=2;i<=k;i++)
        // {
        //     if(st[i-1]==st[i])break;
        //     if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--;
        // }
        // for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n");
        // LL ans=0;
        // for(i=1;i<=k;i++)ans+=a[st[i]].h*a[st[i+1]-1].w;
        // printf("%lld\n",ans);
        //奇怪的思路4:
        // st[1]=1;
        // LL ans=a[1].h*a[n].w;
        // for(int now=2;now<=k;now++)
        // {
        //     for(i=1;i<now;i++)st[i+1]=n-now+i+1;
        //     st[now+1]=n+1;
        //     for(j=1;j<=n;j++)
        //     for(i=2;i<=k;i++)
        //     {
        //     if(st[i]==st[i-1]+1)continue;
        //     if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--;
        //     }
        //     // for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n");
        //     LL sum=0;
        //     for(i=1;i<=now;i++)sum+=a[st[i]].h*a[st[i+1]-1].w;
        //     ans=min(ans,sum);
        // }
        // printf("%lld\n",ans);
    }
    return 0;
}

设第\(i\)个人的高度为\(h(i)\),宽度为\(w(i)\)

1.对于任意\(1\le i,j\le n,\)如果满足\(h(i)\ge h(j)\&\&w(i)\ge w(j)\),则\(j\)是不需要考虑的人

这个应该是很好理解的,更高更宽的人都能过,就不需要考虑更矮更窄的人


2.那么剩下的人中,均满足,\(if(h(i)>h(j))w(i)<w(j);\)(上面的删的条件保证不会出现相等的情况)

宽度(或高度)相等的若干个人,高度最高的会把其他的人都判成没用的


3.所以需要考虑的人中,最高的一定宽度最小,最宽的一定高度最小

4.按高度从大到小排序,满足宽度递增

这里就不用解释了吧,很好懂的,就是由2推导过来的结论


5.显而易见,如果存在\(i<j<k\)(序号),\(k\)\(j\)塞在同一组明显优于\(k\)\(i\)塞在同一组,所以就按照这个排序之后的顺序,每组都是一段连续的区间

这句话当时写的比较模糊,这里重新解释一下:

在前面排序以后的情况下,是这样的图形

对于当前人\(i\),考虑让他和前面的某个人\(j\)分成一组,打一个两个人都能通过的洞

高度为\(h(j)\),宽度为\(w(i)\)

那么这个操作之后\(i\)\(j\)之间的所有点都可以直接通过这个洞(这个结论很显然,不想解释,前面的排序就是为了能够得到这个结论)

所以可以知道,每一组的人员都是一段连续的区间上的人,设左端点为\(l\),右端点为\(r\),这组的花费就是\(h(l)\ast w(r)\)


6.那么对于每个\(i\),要么自己新开一组,要么接在前一组

其实到这里状态转移方程就很明显了:

\(f(i)(j)\)表示(当前到第\(i\)个人,已经分了\(j\)块,\(i\)为第\(j\)个块的终点)的最小花费

\(ans=max(f(n)(j)),1\le j\le k\)

\(f(i)(j)=min(f(i)(j),f(x)(j-1)+w(i)\ast h(x+1)),x<i\)

时间复杂度\(O(k\ast N^2)\)

这个方程需要优化,一看就知道是用斜率优化来做

设存在\(x_1<x_2\)

若有\(x_2\)决策更优

则:

这里的所有\(f()(j)\)都只跟\(f()(j-1)\)有关,可以消去一维

\(f(x_1)+h(x_1+1)\ast w(i)\ge f(x_2)+h(x_2+1)\ast w(i)\)

\(w(i)\ast (h(x_1+1)-h(x_2+1))\ge f(x_2)-f(x_1)\)

\(-w(i)\le \frac{f(x_2)-f(x_1)}{h(x_2+1)-h(x_1+1)}\)

设点\(P_x\)\((h(x+1),f(x))\)

\(f(i)(j)=f(x)(j-1)+w(i)\ast h(x+1)\)

\(->b=y+(-k)\ast x\)

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAX=5e4+3;
const int MAK=1e2+3;
struct milk
{
    LL h,w;
}a[MAX];
// int st[MAX];
int d[MAX];
LL f[2][MAX];
int head,tail;
bool now;
inline void push_d(int x)
{
    //要维护斜率递减
    for(;head<tail;)
    {
    if((f[now][x]-f[now][d[tail]])*(a[d[tail]+1].h-a[d[tail-1]+1].h)>=(f[now][d[tail]]-f[now][d[tail-1]])*(a[x+1].h-a[d[tail]+1].h))
    tail--;
    else break;
    }
    d[++tail]=x;
}
inline bool myru(milk x,milk y){if(x.h==y.h)return x.w>y.w;return x.h>y.h;}
LL rin()
{
    LL s=0;
    char c=getchar();
    bool bj=0;
    for(;(c>'9'||c<'0')&&c!='-';c=getchar());
    if(c=='-')c=getchar(),bj=true;
    for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
    if(bj)return -s;
    return s;
}
int main()
{
    int i,j;
    int n,k;
    for(;scanf("%d%d",&n,&k)>0;)
    {
        for(i=1;i<=n;i++)a[i].w=rin(),a[i].h=rin();
        sort(a+1,a+n+1,myru);
        int nam=0;
        int max_w=0;
        for(i=1;i<=n;i=j+1)
        {
            for(j=i;j<n&&a[j+1].h==a[i].h;j++);
            if(a[i].w>max_w)max_w=a[i].w,a[++nam]=a[i];
        }
        n=nam;
        LL ans;
        for(i=1;i<=n;i++)f[1][i]=a[1].h*a[i].w;
        ans=a[1].h*a[n].w;
        now=false;
        for(j=2;j<=k;j++)
        {
            // printf("\nk:%d\n\n",j);
            now=!now;
            head=1,tail=0;
            for(i=1;i<j;i++)f[!now][i]=0x3f3f3f3f3f3f3f3f,push_d(i);
            for(;i<=n;i++)
            {
                LL k_i=-a[i].w;
                // for(int p=head;p<=tail;p++)printf("%d ",d[p]);printf(" -->\n");
                for(;head<tail;)
                {
                    // printf("%d: %lld?%lld\n",d[head],k_i*(a[d[head+1]+1].h-a[d[head]+1].h),f[now][d[head+1]]-f[now][d[head]]);
                    if(k_i*(a[d[head+1]+1].h-a[d[head]+1].h)<f[now][d[head+1]]-f[now][d[head]])break;
                    else head++;
                }
                // for(int p=head;p<=tail;p++)printf("%d ",d[p]);printf("\n");
                f[!now][i]=f[now][d[head]]+a[i].w*a[d[head]+1].h;
                push_d(i);
                if(i==n)ans=min(ans,f[!now][i]);
                // printf("f[%d]:%lld\n",i,f[!now][i]);
            }
        }
        printf("%lld\n",ans);
    return 0;
}
posted @ 2020-07-31 18:09  zjjws  阅读(133)  评论(0)    收藏  举报