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

匿名 (未验证) 提交于 2019-12-02 23:49:02

再次自闭

开题顺序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\)

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

从大往小枚举异或即可

用上面的背包打表发现,

一个数不能被表示出来它一定是 形式.

不会证明

(口胡一个证明,

首先\(ab-a-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??

回忆一下noip2016蚯蚓

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

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

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

给出期望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)\)

问题转化为求在来次扩欧

即求

得到

又因为

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

令解为

一直像这样递推到即可

给出代码

#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; }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!