正睿OI游记(Day 0x05) 数论赛

Day 0x05 数论赛

再次自闭

开题顺序3,2,1。

写完T2后发现T3锅了,然后重写。

T1暴力没打完。

竟然卡进了前十QwQ。(涨了100+)

T2

大凯的疑惑

给定\(a,b\),求使\(ax+by=c\)没有非负整数\((x,y)\)解的前\(k\)大的正整数\(c\)

保证\(40\%\)的数据有\(a,b \leq 1000\)

保证另外\(50\%\)的数据有\(k \leq 10^5\)

保证\(100\%\)的数据有\(a,b \leq 10^9, k\leq 5 \times 10^6, (a,b)=1\)

40pts做法:

暴力跑一个背包,跑到\(ab-a-b\)

扫描二维码关注公众号,回复: 6831536 查看本文章

从大往小枚举异或即可

90pts做法:

用上面的背包打表发现,

一个数不能被表示出来\(\iff​\)它一定是\(ab-a-b-ua-vb(u,v\in N)​\) 形式.

不会证明

(口胡一个证明,

首先\(ab-a-b\)不能被表示出来,

然后如果一个数\(x​\)不能被表示出来,\(x-a​\),\(x-b​\)也不能被表示,

所以必要性显然,

充分性,

因为\((a,b)=1\)所以任何数都有整数\((x,y)\) 解,

所以某不能被表示出来的\(c=ax_0+by_0\)\((x_0,y_0\in Z)\)

只要证必有一个\(x_0,y_0\)小于0即可.

因为假设两种钱每种最少要拿一次(也就是不能不拿),那么不能凑成的最大钱数 \(k=a\times b\) 证明康这里

所以\(c=ab-(u+1)a-(v+1)b\)必有\(x_0或y_0<0\)

)

所以考虑把\(ab-a-b\)塞到一个set里,不断取出最大值,\(-a,-b\)后再塞入set里,取k次即可.

复杂度\(O(k\ \log\ k)\)

期望得分90,实际得分100??

100pts做法

回忆一下noip2016蚯蚓

考虑开两个队列,一个是操作\(-a\)之后的,一个是操作\(-b\)之后的.

每次取两队首进行取较大的进行操作.

注意:为了防止重复,\(-a\)队列里的可向\(-b\)内转移,但\(-b\)后的不能向\(-a​\)里转移.

给出期望90实际100的代码.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
set<ll > s;
ll a,b,k;
ll ans;
int main(){
    scanf("%lld%lld%lld",&a,&b,&k);
    s.insert(-(a*b-a-b));
    ll cnt=1;
    while(!s.empty()&&cnt<=k){
        cnt++;
        ll x=-*s.begin();
        ans^=x;
        s.erase(s.begin());
        if(x>a)s.insert(a-x);
        if(x>b)s.insert(b-x);
        
    }
    printf("%lld",ans);


    return 0;
}

给出杜教的(期望)100代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)

const int N=10100000;
ll a,b,q1[N],q2[N],ans;
int k;

int main() {
    scanf("%lld%lld%d",&a,&b,&k);
    assert(gcd(a,b)==1);
    if (a>b) swap(a,b);
    ll w=a*b-a-b;
    int h1=0,t1=1,h2=0,t2=0;
    q1[0]=w;
    rep(i,0,k) {
        if (h1==t1&&h2==t2) break;
        if (h2==t2||(h1!=t1&&q1[h1]>q2[h2])) {
            ll z=q1[h1++];
            ans^=z;
            if (z-a>0) q1[t1++]=z-a; 
            if (z-b>0) q2[t2++]=z-b; 
        } else {
            ll z=q2[h2++];
            ans^=z;
            if (z-b>0) q2[t2++]=z-b;
        }
    }
    printf("%lld\n",ans);
}

T3

一开始一个\(1\)\(n\)排列中的所有元素都是不确定的,现在我们要逐个确定下来\(q\)个元素的位置\(p_u=v\)

我们想知道每次给定的元素确定下来的之后,有多少种不同的错排.

保证\(100\%\)的数据有\(n\leq 5 \times 10^3, q\leq n\),保证\(u\neq v\),并且所有的\(u\)\(v\)是两两不同的。

做法

将每个\(p_u\)\(v\)连一条有向边,问题转化为有多少种图满足没有自环.

连成的图一定是若干个环形成的.

元素确定下来,每一次连边都会形成一条链.

若形成一个环,那么这个环可以直接去掉对答案没有影响.

假设目前已经有\(a\)条单链,\(b\)条个单点.

在这里,链和链是本质相同的,和单点本质不同.

因为点不能和自己连边,而一条链可以自闭成一个环.

  1. 考虑容斥,枚举有至少多少个单点连向自己.
    \(ans=\sum\limits_{i=0}^b(-1)^b\dbinom{b}{i}\cdot (a+b-i)!\)
  2. 枚举有多少环自闭了.
    \(ans=\sum\limits_{i=0}^a\dbinom{a,i}D(a+b-k)\)

给出方法二的代码实现

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5007;
const ll mod=1e9+7;

