寻找tju——O(T*m*n)做法

大致题意

给定一个m行n列的字符矩阵,要输出分别以t、j、u字符为顶点的直角三角形(两边与行、列平行)有多少个。

输入形式:
第一行一个整数T,表示接下来需分别判断T个矩阵;
接着是整数m和n,分别代表矩阵的行和列;
然后是m×n大小的字符矩阵。
其中,T≤100,1≤m,n≤1000

示例输入:
3
3 5
uuuuu
tjjjj
uuuuu
2 2
tu
ju
1 1
x
示例输出:
16
2
0

思路

1)“两边与行、列平行”说明只需考虑两直角边“一横一竖”的三角形,也就是满足题意得三角形的两直角边上的点分别在同一行和同一列;
2)“以t、j、u字符为顶点”即判断是否满足题意只需考虑三个点,即只需考虑某一行上的两个不同的字符以及与其中一个字符处于同一列的剩下一个字符
3)T≤100,1≤m,n≤1000,说明要求的算法的时间复杂度至少要小于等于O(Tmn),但凡出现一个平方或次方,这道题就要超时了。

比如:

uux
t w j
uuu

看第2行,考虑其第一个元素‘t’,则与其在同一行上的另一个三角形顶点只能是‘j’或‘u’,此时只有‘j’,则‘t’-'j'([2][1]-[2][3])可作为三角形其中一边;那么,另外一边便锁定在‘t’([2][1])所在的第1列与‘j’([2][3])所在的第3列上。又因为‘t’和‘j’已经确定,所以剩下的一个顶点必然为‘u’。此时,满足条件的‘u’有3个(‘u’([1][1])、‘u’([3][1])、‘u’([3][3]))。那么,以‘t’-'j'([2][1]-[2][3])为横边的满足题意的直角三角形个数为3。

此时,可以发现,当确定直角三角形所在行的两个顶点时,三角形的个数即为两个顶点所在列里(与已确定的两个字符不同的)剩下的那个字符的个数

所以,我们首先需要使用数组来存储每一列上't'、'j'、'u'的个数。这样,便可在确定行上的顶点(所在列)后,以O(1)的复杂度直接得到满足题意的三角形个数。

——那么,如何确定行上的顶点

很简单,当某一个顶点为't'时,另外一个点是同一行上的'j'或'u';当某一个顶点为'j'时,另外一个点是同一行上的't'或'u';当某一个顶点为'u'时,另外一个点是同一行上的't'或'j'。

——但是,确定一个点后再去找另一个点,听起来找完一行上的所有点对似乎需要O(n^2)。不急,我们继续思考,此时问题已抽象成求某两列(设为x和y)特定元素的数目的和(设为h(x)+h(y)),y>x,x与y需要从左往右遍历:惯性思维是x依次遍历,y再从x开始依次遍历。但我们要将“固定x”的角度倒过来,变成“固定y”的角度:y依次遍历,x从1遍历到y。再来看要求的是h(x)+h(y),此时可以发现,当y加1时,x没必要再从1开始,因为上一轮已经遍历过一遍了。即我们只需要从左往右遍历一次,同时记录到截至当前遍历到的元素,每种字符出现的次数以及它们所在列上另外两种字符分别出现次数的累计和——再将问题具象回来,还是以上面的例子为例:

uux
t w j
uuu

在第2行时,当遍历到'w',则记录为't'已经出现了1次,在所有已出现't'所在的列上'j'有0个、'u'有2个;遍历到'j'时,记录加上'j'出现了1次,'j'所在列上't'有0个、'u'有1个。此时由于在'j'之前,有了一个't',则answer加上所有已出现't'的列上‘u’的总数2,并加上当前字符'j'所在列上'u'的个数1与't'已出现的次数1的积。(若该字符前出现过'u',也是类似计算方式)

最终,只需要完整遍历一遍字符矩阵,即可完成对answer的累计。

总的来说

整体思路是,首先遍历一遍矩阵(O(mn)),记录每一列上't'、'j'、'u'的个数(可在输入数据的同时记录),记为l_t[j]、l_j[j]、l_u[j];再遍历一遍矩阵(O(mn)),记录每一行上截至当前遍历到的元素 每种字符出现的次数sum_t、sum_j、sum_u 以及 它们所在列上另外两种字符分别出现次数的累计和sum_t_j、sum_t_u、sum_j_t、sum_j_u、sum_u_t、sum_u_j,将answer加上前述记录进行一定计算后的值(如当前元素为t,则answer+=(sum_j_u+sum_j*l_j[j])+(sum_u_j+sum_u*l_u[j])。sum_j_u或sum_u_j表示竖直直角边在前面的字符j或u所在列上时三角形个数,sum_j*l_j[j]或sum_u*l_u[j]表示竖直直角边在当前元素所在列上时三角形个数)。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int m,n;
        scanf("%d %d",&m,&n);
        vector<string> ju;
        string s;
        int l_t[n],l_j[n],l_u[n];
        memset(l_t, 0, sizeof(l_t));
        memset(l_j, 0, sizeof(l_j));
        memset(l_u, 0, sizeof(l_u));
        for(int i=0;i<m;++i)
        {
            cin>>s;
            ju.push_back(s);

            for(int j=0;j<n;++j)
            {
                if(s[j]=='t'){
                    l_t[j]+=1;
                }else if(s[j]=='j'){
                    l_j[j]+=1;
                }else if(s[j]=='u'){
                    l_u[j]+=1;
                }
            }
        }

        long long ans=0;
        for(int i=0;i<m;++i)
        {
            int sum_t_j=0,sum_t_u=0,sum_j_u=0,sum_j_t=0,sum_u_j=0,sum_u_t=0;
            int tt=0,jj=0,uu=0;
            for(int j=0;j<n;++j)
            {
                if(ju[i][j]=='t'){
                    tt+=1;
                    sum_t_j+=l_j[j];
                    sum_t_u+=l_u[j];
                    if(jj) ans+=sum_j_u+jj*l_u[j];
                    if(uu) ans+=sum_u_j+uu*l_j[j];
                }else if(ju[i][j]=='j'){
                    jj+=1;
                    sum_j_u+=l_u[j];
                    sum_j_t+=l_t[j];
                    if(tt) ans+=sum_t_u+tt*l_u[j];
                    if(uu) ans+=sum_u_t+uu*l_t[j];
                }else if(ju[i][j]=='u'){
                    uu+=1;
                    sum_u_j+=l_j[j];
                    sum_u_t+=l_t[j];
                    if(tt) ans+=sum_t_j+tt*l_j[j];
                    if(jj) ans+=sum_j_t+jj*l_t[j];
                }
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

结语

很遗憾在机试当场这道题是有这个思路,但是忽视了某个细节,一直修改代码最终还是没调出来,喜提AC 0%,大寄特寄。

以上代码是结束后按记忆敲出来,再次调试,最后找到被忽视的细节然后修正的,没有提交过。

也就是说上述代码仅供参考,甚至思路也仅供参考。至于是否能通过所有测试数据,我也不知道。

欢迎各位佬来hack。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值