洛谷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。问题就出在最后一个点上

原创粉丝点击