java乘法逆元与除法取模_【POI08 PER】模意义下的的除法 | 学步园

取模这种东西到底还是好,将广大OIER从高精度的深渊中解放出来

但是,取模也不总是那么方便的

比方说,我们的操作中不可避免地出现了除法

大部分时候题目会告诉我们取模的数是质数,或者至少除数和取模的数互质,这样我们就能用欧拉定理来解决这个问题了

然而,不幸的事终还是发生了

这道题所要求的取模的数就是任意的~~

题目本身不难,从后往前按位统计+树状数组即可,但由于可重排列公式中不可避免地出现了除法,怎么办?

盾哥从这个题的标程中抠出了下面这个不太优美的方法

先将要取模的数分解质因数,然后每次要乘或要除一个数的时候,就将那个数的质因子中跟取模的数的相同的提出来,这部分因子“暴力”(就是有除法就直接约分,要用的时候再快速幂算值),另外的部分就和要取模的数互质了,可以直接求其乘法逆元

由于一个数的质因数个数是log级别的,所以通过一大堆预处理可以让取模过程均摊O(logN)

可是,

我想了一个比较有效的常数优化,就是算一个数的乘法逆元可以“记忆化”,加入这个优化程序能快上将近一倍

感觉算法不太优美,求更漂亮的实现~~

代码:

program syj;

const

maxn=300005;

type

arr=array[0..43]of longint;

var

n,max,i,j,k,phm,mo,dt:longint; s,ans:int64;

a,b,c,sh,ne,u:array[0..maxn]of longint;

ph:array[0..maxn]of longint;

d:array[0..43]of longint;

z:array[0..43,0..maxn]of longint;

f,g:arr;

procedure update(i:longint);

begin

while i<=max do begin

inc(b[i]);inc(i,i and-i);

end;

end;

procedure ask(i:longint;var s:longint);

begin

s:=0;

while i>0 do begin

inc(s,b[i]);dec(i,i and-i);

end;

end;

function calc(i:longint):longint;

procedure mul(n:longint);

begin

if n=1 then ph[i]:=i else begin

mul(n>>1);

ph[i]:=int64(ph[i])*ph[i] mod mo;

if odd(n) then ph[i]:=int64(ph[i])*i mod mo;

end;

end;

begin

if ph[i]>0 then exit(ph[i]);

mul(phm);

calc:=ph[i];

end;

function work(i,j:longint;var f:arr):int64;

var k:longint;

begin

if i=0 then exit(0);

work:=1;

while i>1 do begin

k:=sh[i];

if u[k]>0 then inc(f[u[k]],j) else

if j>0 then work:=work*k mod mo

else work:=work*calc(k) mod mo;

i:=ne[i];

end;

end;

begin

assign(input,'per.in');reset(input);

assign(output,'per.out');rewrite(output);

readln(n,mo);

k:=mo;phm:=1;

for i:=2 to trunc(sqrt(k)) do

if k mod i=0 then begin

inc(dt); d[dt]:=i; u[i]:=dt; j:=1;

while k mod i=0 do begin

k:=k div i;j:=j*i;

end;

phm:=phm*(j div i*(i-1));

end;

if k>1 then phm:=phm*(k-1);

dec(phm,1);

if (k>0)and(k<=n) then begin

inc(dt);d[dt]:=k;u[k]:=dt;

end;

for i:=1 to dt do begin

z[i,0]:=1;

for j:=1 to n do z[i,j]:=int64(z[i,j-1])*d[i] mod mo;

end;

for i:=1 to n do begin

read(a[i]);

if a[i]>max then max:=a[i];

end;

if n>max then max:=n;

for i:=2 to max do

for j:=1 to max div i do

if sh[i*j]=0 then begin

sh[i*j]:=i; ne[i*j]:=j;

end;

s:=1; ans:=1;

for i:=n downto 1 do begin

ask(a[i]-1,k);

fillchar(g,sizeof(g),0);

j:=work(k,1,g);

inc(c[a[i]]);

s:=s*work(c[a[i]],-1,f) mod mo;

j:=s*j mod mo;

if j>0 then

for k:=1 to dt do

j:=int64(j)*z[k,g[k]+f[k]] mod mo;

inc(ans,j);

update(a[i]);

s:=s*work(n-i+1,1,f) mod mo;

end;

writeln(ans mod mo);

close(input);close(output);

end.

速度比盾哥快,哈哈~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值