kuangbin专题二十斜率dp总结

这个专题让我学会了两种dp优化的方式,只要证明出决策单调的话,那么就可以进行斜率优化或者四边形优化。但前提是得能把dp的式子写出来。。然而我在做的时候连式子都写不出来。。
B - Lawrence
B题做的时候连式子都没推出来,现在看看真的是蠢。。
设dp[i][j] 为前i个炸j次的最小值,那么易得dp[i][j]=min(dp[k][j-1]+cost[k+1][i])。cost[i][j] 代表i到j能得到的价值,这个可以用区间和和区间平方和预处理出来,具体读者可以思考一下。
这样把式子化成斜率dp那样,就可以优化了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const ll inf=1e18;
ll dp[maxn][maxn];
int sum[maxn];
int sq[maxn];
int Q[maxn];
ll prod[maxn][maxn];

double K(int a,int b,int j)
{
    double Y=2*dp[a][j-1]+sum[a]*sum[a]+sq[a]-2*dp[b][j-1]-sum[b]*sum[b]-sq[b];
    double X=2*(sum[a]-sum[b]);
    if(X==0)return inf;
    return Y/X;
}

ll Cal(int idx,int i,int j)
{
    return dp[idx][j-1]+prod[idx+1][i];
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m)&&n)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sum[i]);
            //sum[i]=100;
            sq[i]=sum[i]*sum[i];
            sum[i]+=sum[i-1];
            sq[i]+=sq[i-1];
        }

        for(int k=0;k<=n;k++)
            for(int i=k+1;i<=n;i++)
        {
            prod[k+1][i]=(1LL*(sum[i]-sum[k])*(sum[i]-sum[k])-(sq[i]-sq[k]))/2;
            //cout<<prod[k+1][i]<<endl;
        }

        int first,tail;
        for(int j=0;j<=m;j++)
        {
            first=tail=0;
            for(int i=j;i<=n;i++)
            {
                if(j==0)
                    dp[i][j]=prod[1][i];
                else
                {
                    while(first<tail&&K(Q[first+1],Q[first],j)<=(double)sum[i])first++;
                    //cout<<first<<endl;
                    dp[i][j]=Cal(Q[first],i,j);

                    while(first<tail&&K(i,Q[tail],j)<=K(Q[tail],Q[tail-1],j))tail--;
                    Q[++tail]=i;
                }
                //cout<<i<<" "<<j<<" "<<first<<" "<<tail<<" "<<dp[i][j]<<endl;
            }
        }
        cout<<dp[n][m]<<endl;
    }
    return 0;
}

C - 小明系列故事――捉迷藏
不知道为啥这个专题会有这个。。一眼看上去就是个bfs。。
预处理能看见大明和二明的位置,然后再bfs就完事了。
唯一需要注意的地方是,判断一个地方是否走过不能简单的用vis[x][y] 判断,因为没规定一个地方只能走一次,我们得再加个状态代表小明当前是否找到大明和二明的状态。

