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\)
从大往小枚举异或即可
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\)条个单点.
在这里,链和链是本质相同的,和单点本质不同.
因为点不能和自己连边,而一条链可以自闭成一个环.
- 考虑容斥,枚举有至少多少个单点连向自己.
\(ans=\sum\limits_{i=0}^b(-1)^b\dbinom{b}{i}\cdot (a+b-i)!\) - 枚举有多少环自闭了.
\(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;
}