[UOJ 74][UOJ Round #6]破解密码(乘法逆元)

题目链接

http://uoj.ac/problem/74

思路

不妨将这个字符串看成是一个26进制的数字 abc...k
26n1a+X=h0 mod p...(1)
26X+a=h1 mod p...(2)

26(1)(2)
26naa=(26h0h1) mod p
a=(26h0h1)modp26n1

继续手推发现对于任意的 hi,hi+1 均满足
t=(26hihi+1)modp26n1 ,其中 t 是旋转了 i 次后的26进制数的个位。

只要用乘法逆元便可很容易在 O(n) 时间推出这个数字的每一位。

但是要注意到,做乘法逆元之前一定要注意分数的分母不为0,而此题尽管保证一定有解,但是没有保证 26n mod p=1 ,因此要特判一下(比赛时几乎所有人没有注意到这一点,大部分人因此只拿到了50分),当 26n mod p=1 时,不能做乘法逆元,此时对于 hi 来说, hi+1 的值只和 hi 有关,与旋转 i 次后个位的字母无关,此时只需要构造一个长度为 n 的26进制数字并满足其模 ph0 即可,这个比较容易。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 210000

using namespace std;

typedef long long int LL;

LL h[MAXN],pow[MAXN];
int n;
LL p;

LL extGCD(LL a,LL b,LL &x,LL &y) //ax+by=1
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    LL tmp=extGCD(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-(a/b)*y;
    return tmp;
}

LL rev(LL a,LL b) //求模b意义下a的逆元x,ax=1(p b),b是质数所以可以求逆元(gcd(a,b)=1)
{
    LL x,y;
    extGCD(a,b,x,y);
    x=(x%b+b)%b;
    return x;
}

LL ans[MAXN];

int main()
{
    scanf("%d%lld",&n,&p);
    pow[0]=1;
    for(int i=1;i<=2*n;i++)
        pow[i]=(pow[i-1]*26)%p;
    for(int i=1;i<=n;i++)
        scanf("%lld",&h[i]);
    if(pow[n]==1)
    {
        for(int i=1;i<=n;i++)
            ans[n-i+1]=(h[1]%26),h[1]/=26;
        for(int i=1;i<=n;i++)
            printf("%c",(int)ans[i]+'a');
        printf("\n");
        return 0;
    }
    for(int i=1;i<n;i++)
        ans[i]=((((h[i]*26)%p-h[i+1]+p)%p)*rev(pow[n]-1,p))%p;
    ans[n]=((((h[n]*26)%p-h[1]+p)%p)*rev(pow[n]-1,p))%p;
    for(int i=1;i<=n;i++)
        printf("%c",((int)ans[i]+'a'));
    printf("\n");
    return 0;
}

你可能感兴趣的:([UOJ 74][UOJ Round #6]破解密码(乘法逆元))