2019杭电多校第三场

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 (p1)!1modp
因为p为质数,所以 ( p − 2 ) ! ≡ 1 m o d    p (p-2)!\equiv1\mod p (p2)!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)...(P2)(P2)!modP=inv{(Q+1)(Q+2)...(P2)}modP=(Q+1)p2(Q+2)p2...(P2)p2modP

最后一步是由费马小定理推导出来的,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 &lt; m \sum_{1}^{i-1}a_k&lt;m 1i1ak<m
这题很容易想到贪心策略,每次选最大的删去,直到满足前i-1个数的和小于m。
一开始尝试使用堆,复杂度 n 2 l o g n {n^2logn} n2logn
失败后尝试使用线段树。将题目转化为在 [ 1 , i − 1 ] [1,i-1] [1,i1]中选尽量多的数使 ∑ a k &lt; m \sum{a_k}&lt;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;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值