文章

P4503 [CTSC2014] 企鹅 QQ

P4503 [CTSC2014] 企鹅 QQ

Link:Luogu

P4503 [CTSC2014] 企鹅 QQ

题目背景

PenguinQQ 是中国最大、最具影响力的 SNS(Social Networking Services)网站,以实名制为基础,为用户提供日志、群、即时通讯、相册、集市等丰富强大的互联网功能体验,满足用户对社交、资讯、娱乐、交易等多方面的需求。

题目描述

小 Q 是 PenguinQQ 网站的管理员,他最近在进行一项有趣的研究——哪些账户是同一个人注册的。经过长时间的分析,小Q发现同一个人注册的账户名称总是很相似的,例如 Penguin1,Penguin2,Penguin3……于是小 Q 决定先对这种相似的情形进行统计。

小 Q 定义,若两个账户名称是相似的,当且仅当这两个字符串等长且恰好只有一位不同。例如“Penguin1”和“Penguin2”是相似的,但“Penguin1”和“2Penguin”不是相似的。而小 Q 想知道,在给定的 $n$ 个账户名称中,有多少对是相似的。

为了简化你的工作,小Q给你的N 个字符串长度均等于L ,且只包含大小写字母、数字、下划线以及‘@’共64种字符,而且不存在两个相同的账户名称。

输入格式

第一行包含三个正整数 $N,L,S$。其中 $N$ 表示账户名称数量,$L$ 表示账户名称长度,$S$ 用来表示字符集规模大小,它的值只可能为 $2$ 或 $64$。

若 $S$ 等于 $2$,账户名称中只包含字符 01 共 $2$ 种字符;

若 $S$ 等于 $64$,账户名称中可能包含大小写字母、数字、下划线以及 @ 共 $64$ 种字符。

随后 $N$ 行,每行一个长度为 $L$ 的字符串,用来描述一个账户名称。数据保证 $N$ 个字符串是两两不同的。

输出格式

仅一行一个正整数,表示共有多少对相似的账户名称。

输入输出样例 #1

输入 #1

1
2
3
4
5
4 3 64
Fax
fax
max
mac

输出 #1

1
4

说明/提示

$4$ 对相似的字符串分别为:Fax 与 fax,Fax 与 max,fax 与 max,max 与 mac。

测试点编号$N$$L$$S$
$1$$50$$10$$64$
$2$$500$$100$$64$
$3$$3000$$100$$2$
$4$$3000$$100$$64$
$5$$30000$$50$$2$
$6$$30000$$50$$64$
$7$$30000$$200$$2$
$8$$30000$$200$$64$
$9$$30000$$200$$2$
$10$$30000$$200$$64$

解析

这个题看着很简单,但是想都不用想,暴力绝对会超时。 联想一下,我们一般怎么检测一个字符串是重复的呢?哈希表! 所以我们可以有一个思路:即我们去除字符串的特定位置的字符,然后对剩余的字符串做哈希计算

怎么计算$Hash$值?
$h(s) = \sum_{i = 1}^{n} s[i] \times p^{n - i} \mod M$
例如字符串$abc$,其$Hash$值为 $ap^2 + bp^1 +cp^0$

我们这里运用前缀和来进行辅助,得到去除特定位置字符的字符串的哈希值

1
2
3
int pre = ha[j][i - 1];
int suf = ha[j][l] - ha[j][i] * pw[l - i];
t[j] = pre * pw[l - i] + suf;

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const int N = 3e4 + 5;
const int M = 205;
const int p = 2333;

int ha[N][M], t[N], pw[M];
// ha[N][M] 每一位的哈希值 t[N] 中间数组 pw[M] 幂次方表

void solve()
{
    int n, l, s; cin >> n >> l >> s;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= l; j++)
        {
            char c; cin >> c;
            ha[i][j] = ha[i][j - 1] * p + c;
            // 滚动计算哈希值
        }
    }

    pw[0] = 1;
    for (int i = 1; i <= l; i++) pw[i] = pw[i - 1] * p;
    // 初始化幂表

    int ans = 0;
    for (int i = 1; i <= l; i++)
    {// 列举字符位置
        for (int j = 1; j <= n; j++)
        {// 列举字符串
            int pre = ha[j][i - 1];
            int suf = ha[j][l] - ha[j][i] * pw[l - i];
            t[j] = pre * pw[l - i] + suf;
        }
        sort(t + 1, t + n + 1); 
        // 排序,方便后续统计
        int cnt = 1;
        for (int j = 1; j < n; j++)
        {
            if (t[j] == t[j + 1])
            {
                ans += cnt;
                cnt++;
            }
            else cnt = 1;
        }
    }
    cout << ans << endl;
}
本文由作者按照 CC BY 4.0 进行授权