您的位置:首页 > 其它

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

2015-03-09 10:05 204 查看

题目链接

http://uoj.ac/problem/74

思路

不妨将这个字符串看成是一个26进制的数字abc...kabc...k

26n−1a+X=h026^{n-1}a+X=h_0 modmod p...(1)p...(1)

26X+a=h126X+a=h_1 modmod p...(2)p...(2)

26∗(1)−(2)26*(1)-(2)得

26na−a=(26h0−h1)26^na-a=(26h_0-h_1) modmod pp

a=(26h0−h1)modp26n−1a=\frac{(26h_0-h_1)modp}{26^n-1}

继续手推发现对于任意的hi,hi+1h_i,h_{i+1}均满足

t=(26hi−hi+1)modp26n−1t=\frac{(26h_i-h_{i+1})modp}{26^n-1},其中tt是旋转了ii次后的26进制数的个位。

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

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

代码

#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
==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
-1,p))%p;
ans
=((((h
*26)%p-h[1]+p)%p)*rev(pow
-1,p))%p;
for(int i=1;i<=n;i++)
printf("%c",((int)ans[i]+'a'));
printf("\n");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: