线段树优化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\n",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\n",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

这个思想还是很重要的~

 

posted @ 2019-07-22 15:31  yyys  阅读(927)  评论(0编辑  收藏  举报