AYOJ-21523:方格取数--21491:传纸条 --双边dp 最新更新:剪枝!!
来源:互联网 发布:行者步行软件 编辑:程序博客网 时间:2024/06/05 19:59
方格取数
题目描述
设有N*N的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
某人从图的左上角的A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数N(表示N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。
输出
只需输出一个整数,表示2条路径上取得的最大的和。
样例输入
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
样例输出
67
传纸条
题目描述
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。
数据规模和约定
30%的数据满足:1<=m,n<=10
100%的数据满足:1<=m,n<=50
输入格式
输入第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。
输出
输出一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
样例输入
3 3
0 3 9
2 8 5
5 7 0
样例输出
34
首先,这两题相似,都似乎双dp问题,但更重要的是他们的区别!请注意,一格是可以走重复路径,一个不可以!!!
先看《方格取数》
看图
图中的A、B的这一个状态表示某一步,我们可以认为A和B现在的位置为一个“状态”,那么这一个状态可能是有哪几个状态“转移”过来的呢?
如图中红色字体显示的。A和B分别都有两种可能的状态转移而来,那么状态(A,B)就有2*2=4个状态转移而来,分别如下:
(A1,B1),(A1,B2),(A2,B1),(A2,B2);,,,貌似这是排列组合。。。。
明白了这个那么状态转移方程就好写了:
dp(A,B)=max(dp(A1,B1),dp(A1,B2),dp(A2,B1),dp(A2,B2))+map(A)+map(B);map()表示这个格子的数值。转换成坐标就是:
dp[i][ii][j][jj]=max(dp[i][ii-1][j-1][jj],dp[i][ii-1][j-1][jj],dp[i-1][ii][j-1][jj],dp[i-1][ii][j][jj-1])+map[i][ii]+map[j][jj];
虽然是四维数组时间复杂度O(n^4)但是这个思路简单,对于测试数据比较弱的题目还是可以AC的(比如这一题,N<=10)
代码如下:
#include<algorithm>#include<iostream>#include<cstdio>#include<string>#include<cstring>using namespace std;const int MAX=12;int dp[MAX][MAX][MAX][MAX];int mp[MAX][MAX];int max(int a,int b){ return a>b?a:b;}int maxi(int a,int b,int c,int d){ return max(max(a,b),max(c,d));}int main(){ int n,i,ii,j,jj,x,y,w; cin>>n; memset(mp,0,sizeof(mp)); while(cin>>x>>y>>w) { if(x==y && y==w && w==0)break; mp[x][y]=w; } int row=n,col=n;//本题为正方形,所以行和列都为n for(i=1; i<=row; i++) for(j=1; j<=row; j++) for(ii=1; ii<=col; ii++) for(jj=1; jj<=col; jj++) { if(i!=j || ii!=jj)//两次取的点不同 { dp[i][ii][j][jj]=maxi(dp[i][ii-1][j][jj-1],dp[i][ii-1][j-1][jj],dp[i-1][ii][j][jj-1],dp[i-1][ii][j-1][jj]) +mp[i][ii]+mp[j][jj];//需要加上这两点的值 } if(i==j && ii==jj)//两次取的点相同 { dp[i][ii][j][jj]=maxi(dp[i][ii-1][j][jj-1],dp[i][ii-1][j-1][jj],dp[i-1][ii][j][jj-1],dp[i-1][ii][j-1][jj]) +mp[i][ii];//只需要加一次 } } cout<<max(dp[row][col-1][row-1][col],dp[row-1][col][row][col-1])+mp[row][col]<<endl;//最后的一格程序没有包含,故要加上 return 0;}
考虑到时间复杂度高,可以想办法降低维数到 O(n^3)根据题意,每次只能向下和向右走,(i,j)向下就是(i+1,j) 向右是(i,j+1);有什么特点呢?可以发现行和列的和保持不变。令k=i+j;则状态转移方程可以转化为:dp[k][i][j]=max(dp[k-1][i-1][j],dp[k-1][i][j-1],dp[k-1][i-1][j-1],dp[k-1][i][j])+map[i][k-j]+map[j][k-i];注意此时,k的取值范围就扩大了一倍!!
#include<algorithm>#include<iostream>#include<cstdio>#include<string>#include<cstring>using namespace std;const int MAX=12;int dp[2*MAX][MAX][MAX];int mp[MAX][MAX];int max(int a,int b){ return a>b? a:b;}int maxi(int a,int b,int c,int d){ return max(max(a,b),max(c,d));}int main(){ int n,i,ii,j,jj,x,y,w; cin>>n; memset(mp,0,sizeof(mp)); while(cin>>x>>y>>w) { if(x==y && y==w && w==0)break; mp[x][y]=w; } int row=n,col=n;//本题为正方形,所以行和列都为n int all=row+col; for(int k=1; k<=all; k++)//这里的k表示列(两列)i和j分别表示行, for(i=1; i<=row; i++) for(j=1; j<=row; j++) { if(i!=j && k>=i && k>=j)//两次走到不同点 { dp[k][i][j]=maxi(dp[k-1][i][j],dp[k-1][i][j-1],dp[k-1][i-1][j],dp[k-1][i-1][j-1]) +mp[i][k-i]+mp[j][k-j];//注意这个地方不要写成mp[i][k-j]了,,wa了一次 } if(i==j && k>=i && k>=j)//两次相同点 { dp[k][i][j]=maxi(dp[k-1][i][j],dp[k-1][i][j-1],dp[k-1][i-1][j],dp[k-1][i-1][j-1]) +mp[i][k-i];//当i和j相等 ,只加一次 } } cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])+mp[row][col]<<endl;//最后一个点必须加上 return 0;}
然后是《传纸条》
基本思路一样,转化为从一个点出发,到达另一个点,特点是
1,首尾两个点没有值;
2,路径不能重复;
这样,代码中就要去掉一格if语句,然后最后也不用加上终点的值。
#include<algorithm>#include<iostream>#include<cstdio>#include<string>#include<cstring>using namespace std;int dp[105][52][52];int mp[52][52];int max(int a,int b){return a>b? a:b;}int main(){ int N,i,j,k,row,col,all; scanf("%d",&N); while(N--) { scanf("%d%d",&row,&col); memset(dp,0,sizeof(dp)); memset(mp,0,sizeof(mp)); for(i=1; i<=row; i++) for(j=1; j<=col; j++) scanf("%d",&mp[i][j]); all=row+col; for(k=2; k<=all; k++) for(i=1; i<=row; i++) for(j=1; j<=row; j++) { if(i!=j && k>=i && k>=j ) { int a= max( dp[k-1][i][j],dp[k-1][i-1][j-1]); int b= max( dp[k-1][i-1][j],dp[k-1][i][j-1]); dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j]; } } cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])<<endl; } return 0;}
代码运行时间一百六十多毫秒,而后参照牛人的代码,可以剪枝的!!兴奋中。。。。
#include<algorithm>#include<iostream>#include<cstdio>#include<string>#include<cstring>using namespace std;int dp[105][52][52];int mp[52][52];int max(int a,int b){return a>b? a:b;}int main(){ int N,i,j,k,row,col,all; scanf("%d",&N); while(N--) { scanf("%d%d",&row,&col); memset(dp,0,sizeof(dp)); // memset(mp,0,sizeof(mp));删掉 for(i=1; i<=row; i++) for(j=1; j<=col; j++) scanf("%d",&mp[i][j]); all=row+col; for(k=3; k<all; k++)//优化了 for(i=1; i<=row; i++) for(j=i+1; j<=row; j++)//优化了,因为双线的话,一定有一个条在另一条上边,横坐标加一 { // if(i!=j && k>=i && k>=j ) // { // int a= max( dp[k-1][i][j],dp[k-1][i-1][j-1]); // int b= max( dp[k-1][i-1][j],dp[k-1][i][j-1]); // dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j]; // } if(k-i<1 || k-j<1)break; //这里因为i和j不表示坐标,而是两个移动坐标的横坐标 if(k-i>col || k-j>col)continue;//有k为纵坐标的和,有k=row+col得,col=k-row;这里的i和j都表示row,因而得出col的取值范围 //特别注意,这里k-i>col 不是 row !! int a=max(dp[k-1][i][j],dp[k-1][i-1][j-1]);//一下代码一样了 int b=max(dp[k-1][i-1][j],dp[k-1][i][j-1]); dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j]; } cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])<<endl; } return 0;}
时间减少了三分之二!可能还有优化的,望高人指导。
2 1
- AYOJ-21523:方格取数--21491:传纸条 --双边dp 最新更新:剪枝!!
- 方格取数+传纸条
- 方格取数&传纸条【多进程DP】
- 方格取数or传纸条
- NOIP 2000 方格取数 NOIP 2008 传纸条 多线程DP
- codevs 1169 传纸条 && 1043 方格取数(棋盘DP)
- NOIP 2000 方格取数 2008传纸条
- 洛谷1004 方格取数+洛谷1006 传纸条
- Codevs 1169 传纸条/1043 方格取数 2008/2000年NOIP全国联赛提高组 多线程dp
- [DP]方格取数
- 方格取数 双线DP
- 【DP】[NOIP2000]方格取数
- OpenJudge_P8786 方格取数(DP)
- 双进程动态规划(noip 2008 传纸条,codevs 方格取数)
- 方格取数 (多进程DP)
- 多线程DP 三取方格数
- neu1458 方格取数 dp解法
- HDU - 1565 方格取数(1) (DP)
- stm32 低功耗设计[操作寄存器+库函数]
- c++ builder release编译选项说明
- UTF-8中的BOM-带还是不带?
- STL 中map 容器的基本用法
- Unix 网络编程 学习笔记一 环境搭建
- AYOJ-21523:方格取数--21491:传纸条 --双边dp 最新更新:剪枝!!
- Linux下串口工具的安装与配置
- DllMain中不当操作导致死锁问题的分析——DllMain中要谨慎写代码(完结篇)
- JQuery运用toggle实现网页加载完成自动弹窗
- 嵌入式linux学习笔记4之字符设备驱动
- 2-三角形类2(get函数)
- Webservice接口
- Linux自动下发送HTML格式并带附件的邮件
- Pro Android学习笔记(四二):Fragment(7):切换效果