Fansblog
威尔逊定理 + 质数密度
题目给定一个大质数P,小于P的最大质数为Q,求
Q
!
m
o
d
  
P
Q!\mod P
Q!modP。
p的范围为1e9到1e14,暴力求阶乘取模走不通,发现Q与P都为质数,根据素数密度分布,Q与P差的不是太远…直接暴力找。由威尔逊定理
(
p
−
1
)
!
≡
−
1
m
o
d
  
p
(p-1)!\equiv -1\mod p
(p−1)!≡−1modp
因为p为质数,所以
(
p
−
2
)
!
≡
1
m
o
d
  
p
(p-2)!\equiv1\mod p
(p−2)!≡1modp
所以
Q
!
m
o
d
  
P
=
(
P
−
2
)
!
(
Q
+
1
)
(
Q
+
2
)
.
.
.
(
P
−
2
)
m
o
d
  
P
=
i
n
v
{
(
Q
+
1
)
(
Q
+
2
)
.
.
.
(
P
−
2
)
}
m
o
d
  
P
=
(
Q
+
1
)
p
−
2
(
Q
+
2
)
p
−
2
.
.
.
(
P
−
2
)
p
−
2
m
o
d
  
P
\begin{aligned} Q!\mod P&=\frac{(P-2)!}{(Q+1)(Q+2)...(P-2)}\mod P\\ &=inv\{(Q+1)(Q+2)...(P-2)\}\mod P\\ &=(Q+1)^{p-2}(Q+2)^{p-2}...(P-2)^{p-2}\mod P \end{aligned}
Q!modP=(Q+1)(Q+2)...(P−2)(P−2)!modP=inv{(Q+1)(Q+2)...(P−2)}modP=(Q+1)p−2(Q+2)p−2...(P−2)p−2modP
最后一步是由费马小定理推导出来的,a mod p的逆元为ap-2 mod p(a与p互质)
UPD:判断素数的方法可以用筛法试因子,也可以用米勒拉宾算法。
UPD:1e14最后会爆longlong,可以用慢速乘,也可以用大数。
UPD:发现埃氏筛小数据比欧拉筛快多了
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<cstdlib>
#define ll long long
#define mem(a,x) memset(a,x,sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=1e7+50;
ll prime[maxn];
int visit[maxn];
ll p;
void Euler_Prime(){
for (ll i = 2;i <= maxn; i++)
{
if (!visit[i]) prime[++prime[0]] = i;
for (int j = 1; j <=prime[0] && i*prime[j] <= maxn; j++)
{
visit[i*prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
ll mul(ll a,ll b){
ll s=0;
while (b)
{
if (b&1) s=(s+a)%p;
a=(a+a)%p;
b>>=1;
}
return s%p;
}
ll power(ll x,ll n,ll mod){
ll res=1;
while(n>0){
if(n&1) res=mul(res,x) % mod;
x=mul(x,x) % mod;
n>>=1;
}
return res;
}
int check(ll k){
for(ll i=1;i<=prime[0];i++)
{
ll pnum=prime[i];
if(k%pnum == 0) return 0;
if(pnum*pnum > k) break;
}
return 1;
}
int main(){
ios
Euler_Prime();
int T;
cin>>T;
while(T--)
{
cin>>p;
ll q=p-2;
while(!check(q)) q--;
ll ans=1;
for(ll i=q+1;i<=p-2;i++)
ans=mul(ans,i)%p;
cout<<power(ans,p-2,p)%p<<endl;
}
return 0;
}
Find the answer
权值线段树 + 离散化
给一个数组
{
a
n
}
\{a_n\}
{an},对每个位置
i
i
i,求最少删去多少个数可以使
∑
1
i
−
1
a
k
<
m
\sum_{1}^{i-1}a_k<m
∑1i−1ak<m。
这题很容易想到贪心策略,每次选最大的删去,直到满足前i-1个数的和小于m。
一开始尝试使用堆,复杂度
n
2
l
o
g
n
{n^2logn}
n2logn。
失败后尝试使用线段树。将题目转化为在
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]中选尽量多的数使
∑
a
k
<
m
\sum{a_k}<m
∑ak<m。 每个节点记录区间数的和and数的数量,先排序去重离散化数据,然后动态查询,每次查询后将
a
[
i
]
a[i]
a[i]加入线段树。复杂度
n
l
o
g
n
nlogn
nlogn。
UPD:用树状数组+二分或者STL里multiset也能做。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<cstdlib>
#include<string.h>
#include<iomanip>
#define ll long long
#define Max(a,b) ((a) > (b) ? (a) : (b))
#define INF 0x3f3f3f3f
const int N = 2e5+10;
int a[N],b[N],n,m;
const int mod = 1e9+7;
const int maxn = 1e6 + 10 ;
typedef std::pair<int, int> pii;
typedef std::pair<ll, ll> pll;
ll gcd(ll p,ll q){return q==0?p:gcd(q,p%q);}
using namespace std;
struct node{
int l,r,num;
ll val;
}tree[N<<2];
void build(int l,int r,int id){
tree[id].l=l;
tree[id].r=r;
tree[id].val=tree[id].num=0;
if(l==r)return ;
int mid=(l+r)>>1;
build(l,mid,id<<1);
build(mid+1,r,id<<1|1);
}
int query(int id,int val){
if(tree[id].val<=val)
return tree[id].num;
if(tree[id].l==tree[id].r)
return val/b[tree[id].l]; //val < tree[id].val
if(tree[id<<1].val>=val)
return query(id<<1,val);
else
return tree[id<<1].num+query(id<<1|1,val-tree[id<<1].val);
}
void update(int tar,int id){
if(tree[id].l==tree[id].r)
{
tree[id].val+=b[tar];
tree[id].num++;
return ;
}
if(tar<=tree[id<<1].r) update(tar,id<<1);
else update(tar,id<<1|1);
tree[id].val=tree[id<<1].val+tree[id<<1|1].val;
tree[id].num=tree[id<<1].num+tree[id<<1|1].num;
}
int main(){
int T,pos;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+1+n);
int nn=unique(b+1,b+1+n)-(b+1);
build(1,nn,1);
for(int i=1;i<=n;i++)
{
if(i==1) printf("0 ");
else printf("%d ",i-1-query(1,m-a[i]));
pos=lower_bound(b+1,b+1+n,a[i])-b;
update(pos,1);
}
printf("\n");
}
return 0;
}