zoukankan      html  css  js  c++  java
  • [luogu P1006]传纸条

    这题做的我怀疑人生。。这题应该属于看起来简单,做起来费劲那种类型的。

    介绍三种做法,复杂度各不相同, \(O(n^5)\) \(O(n^4)\)\(O(n^3)\) 都有(五次方的好像是一个新做法)

    题意:给出\(n\)\(m\)列的矩阵,以左上角为起点,以右下角为终点,找到两条不相交路径,经过的权值和最大是多少。

    \(O(n^5)\)做法

    把最终的路径划分为 \(n\) 行,设 \(f_{i,j,k}\) 表示两张纸条传到第 \(i\) 行,第一张传到第 \(j\) 列,第二张传到第 \(k\) 列,能得到的最大好心值。

    转移方程很好写,从上一行合法的位置转移过来即可。这种方法容易确保路径不相交,而且跑不满,最慢的点只有300ms,开了O2甚至比 \(n^4\) 做法还要快。

    代码:

    #include<cstdio>
    #include<cctype>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int a[81][81],qz[81][81];int n,m;
    int f[81][81][81];
    int main(void){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&a[i][j]),qz[i][j]=qz[i][j-1]+a[i][j];
    	
    	memset(f,0xc1,sizeof(f));//-1044266559
        //赋初值为无穷小,可以自动过滤掉不合法状态
    	for(int i=1;i<=m;++i)f[1][1][i]=qz[1][i];
    	for(int i=2;i<=n;++i){
    		for(int j=1;j<m;++j)for(int k=j+1;k<=m;++k){
    			for(int jj=1;jj<=j;++jj)for(int kk=j+1;kk<=k;++kk)
    				f[i][j][k]=max(f[i][j][k],f[i-1][jj][kk]+qz[i][j]-qz[i][jj-1]+qz[i][k]-qz[i][kk-1]);
    		}
    	}
    	
    	printf("%d\n",f[n][m-1][m]);
    	return 0;
    }
    
    

    \(O(n^4)\)做法

    其他题解里介绍的很多了。用 \(f_{x1,y1,x2,y2}\) 表示两个纸条分别传到\((x1,y1)(x2,y2)\)的最大值。

    这种做法看起来不容易确保路径不相交,其实是可以的,就是有些麻烦。

    想象一个方格图,左上角是(1,1),右下角是(n,m),想一想如果一个纸条在另一个纸条左上方,里面的那个纸条就动不了了,因为我们不知道右下的纸条经过了哪些格。

    如果两个纸条在同一行或者同一列,或者一个在另一个右上,却可以进行转移。(原来这么简单)

    具体实现呢?分成同一行或者同一列,还有右上两种情况考虑。本来以为边界值不太好写,后来发现想复杂了(我太菜了,老是搞不明白这种细节)。此代码可以进一步简化。

    #include<cstdio>
    #include<cctype>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int a[81][81];int n,m;
    int f[81][81][81][81];
    //x1>=x2 y1<=y2
    int main(void){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&a[i][j]);//,qz[i][j]=qz[i][j-1]+a[i][j];
    
        memset(f,0xc1,sizeof(f));//-1044266559
        f[1][1][1][1]=0;
    
        for(int x1=1;x1<=n;++x1)for(int x2=1;x2<=x1;++x2)
            for(int y2=1;y2<=m;++y2)for(int y1=1;y1<=y2;++y1){
                //x1 y1 x2 y2 可以用作局部变量 
                if(x1==x2&&y1==y2)continue;
                int &dp=f[x1][y1][x2][y2];
                if(x1==x2){
                    dp=max(dp,f[x1][y1][x2][y2-1]+a[x2][y2]);
                    dp=max(dp,f[x1][y1][x2-1][y2]+a[x2][y2]);
                }
                else if(y1==y2){
                    dp=max(dp,f[x1-1][y1][x2][y2]+a[x1][y1]);
                    dp=max(dp,f[x1][y1-1][x2][y2]+a[x1][y1]);
                }
                else {
                    dp=max(dp,f[x1][y1][x2][y2-1]+a[x2][y2]);
                    dp=max(dp,f[x1][y1][x2-1][y2]+a[x2][y2]);
                    dp=max(dp,f[x1-1][y1][x2][y2]+a[x1][y1]);
                    dp=max(dp,f[x1][y1-1][x2][y2]+a[x1][y1]);
                }
            }
    
        printf("%d\n",f[n][m-1][n-1][m]);
        return 0;
    }
    }
    
    

    \(O(n^3)\)做法

    最快的一种做法,别的题解也介绍过了。

    两个纸条一起走,这个走一步那个也走一步。是不是巧妙地避免了路径相交?

    \(f_{i,j,k}\) 表示两个纸条分别走了 \(i\) 步,一个走到 \(j\) 行一个走到 \(k\) 行。步数行数都知道了,可以算列数。转移方程:\(f_{i,j,k}=\max(f_{i-1,j,k},f_{i-1,j-1,k},f_{i-1,j,k-1},f_{i-1,j-1,k-1})+a_{j,i+2-j}+a_{k,i+2-k}\)

    具体实现需要注意,每一步最少/最多走到多少行,比如你走了很多步不可能还在第一行。

    代码

    #include<cstdio>
    #include<cctype>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #define Max(a,b,c,d) max(max(a,b),max(c,d))
    using namespace std;
    
    int n,m;
    int a[81][81],f[161][81][81];
    int main(void){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&a[i][j]);
    	memset(f,0xc0,sizeof(f));
    	f[0][1][1]=0;
    	for(int i=1;i<n+m-2;++i){
    		const int liml=max(1,i-m+2),limr=min(n,i+1);
            //limit_left limit_right
    		for(int j=liml;j<=limr;++j)for(int k=liml;k<=limr;++k)if(k!=j)
    			f[i][j][k]=max(f[i][j][k], Max(f[i-1][j][k],f[i-1][j-1][k],f[i-1][j-1][k-1],f[i-1][j][k-1]) + a[j][i+2-j]+a[k][i+2-k] );
    	}
    	printf("%d\n",f[n+m-3][n-1][n]);
    	return 0;
    }
    
  • 相关阅读:
    ajax获取值的两种方法
    java反射
    idea 方便的设置代码段
    jstl核心标签库
    git遇到的问题 .Git: There is no tracking information for the current branch.
    java使用顺序存储实现队列
    RabbitMQ基本操作
    springboot 如何操作redis
    docker遇到的问题以及docker 操作镜像的基本操作
    教你在 Yii2 中添加全局函数
  • 原文地址:https://www.cnblogs.com/happyguy/p/12944667.html
Copyright © 2011-2022 走看看