• 线段树优化dp


    题目大意

    有一个 n行m列的矩阵,每个格子有一花费Tij,要求在每行选出恰好一个格子,使得这n个格子的Tij 之和最小,每个格子还有一权值Wij.对于相邻两行选择的格子(i,j1)(i-1,j2)要求abs(j1-j2)<=W(i,j1)+W(i-1,j2),多组数据T(2.5S)
    Sample Input
    1
    3 5
    9 5 3 8 7
    8 2 6 8 9
    1 9 7 8 6
    0 1 0 1 2
    1 0 2 1 1
    0 2 1 0 2
    Sample Output
    10
    数据范围与约定
    对于 20% 的数据,保证数据,保证 T = 1
    对于另 30% 的数据,保证 m < = 500 ;
    对于 100% 的数据,保证 T<= 5, 2≤n≤100 ,1 <=m<=5000,0<= Tij 、Wij≤100000


    这个的dp方程恨好推啦,那么前50分就可以这样拿

    #include<bits/stdc++.h>
    #define INF 2100000001
    #define N 103
    #define M 5003
    #define re register
    using namespace std;
    int read()
    {
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    int c[N][M],w[N][M],dp[N][M];
    int main()
    {
    //    freopen("elect.in","r",stdin);
    //    freopen("elect.out","w",stdout);
        int T=read();
        int n=read(),m=read();
        while(T--)
        {
            for(re int i=1;i<=n;++i)
              for(re int j=1;j<=m;++j)
                c[i][j]=read();
            for(re int i=1;i<=n;++i)
              for(re int j=1;j<=m;++j)
                w[i][j]=read();
            for(re int i=2;i<=n;++i)
              for(re int j=1;j<=m;++j)
                dp[i][j]=INF;
            for(re int i=1;i<=m;++i)
              dp[1][i]=c[1][i];
            for(re int i=2;i<=n;++i)
            {
                for(re int j=1;j<=m;++j)//now
                {
                    for(re int k=1;k<=m;++k)//last
                    {
                        if(w[i][j]+w[i-1][k]>=abs(j-k))
                          dp[i][j]=min(dp[i][j],dp[i-1][k]+c[i][j]);
                    }
                }
            }
            int ans=INF;
            for(re int i=1;i<=m;++i)
              ans=min(ans,dp[n][i]);
            printf("%d
    ",ans);
        }
    } 
    /*
    1
    3 5
    9 5 3 8 7
    8 2 6 8 9
    1 9 7 8 6 
    0 1 0 1 2
    1 0 2 1 1
    0 2 1 0 2
    
    */
    50分

    当然,50分的时间是O(T*m^2*n)的,肯定过不了,考虑优化。

    我们发现,每一层的dp都是从上一层转移过来,而可以转移的条件又比较特殊,如果是一段连续的区间我们就可以考虑单调队列优化或者斜率优化。但不幸的是,区间不是连续的,而是与W值有关。

    |j-k|<=w[i][j]+w[i-1][k],我们可能看到绝对值,思考过分类讨论,但行不通。

    但看到绝对值,我们还可以想到“距离”,那么把j左右都延伸w[i][j]的距离,把k左右都延伸w[i-1][k]的距离,若两者有交集则说明可以转移,那么对于每一个j,其都有覆盖的一段区间,若上一层的k和这一层覆盖的区间有交集,说明可以转移,而转移的也就是共同区间的最小值,也可以说就是j可以到的区间中的最小值(这个最小值是上一层的)用线段树维护一个区间最小值就好了。

    #include<bits/stdc++.h>
    #define INF 2100000001
    #define N 103
    #define M 5003
    #define re register
    using namespace std;
    int read()
    {
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    int c[N][M],dp[N][M],minn[M<<2],flagg[M<<2],l[N][M],r[N][M];
    void build(int k,int l,int r)
    {
        if(l==r){minn[k]=INF;flagg[k]=INF;return;}
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        minn[k]=min(minn[k<<1],minn[k<<1|1]);
        flagg[k]=min(flagg[k<<1],flagg[k<<1|1]);
    }
    void pushdown(int k)
    {
        if(flagg[k]==INF) return;
        minn[k<<1]=min(minn[k<<1],flagg[k]);
        minn[k<<1|1]=min(minn[k<<1|1],flagg[k]);
        flagg[k<<1]=min(flagg[k<<1],flagg[k]);
        flagg[k<<1|1]=min(flagg[k<<1|1],flagg[k]);
        flagg[k]=INF;
    }
    void modify(int k,int L,int R,int l,int r,int v)
    {
        if(L>=l&&R<=r)
        {
            minn[k]=min(minn[k],v);
            flagg[k]=min(flagg[k],v);
            return ;
        }
        pushdown(k);
        int mid=(L+R)>>1;
        if(l<=mid) modify(k<<1,L,mid,l,r,v);
        if(r>mid) modify(k<<1|1,mid+1,R,l,r,v);
        minn[k]=min(minn[k<<1],minn[k<<1|1]);
    }
    int query(int k,int L,int R,int l,int r)
    {
        int ans=INF;
        if(L>=l&&R<=r) return minn[k];
        int mid=(L+R)>>1;
        pushdown(k);
        if(l<=mid) ans=min(ans,query(k<<1,L,mid,l,r));
        if(r>mid) ans=min(ans,query(k<<1|1,mid+1,R,l,r));
        return ans;
    }
    int main()
    {
    //    freopen("elect.in","r",stdin);
    //    freopen("elect.out","w",stdout);
        int T=read();
        int n=read(),m=read();
        while(T--)
        {
            memset(l,0,sizeof(l));
            memset(r,0,sizeof(r));
            for(re int i=1;i<=n;++i)
              for(re int j=1;j<=m;++j)
                c[i][j]=read();
            for(re int i=1;i<=n;++i)
              for(re int j=1;j<=m;++j)
              {
                  int w=read();
                  l[i][j]=max(1,j-w);
                  r[i][j]=min(m,j+w);
              }
            build(1,1,m);
            for(re int j=1;j<=m;++j)
              dp[1][j]=c[1][j],modify(1,1,m,l[1][j],r[1][j],dp[1][j]);
            for(re int i=2;i<=n;++i)
            {
                for(re int j=1;j<=m;++j)//now
                {
                    int p=query(1,1,m,l[i][j],r[i][j]);//找到最小的 
                    dp[i][j]=p+c[i][j];
                }
                build(1,1,m);//每一层都有一个线段树,维护上一层的最小值,因为只有i-1对i有影响 
                for(int j=1;j<=m;++j)
                modify(1,1,m,l[i][j],r[i][j],dp[i][j]);//把这一层的值插入到线段树中 
            }
            int ans=INF;
            for(re int i=1;i<=m;++i)
              ans=min(ans,dp[n][i]);
            printf("%d
    ",ans);
        }   
    } 
    /*
    1
    3 5
    9 5 3 8 7
    8 2 6 8 9
    1 9 7 8 6 
    0 1 0 1 2
    1 0 2 1 1
    0 2 1 0 2
    
    1
    3 5
    9 3 8 6 4
    9 4 6 9 1
    8 7 5 9 1
    0 2 1 0 1
    1 0 0 0 0 
    1 1 2 0 0
    */
    View Code

    这个思想还是很重要的~

  • 相关阅读:
    打开LogCat以便查看日志
    sql语句优化
    IIS部署说明
    VM上Hadoop3.1伪分布式模式搭建
    C# 程序结构
    CSS笔记1:属性定位
    VS2013 添加控制台程序
    布局 Layout
    [游泳] 游泳学习课程
    "12306"台前幕后:五年利益之争 仓促上线
  • 原文地址:https://www.cnblogs.com/yyys-/p/11226169.html
Copyright © 2020-2023  润新知