#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
char Map[maxn][maxn];
int Dx,Dy,Ex,Ey,Sx,Sy;
int see_D_dx,see_D_ux,see_D_ly,see_D_ry;
int see_E_dx,see_E_ux,see_E_ly,see_E_ry;
int n,m,t;
void pre_init()
{
    int tmpx=Dx,tmpy=Dy;
    tmpy++;
    while(tmpy<=m&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy++;
    tmpy--;
    see_D_ry=tmpy;
    tmpy=Dy;
    tmpy--;
    while(tmpy>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy--;
    tmpy++;
    see_D_ly=tmpy;
    tmpy=Dy;
    tmpx++;
    while(tmpx<=n&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx++;
    tmpx--;
    see_D_dx=tmpx;
    tmpx=Dx;
    tmpx--;
    while(tmpx>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx--;
    tmpx++;
    see_D_ux=tmpx;
    tmpx=Ex,tmpy=Ey;
    tmpy++;
    while(tmpy<=m&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy++;
    tmpy--;
    see_E_ry=tmpy;
    tmpy=Ey;
    tmpy--;
    while(tmpy>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpy--;
    tmpy++;
    see_E_ly=tmpy;
    tmpy=Ey;
    tmpx++;
    while(tmpx<=n&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx++;
    tmpx--;
    see_E_dx=tmpx;
    tmpx=Ex;
    tmpx--;
    while(tmpx>=1&&(Map[tmpx][tmpy]=='.'||Map[tmpx][tmpy]=='S'))tmpx--;
    tmpx++;
    see_E_ux=tmpx;
    //cout<<see_D_dx<<' '<<see_D_ux<<' '<<see_D_ly<<' '<<see_D_ry<<endl;
    //cout<<see_E_dx<<' '<<see_E_ux<<' '<<see_E_ly<<' '<<see_E_ry<<endl;
}
int vis[maxn][maxn][5];
struct HeapNode
{
    int x,y;
    int d;
    int state;
    HeapNode(int _x,int _y,int _d,int _s):x(_x),y(_y),d(_d),state(_s){}
    HeapNode(){}
    bool operator<(const HeapNode &b)const
    {
        return d>b.d;
    }
};
int Sta(int x,int y)
{
    int res=0;
    if(x==Dx&&y<=see_D_ry&&y>=see_D_ly)
        res|=1;
    else if(y==Dy&&x>=see_D_ux&&x<=see_D_dx)
        res|=1;
    if(x==Ex&&y<=see_E_ry&&y>=see_E_ly)
        res|=2;
    else if(y==Ey&&x>=see_E_ux&&x<=see_E_dx)
        res|=2;
    return res;
}
int dis[4][2]={-1,0,1,0,0,-1,0,1};
int solve()
{
    //cout<<Sx<<' '<<Sy<<endl;
    memset(vis,0,sizeof(vis));
    queue<HeapNode>Q;
    int itstate=Sta(Sx,Sy);
    //cout<<itstate<<endl;
    Q.push(HeapNode(Sx,Sy,0,itstate));
    while(!Q.empty())
    {
        HeapNode u=Q.front();Q.pop();
        if(vis[u.x][u.y][u.state])continue;
        vis[u.x][u.y][u.state]=1;
        if(u.d>t)return -1;
        if(u.state==3)
        {
            //puts("yes");
            return u.d;
        }
        for(int i=0;i<4;i++)
        {
            int tmpx=u.x+dis[i][0];
            int tmpy=u.y+dis[i][1];
            if(tmpx<1||tmpx>n||tmpy<1||tmpy>m)continue;
            if(Map[tmpx][tmpy]=='.')
            {
                int tmp=(u.state|Sta(tmpx,tmpy));
                //cout<<tmpx<<' '<<tmpy<<' '<<tmp<<endl;
                Q.push(HeapNode(tmpx,tmpy,u.d+1,tmp));
            }
        }
    }
    return -1;
}
int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        scanf("%d %d %d",&n,&m,&t);
        for(int i=1;i<=n;i++)
            scanf("%s",Map[i]+1);
        printf("Case %d:\n",cas++);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
        {
            if(Map[i][j]=='D')
                Dx=i,Dy=j;
            else if(Map[i][j]=='E')
                Ex=i,Ey=j;
            else if(Map[i][j]=='S')
                Sx=i,Sy=j;
        }
        pre_init();
        printf("%d\n",solve());
    }
    return 0;
}

F - Cross the Wall
好题。
我们可以仔细思考思考,对于一个方块来说,如果存在另一个方块,长和宽都大于它,那么这个方块就没必要存在了。基于这个理论,我们可以先预处理出那些必须存在的方块,然后将其以宽增大,高减少的方式排列。再进行dp就完事了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
const int maxh=1000005;
const int inf=0x3f3f3f3f;
int vis[maxh];
vector<int>V;
int stck[maxh],q[maxn];
struct Node
{
    int w,h;
    Node(int _w,int _h):w(_w),h(_h){}
    Node(){}
    bool operator<(const Node&b)const
    {
        return h>b.h;
    }
}nodes[maxn];
long long dp[maxn][105];
double K(int a,int b,int i,int j)
{
    double Y=dp[a][j-1]-dp[b][j-1];
    double X=nodes[b+1].h-nodes[a+1].h;
    return Y/X;
}

long long getup(int a,int b,int i,int j)
{
    return dp[a][j-1]-dp[b][j-1];
}
long long getdown(int a,int b,int i,int j)
{
    return nodes[b+1].h-nodes[a+1].h;
}
long long Cal(int a,int i,int j)
{
    return dp[a][j-1]+1LL*nodes[a+1].h*nodes[i].w;
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    //cout<<(1LL<<60)<<endl;
    int n,k;
    while(~scanf("%d %d",&n,&k))
    {
        V.clear();
        memset(vis,0,sizeof(vis));
        int w,h;
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d",&w,&h);
            vis[w]=max(vis[w],h);
        }
        for(int i=1;i<maxh;i++)if(vis[i])
            V.push_back(i);
        int top=0;
        for(int i=0;i<V.size();i++)
        {
            int tmp=V[i];
            while(top>0&&vis[V[stck[top]]]<=vis[tmp])top--;
            stck[++top]=i;
        }
        int now=0;
        for(int i=1;i<=top;i++)
            nodes[++now]=Node(V[stck[i]],vis[V[stck[i]]]);
        int first=0,tail=0;
        long long ans=1e15;
        for(int j=1;j<=k;j++)
        {
            first=tail=0;
            for(int i=1;i<=now;i++)
            {
                if(j==1)
                {
                    dp[i][j]=1LL*nodes[i].w*nodes[1].h;
                    continue;
                }
                while(first<tail&&getup(q[first+1],q[first],i,j)<=1LL*nodes[i].w*getdown(q[first+1],q[first],i,j))first++;
                dp[i][j]=Cal(q[first],i,j);
                while(first<tail&&getup(i,q[tail],i,j)*getdown(q[tail],q[tail-1],i,j)<=getup(q[tail],q[tail-1],i,j)*getdown(i,q[tail],i,j))tail--;
                q[++tail]=i;
            }
        }
        for(int i=1;i<=k;i++)
            ans=min(ans,dp[now][i]);
        cout<<ans<<endl;
    }
    return 0;
}

H - Tree Construction
这个题公式也没推出来,看了题解才发现好奇妙啊。
dp[i][j] 代表第i个点到第j个点建成树的最小花费。
那么dp[i][j]=min(dp[i][k]+dp[k+1][j]+cost(i,j,k));
cost(i,j,k)=nodes[k].x-nodes[i].x+nodes[k+1].y-nodes[j].y
如果不理解cost的话可以自己画画图。
然后我们用四边形优化就可以做出来了。
讲道理真爽。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf=0x3f3f3f3f;
typedef pair<int,int>pii;
pii nodes[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int x,y;
        for(int i=1; i<=n; i++)
        {
            scanf("%d %d",&x,&y);
            nodes[i]=make_pair(x,y);
        }
        memset(dp,inf,sizeof(dp));
        for(int i=1; i<=n; i++)
        {
            dp[i][i]=0;
            s[i][i]=i;
        }
        for(int i=n; i>=1; i--)
            for(int j=i+1; j<=n; j++)
            {
                int tmp=inf;
                int idx;
                for(int k=s[i][j-1]; k<=s[i+1][j]; k++)
                {
                    if(k+1>j)continue;
                    if(tmp>dp[i][k]+dp[k+1][j]+nodes[k+1].first-nodes[i].first+nodes[k].second-nodes[j].second)
                    {
                        tmp=dp[i][k]+dp[k+1][j]+nodes[k+1].first-nodes[i].first+nodes[k].second-nodes[j].second;
                        idx=k;
                    }
                }
                dp[i][j]=tmp;
                s[i][j]=idx;
            }
        cout<<dp[1][n]<<endl;
    }
}

I - Post Office
这个dp式子挺好想的,设dp[i][j] 为前i个村子有j个邮局。那么dp[i][j]=min(dp[k][j-1]+cost(k+1,i)),关键这个cost算得我头皮发麻。。可以发现把邮局放在正中央最好,那么cost也可以进行递推。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=305;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[35][maxn];
int s[35][maxn];
int w[maxn][maxn];
int v,p;
void getw()
{
    for(int i=1;i<=v;i++)
    {
        w[i][i]=0;
        for(int j=i+1;j<=v;j++)
            w[i][j]=w[i][j-1]+a[j]-a[(i+j)>>1];
    }
}
int main()
{

    scanf("%d %d",&v,&p);
    for(int i=1;i<=v;i++)
        scanf("%d",&a[i]);
    getw();
    memset(dp,inf,sizeof(dp));
    for(int i=1;i<=v;i++)
    {
        s[1][i]=0;
        dp[1][i]=w[1][i];
    }
    for(int i=2;i<=p;i++)
        for(int j=v;j>=i;j--)
        {
            int tmp=inf;
            int idx;
            s[i][v+1]=v;
            for(int k=s[i-1][j];k<=s[i][j+1];k++)
            {
                if(tmp>dp[i-1][k]+w[k+1][j])
                {
                    tmp=dp[i-1][k]+w[k+1][j];
                    idx=k;
                }
            }
            dp[i][j]=tmp;
            s[i][j]=idx;
        }
    cout<<dp[p][v]<<endl;
    return 0;
}

J - Batch Scheduling
这也是一道好题。让我学会了如何换种方式思考问题。
一开始做的时候,写了一个从前往后推的式子,dp[i][j] 代表前i个分了j块,因为你要计算时间所以必须要维护块数,然后再斜率优化后复杂度为n^2,结果还是T了。。
看了题解后才发现从后往前推更好。。这样似乎就不需要维护块数了。但式子不是特别好想。
设dp[i]为从i到n需要的最少的时间,那么dp[i]=min(dp[j]+(s+T[i]+T[i+1]+…+T[j-1])*(F[i]+F[i+1]+…+F[n]))
当时在F为啥要加到j之后想了一会,之后就觉得挺有道理的,因为j之后的时间并没有算i到j-1的时间,所以要在这里补上去。
读者如果不理解的话可以自己思考思考。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=10005;
int T[maxn],F[maxn];
int Q[maxn];
long long dp[maxn];
int n,s;
long long getup(int j,int k)
{
    return dp[j]-dp[k];
}
long long getdown(int j,int k)
{
    return T[k-1]-T[j-1];
}
long long Cal(int i,int j)
{
    return dp[j]+1LL*F[i]*(T[j-1]-T[i-1]+s);
}
int main()
{

    scanf("%d %d",&n,&s);

    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&T[i],&F[i]);
        T[i]+=T[i-1];
    }
    for(int i=n;i>=1;i--)
        F[i]+=F[i+1];
    int first=0,tail=0;
    Q[0]=n+1;
    for(int i=n;i>=1;i--)
    {
        while(first<tail&&getup(Q[first+1],Q[first])<=F[i]*getdown(Q[first+1],Q[first]))first++;
        dp[i]=Cal(i,Q[first]);
        while(first<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])<=getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
        Q[++tail]=i;
    }
    cout<<dp[1]<<endl;
}

斜率优化和四边形优化大概知识算是掌握了。只要能把式子写出来应该就会做了,关键是连式子都写不出来。。
为了跑进度,这个专题两个难题都没写,等忙完了会补上的。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值