两道(疑似)哈希的题

本文讨论了EOJ登录系统中关于密码碰撞的问题,其中如果一个密码是另一个密码的子串,就能成功登录。针对这个问题,提出了通过枚举子串并使用哈希统计的方法来计算出可能的登录对。此外,还介绍了新生训练中的一道斐波那契数列题目,由于数列的数值过大,需要通过哈希技巧来解决超出常规数据类型范围的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2018.2.5 新生训练Week3 E.密码碰撞


EOJ 的登录系统爆出了一个重大问题,当正确的密码是你输入的密码的子串时,就可以成功登录!

例如你的密码是 abc,则你输入 abcc,aabc,甚至 dfjklsdfabcsdjfkl,都可以成功登录!

出现了这么大的问题,那就一定要有人来背锅,管理员们希望在背锅之前先衡量一下锅的大小。

现在有一份 EOJ 用户的密码表,里面包含了 n 个用户的密码,第 i 个用户的密码是 pwdi。我们定义锅的大小为所有有序对 (i,j) (i≠j) 的数量,使得用户 i 能够输入他的密码 pwdi 成功登陆用户 j 的账户。

换句话说,我们现在需要知道,有多少有序对 (i,j) (i≠j) 使得 pwdj 是 pwdi 的子串。

第 1 行包含一个整数 n,1≤n≤20 000,表示密码表中密码的数量。
第 1+i (1≤i≤n) 行包含一个长度不超过 10 且由小写字母组成的字符串,表示 pwdi。

说明


因为长度太短了,所以可以直接枚举子串(每个密码最多55个子串),hash一下存进map里统计子串的出现次数。然后对于每个密码,计算其在子串中出现的次数。记得要减去n,因为每个密码也一定是自己的子串。

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

const int maxn = 20005;
typedef long long ll;
ll SS[maxn];
set<ll> S[maxn];
map<ll, int> m;
char s[15];
int n, ans, len;

int main()
{
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        scanf("%s", s);
        len = strlen(s);
        for (int j = 0; j < len; ++j)
        {
            ll h = 0;
            for (int k = j; k < len; ++k)
            {
                h = h * 29 + s[k]-'a'+1;
                S[i].insert(h);
                if (!j && k+1 == len) SS[i] = h;
            }
        }
        for (auto x: S[i]) ++m[x];
    }
    for (int i = 0; i < n; ++i)
        ans += m[SS[i]];
    printf("%d\n", ans-n);
    return 0;
}

2018.2.12 新生训练Week4 D.斐波那契数列


有一个数列 AnAn,其中 A1=1,A2=2,An+2=An+1+AnA1=1,A2=2,An+2=An+1+An
给你一个数字,问他是这个数列的第几项。
每行包括数列中的一项 AkAk (k≤100000)。
总行数 T≤100。

说明


看到标题以为是很水的题……
实际上,斐波那契数列的100000项是一个超出long long范围的数,因此一开始考虑用高精度。后来发现数据加强了,时限和内存又比较紧(卡掉了我的python预处理算法&&python滚动暴力算法&&C++高精度+二分查找算法),只能使用一些技巧。
类似hash,用一个大质数(比如19260817)将斐波那契数列的各项hash掉,再利用同余定理查找答案,就做完了……?

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

const int maxn = 1e5+5;
const int p = 19260817;
int now, f[maxn];
map<int, int> m;
string s;

int main()
{
    f[0] = f[1] = 1;
    m[1] = 1;
    for (int i = 2; i < maxn; ++i)
    {
        f[i] = (f[i-1] + f[i-2]) % p;
        m[f[i]] = i;
    }
    while (cin >> s)
    {
        now = 0;
        for (auto &i: s)
            now = (now*10 + i-'0') % p;
        printf("%d\n", m[now]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值