洛谷Oj-创意吃鱼法-二维前缀和/二维动态规划
来源:互联网 发布:最优化算法工程例题 编辑:程序博客网 时间:2024/06/08 17:39
问题描述:
回到家中的猫猫把三桶鱼全部转移到了她那长方形大池子中,然后开始思考:到底要以何种方法吃鱼呢(猫猫就是这么可爱,吃鱼也要想好吃法 ^_*)。她发现,把大池子视为01矩阵(0表示对应位置无鱼,1表示对应位置有鱼)有助于决定吃鱼策略。
在代表池子的01矩阵中,有很多的正方形子矩阵,如果某个正方形子矩阵的某条对角线上都有鱼,且此正方形子矩阵的其他地方无鱼,猫猫就可以从这个正方形子矩阵“对角线的一端”下口,只一吸,就能把对角线上的那一队鲜鱼吸入口中。
猫猫是个贪婪的家伙,所以她想一口吃掉尽量多的鱼。请你帮猫猫计算一下,她一口下去,最多可以吃掉多少条鱼?
AC代码①:二维前缀和
int n,m;int pond[3000][3000],sum[3000][3000];//前缀和数组sumvoid calc()//计算前缀和{ for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + pond[i][j];//优美}int query(int x1,int y1,int x2,int y2)//查询前缀和{ return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];}void turn()//翻转矩阵{ for(int i = 1; i <= n; ++i) for(int j = 1; j <= m / 2; ++j) swap(pond[i][j],pond[i][m - j + 1]);//swap函数很好用}int work(){ calc();//计算前缀和 int ans = 0;//初始化为0 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1)//如果是1 { int k = 1; //不越界&&是1&&前缀和,方向:从右下到左上 while(i - k >= 1 && j - k >= 1 && pond[i - k][j - k] == 1 && query(i - k,j - k,i,j) == k + 1) k++;// 方向:从左上到右下,结果最后一个点TLE,所以说是否AC还是要看数据出得怎样// while(i + k <= n && j + k <= m && pond[i + k][j + k] == 1 && query(i,j,i + k,j + k) == k + 1)// k++; ans = max(ans,k);//更新答案 } return ans;}int main(){ cin >> n >> m; //输入 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%d",&pond[i][j]); int ans = work();//第一次 turn();//翻转 ans = max(ans,work());//第二次 cout << ans << endl; return 0;}
代码②:动态规划 + 矩阵翻转(TLE一个点)
int n,m;int pond[2500][2500],sumr[2500][2500],sumc[2500][2500],dp[2500][2500];//行前缀和r,列前缀和c,dp[i][j]记录以点(i,j)为右下角顶点的最大单位矩阵void calcr()//计算行前缀和{ for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) sumr[i][j] = sumr[i][j - 1] + pond[i][j];}void calcc()//计算列前缀和{ for(int j = 1; j <= m; ++j) for(int i = 1; i <= n; ++i) sumc[i][j] = sumc[i - 1][j] + pond[i][j];}void turn()//翻转矩阵{ for(int i = 1; i <= n; ++i) for(int j = 1; j <= m / 2; ++j) swap(pond[i][j],pond[i][m - j + 1]);}int work(){ calcr();//计算行前缀和 calcc();//计算列前缀和 //初始化dp数组,边界 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1) dp[i][j] = 1; int ans = 0;//初始化为0,而不是-inf for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1 && pond[i - 1][j - 1] == 1)//如果为1 { for(int k = dp[i - 1][j - 1]; k >= 0; --k)//关键中的关键 if(sumr[i][j] - sumr[i][j - k - 1] == 1 && sumc[i][j] - sumc[i - k - 1][j] == 1)//利用前缀和判断是否满足单位矩阵的条件 { dp[i][j] = k + 1;//注意是k + 1 break;//找到一个最大的解就退出循环 } } //找出最大值 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) ans = max(ans,dp[i][j]); return ans;//返回}int main(){ cin >> n >> m; //输入 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%d",&pond[i][j]); int ans = work();//第一次 turn();//翻转 ans = max(ans,work());//第二次 cout << ans << endl; return 0;}
代码③:动态规划 + 矩阵不翻转(AC)
int n,m;int pond[2600][2600],sumr[2600][2600],sumc[2600][2600],dp[2600][2600];void calcr(){ for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) sumr[i][j] = sumr[i][j - 1] + pond[i][j];}void calcc(){ for(int j = 1; j <= m; ++j) for(int i = 1; i <= n; ++i) sumc[i][j] = sumc[i - 1][j] + pond[i][j];}int work(){ calcr(); calcc(); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1) dp[i][j] = 1; int ans = 0; for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1 && pond[i - 1][j - 1] == 1) { for(int k = dp[i - 1][j - 1]; k >= 0; --k) if(sumr[i][j] - sumr[i][j - k - 1] == 1 && sumc[i][j] - sumc[i - k - 1][j] == 1) { dp[i][j] = k + 1; break; } } for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) ans = max(ans,dp[i][j]); return ans;}int main(){ cin >> n >> m; for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) { scanf("%d",&pond[i][j]); dp[i][j] = pond[i][j];初始化 } calcr(); calcc(); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1 && pond[i - 1][j - 1] == 1) { for(int k = dp[i - 1][j - 1]; k >= 0; --k) if(sumr[i][j] - sumr[i][j - k - 1] == 1 && sumc[i][j] - sumc[i - k - 1][j] == 1) { dp[i][j] = k + 1; break; } } int ans = 0; for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) { ans = max(ans,dp[i][j]); dp[i][j] = pond[i][j];//再次初始化 } //两次DP大致相同 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(pond[i][j] == 1 && pond[i - 1][j + 1] == 1) { for(int k = dp[i - 1][j + 1]; k >= 0; --k) if(sumr[i][j + k] - sumr[i][j - 1] == 1 && sumc[i][j] - sumc[i - k - 1][j] == 1) { dp[i][j] = k + 1; break; } } for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) ans = max(ans,dp[i][j]); cout << ans << endl; return 0;}
解决方法:
初次看到这道题,觉得DP还是无从下手,于是想偷懒写个前缀和
自己的思维还不够缜密,漏掉了副对角线的情况
这道题还需要注意的就是边界,因为第0行和第0列的元素都为0,所以不需要对前缀和数组memset
要考虑到矩阵全是0的情况,因此ans不能初始化为-inf。还要考虑到最后一个点可能是2500*2500的,数组一定要开大一点
学到了一个小技巧,那就是对矩阵进行翻转,这样不用调整方法。缺点就是需要重新求前缀和,翻转也需要时间开销,最后一个点很有可能超时
本题的多组输入真是坑,明明只有一组输入
对于最后一个点超时,可以换一个方向试试,说不定出题人就是卡这种最常见的方向
这道题抽象一下,应该叫做求最大单位矩阵
对于刚才说的关键中的关键,我之前的错误写法是 dp[i][j] = dp[i - 1][j - 1] + 1;
,用通俗的话来说就是有点完美主义了,不是最好的我不要,而不是尽最大可能去利用,这有点难以言传,放一个测试数据
4 4
1 0 0 0
0 1 0 0
1 0 1 0
0 0 0 1
答案:2,显然正确答案应该是3。问题就出在最后一个点上
- 洛谷Oj-创意吃鱼法-二维前缀和/二维动态规划
- 二维前缀和
- 二维前缀和
- BZOJ 1218 二维前缀和
- HDU1176(二维动态规划)
- |洛谷|动态规划|P1736 创意吃鱼法
- HDU1081二维子数组最大和/压缩/动态规划/DP
- 【动态规划】求二维矩阵的最大和子矩阵
- 【动态规划】[luoguP1736]创意吃鱼法
- bzoj1218 激光炸弹 二维前缀和
- 【POJ2155】Matrix-二维树状数组+前缀和
- hdu 5791 Two 二维前缀和
- codeforces Star sky(二维前缀和)
- 二维前缀和 codeforces 832C
- Codeforces835C-Star sky(二维前缀和+思维)
- codeforces835 b贪心 c 二维前缀和
- codeforces 835C(二维前缀和)
- 【CS 1373】射命丸文(二维前缀和)
- Play从2.6.x开始使用Akka HTTP作为默认服务后端
- B2C电商网站技术面试题分享
- webpack入门第一篇
- sql 的MINUS指令的应用(只适用于oracle,别的数据库可以使用联合连接union join!比较少见!)
- set+链表 【POJ Challenge】生日礼物
- 洛谷Oj-创意吃鱼法-二维前缀和/二维动态规划
- 可能是国内最火的开源项目 —— PHP 篇
- Life Forms POJ
- 在Android设备上配置TensorFlow(四)无法使用TensorFlow训练新model
- 一体机装系统失败进不去系统该怎么办?
- Git 常用命令 笔记
- Codeforces 862 C. Mahmoud and Ehab and the xor (技巧)
- 顺序表应用2:多余元素删除之建表算法
- 《C++ Concurrency in Action》笔记11 同步初始化