ll n,q;
ll f[maxn],g[maxn],d[maxn];
bool va[maxn],vb[maxn];
ll qpow(ll a,ll b){
    ll rt=1;
    while(b){
        if(b&1)rt=(rt*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return rt;
}
ll p(ll n,ll m){
    if(n<m)return 0;
    if(m==0)return 1;
    return f[n]*g[n-m]%mod;
}
ll C(ll n,ll m){
    if(n<m)return 0;
    if(n==m||m==0)return 1;
    return f[n]*g[m]%mod*g[n-m]%mod;
}
int main(){
    f[0]=1;
    for(int i=1;i<=5000;i++)f[i]=f[i-1]*i%mod;
    g[5000]=qpow(f[5000],mod-2);
    for(int i=5000;i>=1;i--)g[i-1]=g[i]*i%mod;
    d[0]=1,d[1]=0;
    for(int i=2;i<=5000;i++)d[i]=(i-1)*(d[i-1]+d[i-2])%mod;
    scanf("%lld%lld",&n,&q);
    printf("%lld\n",d[n]%mod);
    ll a=0,b=n;
    for(int i=1;i<=q;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        if(vb[u]&&va[v])a--;
        else if(vb[u]&&!va[v])b--;
        else if(!vb[u]&&va[v])b--;
        else a++,b-=2;
        va[u]=true,vb[v]=true;
        ll ans=0;
        for(int j=0;j<=a;j++){
            ans=(ans+C(a,j)%mod*d[a+b-j]%mod)%mod;
        }
        printf("%lld\n",ans);
    }


    return 0;
}

T1

\(T\)组数据,求\(a^x \equiv b \pmod {p^e}\)的最小非负整数解。

对于\(20\%\)的数据,保证 \(1 \leq T \leq 10, p^e \leq 10^5\)

对于\(50\%\)的数据,保证 \(1 \leq T \leq 10, p^e \leq 3^{21}\)

对于另外\(20\%\)的数据,保证 \(1 \leq T \leq 1000, p^e \leq 3^{21}\),并且所有的\(a, p, e\)都是相同的。

对于\(100\%\)的数据,保证\(1 \leq T \leq 1000, 1\leq a, b \leq p^e-1, p \nmid a, p \nmid b, 3\leq p\leq 50, e \geq 1, p^e \leq 10^{18}, p\)是质数。

20pts

暴力枚举到\(\phi(p^e)\)即可

50pts

因为\(p \nmid a, p \nmid b\)

直接BSGS即可

70pts

在50pts基础上

只预处理一次hash表即可.

块大小开(O(\sqrt{\dfrac{p}{n}}))即可

100pts

因为\(p\in prime\) \(g_{p^e}=g_p\)

求出原根\(g\)

对方程两边取\(g\)的对数

得到\(\log_g{a}x\equiv\log_g{b}(mod \ p^e)\)

问题转化为求\(\log_g(k)​\)在来次扩欧

即求 \(g^X\equiv k(mod \ p^e)​\)

\(g^X \equiv k(mod\ p)​\)

得到\(X\equiv X_0(mod \ p-1)​\)

又因为\(g^X \equiv k(mod\ p^2)\implies g^X \equiv k(mod\ p)​\)

所以枚举\(X\equiv X_0+|_{i=0 \to p}(p-1)*i(mod \ p(p-1))\)

令解为\(X_1​\)

一直像这样递推到\(X_e​\)即可

给出代码

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int prime[14]={3,5,7,11,13,17,19,23,29,31,37,41,43,47};
const int root[14]={2,2,3,2,2,3,2,5,2,3,2,6,3,5};
ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!b) return x=1,y=0,a;
    else{
        ll rt=exgcd(b,a%b,y,x);
        y-=(a/b)*x;
        return rt;      
    }
}
inline ll mul(ll x,ll y,ll p){
    ll k=(ll)((1.0l*x*y)/(1.0l*p)),t=x*y-k*p;
    t-=p;while(t<0)t+=p;
    return t;
}

ll qpow(ll a,ll b,ll p) {
    ll rt=1;
    while(b){
        if(b&1)rt=mul(rt,a,p);
        a=mul(a,a,p);
        b>>=1;
    }
    return rt;
}

ll findg(ll g,ll a,ll p,ll e){
    ll mod=p,step=1,ans=0;
    for(int i=0;i<e;i++){
        ll now=qpow(g,ans,mod);
        ll base=qpow(g,step,mod);
        ll to=a%mod;
        while(now!=to){
            now=mul(now,base,mod);
            ans+=step;
        }
        if(step==1)step=p-1;
        else step*=p;
        mod*=p;
    }
    return ans;
}

void solve(){
    ll a,b,p,e,rt;
    scanf("%lld%lld%lld%lld",&a,&b,&p,&e);
    for(int i=0;i<14;i++)if(prime[i]==p)rt=root[i];
    a=findg(rt,a,p,e);
    b=findg(rt,b,p,e);
    p=(p-1)*qpow(p,e-1,LLONG_MAX);
    ll x,y;
    ll gcd=exgcd(a,p,x,y);
    if(b%gcd){
        printf("-1\n");
        return ;
    }
    x=mul(x,b/gcd,p);
    p/=gcd;
    x=(x%p+p)%p;
    printf("%lld\n",x);
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        solve();
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/pmt2018/p/11222602.html
今日推荐