HDU 4495 Rectangle(前缀hash)

34 篇文章 1 订阅
9 篇文章 0 订阅

Rectangle

Time Limit: 10000/10000 MS (Java/Others)    Memory Limit: 65535/102400 K (Java/Others)
Total Submission(s): 529    Accepted Submission(s): 195


Problem Description
Given a rectangle which contains N rows and each of them contains a string length of M.
You must find out a symmetrical isosceles right triangle (with two equal edges and a right angle) with two edges parallel two side of the rectangle. Symmetry means the value should be the same according to the shortest altitude of the triangle. And just output the area of the triangle. 
 

Input
The first line of the input contains an integer T (1 <= T <= 20), which mean there are T test case follow.
For each test case, the first line contains two integer number N and M (1 <= N, M <= 500) which means described above.
And then N lines follow, which contains a string of length M. The string only contains letters or digits.
 

Output
For each test case, output a single integer that is the answer to the problem described above. 
 

Sample Input
  
  
1 4 4 abab dacb adab cabb
 

Sample Output
  
  
6
 

Source
 

Recommend
liuyiding

解题思路:

    有一个n*m的包含数字或字母矩阵,让你在上面找出最大的腰与矩阵边平行的等腰直角三角形。


解题思路:

    首先,满足题目要求的三角形只有4种情况,分别处理它们比较麻烦,我们直接选取一种情况然后旋转矩阵即可。

    那么现在就只有一种三角形了,对于现在问题的求解,比较容易想到的就是枚举直角点,然后枚举长度检查。可以发现对于一个三角形的检查是O(n^2)的效率非常低,但是我们可以先处理出每个点最大可以延展的线段长度,然后再dp,这样我们就可以对每个点O(n)的判断线段相等,在处理所有点后再O(n^2)的dp即可。

    可是判断最长延伸线段的长度总复杂度是O(n^3)的还是不够。我们现在每个点都要向上向左扫一遍,实际上是有非常多的重复操作,而且对于每一个点,延伸的线段长度是满足单调性的。所以我们就可以对于每个点二分延伸长度然后利用前缀判断。可是这样又有了一个问题对于字符串的前缀没有办法O(1)的检查,为了解决这个问题hash就要派上用场了,我们对前缀进行hash,然后就可O(1)的判定某一段是否相等。不过由于n可以去到500,这里就只能有损hash,虽然牺牲了一定的正确性,但是实际上我们比较的一定是相同长度的串的hash值,正确率是非常高的。

    进过上面一系列优化,我们的时间复杂度变成了:处理前缀hash的O(n^2) + 枚举顶点二分长度的O(n^2 * log n) + dp的O(n^2)。总时间复杂度O(n^2 * log n)。


AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <string>
#include <map>
#include <set>
#include <list>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define ULL unsigned long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXN=500+10;
const int BASE=123;//123进制,'z'的ASCII码最大,为122
char maze[MAXN][MAXN], tmp_maze[MAXN][MAXN];
int N, M, ans;
int max_length[MAXN][MAXN];//以i,j为起点的最大向上向左相等线段长度
int dp[MAXN][MAXN];//以i,j为起点的最大向上向左相等的三角形边长
ULL hash_val[MAXN][MAXN][2];//0表示横着,1表示竖着,会溢出,有损哈希前缀
ULL p[MAXN];//保存123的次幂,用来计算某一段的hash值

void init()
{
    ans=0;
}

inline ULL get_val(int x, int l, int r, bool dir)
{
    return hash_val[x][r][dir]-hash_val[x][l][dir]*p[r-l];
}

bool judge(int y, int x, int len)
{
    ULL a=get_val(y, x-len, x, 0);
    ULL b=get_val(x, y-len, y, 1);
    return a==b;
}


/*
 这里选取的是这种形状的三角形
   #
  ##
 ###
 */
int solve()
{
    mem(dp, 0);
    for(int i=1;i<=N;++i)//处理出前缀和哈希
        for(int j=1;j<=M;++j)
        {
            hash_val[i][j][0]=hash_val[i][j-1][0]*BASE+maze[i][j];
            hash_val[j][i][1]=hash_val[j][i-1][1]*BASE+maze[i][j];
        }
    
    for(int i=1;i<=N;++i)//枚举起点
        for(int j=1;j<=M;++j)
        {
            int l=0, r=min(i, j)+1;//二分向左向上相同的最大线段长度
            while(l+1<r)
            {
                int mid=(l+r)>>1;
                if(judge(i, j, mid))
                    l=mid;
                else r=mid;
            }
            max_length[i][j]=l;
        }
    int res=0;
    //通过dp由最大相等的线段长度得到最大对称的三角形
    for(int i=1;i<=N;++i)
        for(int j=1;j<=M;++j)
        {
            dp[i][j]=min(dp[i-1][j-1]+2, max_length[i][j]);
            res=max(res, dp[i][j]);
        }
    return (res+1)*res/2;
}

void revolve()//旋转矩形
{
    for(int i=1;i<=N;++i)
        for(int j=1;j<=M;++j)
            tmp_maze[i][j]=maze[i][j];
    for(int i=1;i<=M;++i)
        for(int j=1;j<=N;++j)
            maze[i][j]=tmp_maze[j][M-i+1];
    swap(N, M);
}

int main()
{
    p[0]=1;//预处理出123的次幂
    for(int i=1;i<MAXN;++i)
        p[i]=p[i-1]*BASE;
    int T_T;
    scanf("%d", &T_T);
    while(T_T--)
    {
        scanf("%d%d", &N, &M);
        init();
        for(int i=1;i<=N;++i)
            scanf("%s", maze[i]+1);
        //一共4个方向,旋转一次矩阵求解一次
        ans=max(ans, solve());
        for(int i=0;i<3;++i)
        {
            revolve();
            ans=max(ans, solve());
        }
        printf("%d\n", ans);
    }
    
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值