集训做题集合(未完...)
前言:
基于我的数论烂得没边,所以 (全部) 部分题目会借鉴大佬博客,但是由于今天的网页崩了\(inf\)次,所以我压根找不到到底看的是哪位大佬的博客了。所以,如果您在下面的解析或代码中看到了自己的身影,请告诉我一声哦~~
Weird LCM Operations
思路:
显然,前一半的数都作为最大公约数出现过,那儿我们只需要考虑后一半。题目要求操作数不大于\(\ \lfloor \frac{n}{6}\ \rfloor\)这提示我们需要将后一半没三个分为一组,即\(x-1,x,x+1\)分为一组。若这三个数互质,则这三个数可以作为子序列的最大公约数出现。那么什么情况下这三个数互质呢?当\(x\)为偶数的时候。那\(x\)为奇数的时候我们怎么办呢?(凉拌西红柿炒鸡蛋) 所以我们不能每三个分为一组。既然三个不行,那四个呢?我们发现,每四个数中的前第三个或者后三个会满足要求。那我们就把每四个分为一组,满足条件的三个拿走,剩下的那个留着下一次用。通过手模我们可以发现剩下的数为一个等差数列且差值为\(4^{i-1}(第i次循环)\)。最后,如果剩下一个数,那就让它和1,2交换。剩下两个就和1交换。剩下三个......什么玩意剩下三个,剩下三个肯定为一组啊。
代码:
$code$
#include<iostream>
#include<vector>
#include<array>
using namespace std;
const int N=3e5+5;
vector<int> v,vc;vector<array<int,3>> res;bool vis[N];int T,n;
int main(){
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cin>>n;
res.clear();v.clear();
for(int i=n/2+1;i<=n;i++) v.push_back(i);//后一半需要处理
int now=1;//差值
while(v.size()>=3){
vc.clear();//暂存器
for(int i=0;i<v.size();i++) vis[i]=0;//清空标记
for(int i=(v.back()%(now*2)?v.size()-1:v.size()-2);i>=0;i-=4){//从奇/偶数开始
res.push_back({v[i],v[i]-now,v[i]-now*2});//答案存起来
vis[i]=1;
if(i) vis[i-1]=1;
if(i>1) vis[i-2]=1;//标记
}
for(int i=0;i<v.size();i++) if(!vis[i]) vc.push_back(v[i]);//把没处理的挑出来
v=vc;now*=4;//接着放回去准备处理 差值*4
}
if(v.size()==1) res.push_back({1,2,v[0]});//剩一个
if(v.size()==2) res.push_back({1,v[0],v[1]});//剩两个
cout<<res.size()<<'\n';
for(auto x:res) cout<<x[0]<<" "<<x[1]<<" "<<x[2]<<'\n';
}
return 0;
}
鬼街:
借鉴自大佬
折半报警器。(其实到现在也没完全学会👉👈)
我们需要安装阈值为\(y\)监控器来监控\(k\)个房子的闹鬼事件。不难发现 (其实有点难),至少有一个房子的闹鬼次数大于等于\(\lceil \frac{y}{k} \rceil\)是监控器响的充分条件。那我们不妨给该监控器监视的每一个房子装一个小监控器,它的阈值为\(\lceil \frac{y}{k} \rceil\),当有房子满足这一条件时再查看是否监控器能响。但是呢,此时的代码还是有点过于劣了。怎么考虑优化捏?对于每个到达小报警器阈值但是报警器没有响的房子,我们可以更新这个房子对应的报警器以此来达到优化的效果。再有就是,每一个报警器响过以后就不要了(题目好像没太说明),我们可以打一个时间戳来解决这个问题。
代码:
$code$
#include<iostream>
#include<queue>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+5;
int m,n,np[N],seq[N],op,x,y,last,lim[N],lst[N],tim[N],sum[N],tot[N],idx;
vector<int> pri,pos[N],ans;
struct node{
int B,time,id;//阈值 时间戳 监控器编号
bool operator<(const node &x)const{return B>x.B;}
};priority_queue<node> q[N];
inline void ins(int x){
tim[x]++;lst[x]=0;
for(auto y:pos[x]){
q[y].push({(lim[x]+tot[x]-1)/tot[x]+sum[y],tim[x],x});
lst[x]+=sum[y];
}
}
inline void add(int x,int k){
sum[x]+=k;//闹鬼次数加和
while(!q[x].empty()){
auto y=q[x].top();
if(y.time<tim[y.id]) q[x].pop();//过时了
else if(y.B<=sum[x]){//到达小监控器阈值
int t=y.id;//监控器编号
q[x].pop();
lim[t]+=lst[t];
for(auto v:pos[t]) lim[t]-=sum[v];//后面解释这两步
if(lim[t]<=0) ans.push_back(t),tim[t]=1e9;//达到监控器阈值
else ins(t);//更新监控器
}else break;
}
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=2;i<=n;i++){
if(!np[i]) pri.push_back(i),seq[i]=i;
for(auto j:pri){
if(i*j<=n){
np[i*j]=1;seq[i*j]=j;
if(i%j==0) break;
}else break;
}
}//预处理分解质因数
while(m--){
cin>>op>>x>>y;y=y^last;
if(op==0){
while(x>1){
int c=seq[x];add(c,y);
while(x%c==0) x/=c;
}sort(ans.begin(),ans.end());
cout<<(last=ans.size())<<' ';
for(int i=0;i<ans.size();i++) cout<<ans[i]<<' ';cout<<'\n';
ans.clear();
}else{
++idx;//给监控器编号
if(!y){
ans.push_back(idx);
continue;
}lim[idx]=y;//阈值为y
while(x>1){
int c=seq[x];pos[idx].push_back(c);//监控的房子
while(x%c==0) x/=c;
}tot[idx]=pos[idx].size();ins(idx);//tot为监控房子的数目
}
}
return 0;
}
解释:
\(lst\)与\(sum\)之间只差了\(x\)新增的\(k\),用原来的阈值减去这部分新增的,把剩下的阈值在均摊给每一个房子,这样就达到优化的效果了。
ConstructOR
\(CF\)崩了,代码交不上去,不知道对不对,就不要误导后人了吧(先把代码存这)
关于关了同步输入流使用puts导致调了一早上这件事......
$code$
#include<iostream>
#define int long long
using namespace std;
int T,a,b,d,a1,b1,d1,x;
signed main(){
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cin>>a>>b>>d;
x=0;
for(int i=0;i<=31;i++){
if((a>>i)&1){
a1=i;
break;
}
}for(int i=0;i<=31;i++){
if((b>>i)&1){
b1=i;
break;
}
}for(int i=0;i<=31;i++){
if((d>>i)&1){
d1=i;
break;
}
}
if(a1<d1||b1<d1){
cout<<"-1"<<'\n';
continue;
}
for(int i=d1;i<30;i++) if(!((1<<(i-d1))&x)) x+=((d/(1<<d1))<<(i-d1));
cout<<x*(1<<d1)<<'\n';
}
return 0;
}
SEJ-Strongbox
思路:
由这个规律对 \(x=y\) 也同样适用可得,若\(x\)是密码,那么\(2x,3x,...,kx\)都是密码,所以\(x\)是\(n\)的因数。又因为第\(k\)次的密码是正确的,所以\(x\)也是\(m[k]\)的因数。所以\(x\)是\(gcd(n,m[k])\)的因数。那么我们先把\(gcd(n,m[k])\)的所有因数求出来,然后离线操作。由上述性质的反面可得,若一个数不是密码,那它的所有因数也都不是密码。由此,我们可以从前\(k-1\)次错误密码入手,逐渐筛选掉不合法的数。最后因为合法的数为一个等差数列,差值为队首元素的值,所以只要用\(n\)除以队首元素的值就好了。
代码:
$code$
#include<iostream>
#include<cmath>
#include<set>
#define int long long
using namespace std;
const int N=2.5e5+5;
int n,k,m[N],cnt,p[N];set<int> s;
inline int gcd(int x,int y){
if(!y) return x;
return gcd(y,x%y);
}//求最大公约数
inline void del(int x){
if(s.find(x)==s.end()) return ;
s.erase(x);
for(int i=1;i<=cnt;i++) if(x%p[i]==0) del(x/p[i]);
}//删去不合法的数
signed main(){
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=k;i++) cin>>m[i];
int w=gcd(n,m[k]),y=w;
for(int i=2;i*i<=w;i++){
if(y%i) continue;
p[++cnt]=i;
while(y%i==0) y/=i;
}if(y!=1) p[++cnt]=y;//分解所有的因数
int x=sqrt(w);
for(int i=1;i<=x;i++){
if(w%i) continue;
s.insert(i);s.insert(w/i);//插入因数
}
for(int i=1;i<k;i++) del(gcd(w,m[i]));//删除
cout<<n/(*s.begin())<<'\n';
return 0;
}
古代猪文
思路:
借鉴自大佬文章
大佬写的还是很清楚的,不过补一嘴,\(Case~2\)的那个式子转化利用的是欧拉定理的推论,详情见这里
代码:
$code$
#include<iostream>
#include<cmath>
#define int long long
const int N=36000,mod=999911659;
int n,g,m[]={0,2,3,4679,35617},M[5],invm[5],ans,sum[5];
inline int qpow(int x,int y,int mod){
int res=1;
while(y){
if(y&1) res=(res*x)%mod;
x=x*x%mod;
y>>=1;
}return res;
}//快速幂
struct node{
int mod,fac[N],inv[N];
inline void pre(){
fac[0]=inv[0]=1;
for(int i=1;i<mod;i++) fac[i]=fac[i-1]*i%mod;
inv[mod-1]=qpow(fac[mod-1],mod-2,mod);
for(int i=mod-2;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}//组合数预处理
inline int C(int n,int m){
if(n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m];
}//组合数
inline int lucas(int n,int m){
if(!m) return 1;
return C(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}//Lucas定理
}S[5];
using namespace std;
signed main(){
ios::sync_with_stdio(false);
cin>>n>>g;
if(g==mod){
puts("0");
return 0;
}//一定要特判!
for(int i=1;i<=4;i++) S[i].mod=m[i],S[i].pre();//预处理
for(int i=1;i<=sqrt(n);i++){
if(n%i==0){
for(int j=1;j<=4;j++){
sum[j]=(sum[j]+S[j].lucas(n,i))%mod;
if(i!=sqrt(n)) sum[j]=(sum[j]+S[j].lucas(n,n/i))%mod;//防止一个数算两遍
}
}
}
for(int i=1;i<=4;i++) M[i]=(mod-1)/S[i].mod,invm[i]=qpow(M[i],S[i].mod-2,S[i].mod);
for(int i=1;i<=4;i++) ans+=(M[i]*invm[i]*sum[i]);
ans=(ans%(mod-1)+mod-1)%(mod-1);//中国剩余定理
cout<<qpow(g,ans,mod)<<'\n';
return 0;
}
【模板】扩展中国剩余定理
思路:
其实真的只是板子没啥说的,不过发现一个大佬写的真的超级详细
代码:
$code$
#include<iostream>
#define int long long
#define ll __int128
using namespace std;
int n,a,b,ans;ll A,B,x,y;
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1;y=0;
return a;
}else{
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
}
signed main(){
ios::sync_with_stdio(false);
cin>>n;
A=1,B=0;
for(int i=1;i<=n;i++){
cin>>a>>b;
ll gcd=exgcd(A,a,x,y);
x=(B-b)/gcd*x;
B=B-A*x;
A=a/gcd*A;
B=(B%A+A)%A;
}ans=(B%A+A)%A;
cout<<ans;
return 0;
}
屠龙勇士
思路:
扩展扩展中国剩余定理,其实跟扩展中国剩余定理是一样的,多成个系数就好了。
用\(upper\)_\(bound\)找出符合要求的剑,然后套\(exexcrt\)就好了,因为还没\(xiáo\)会龟速乘,所以用的__\(int128\),然后记得要把怪物至少砍成负血就好了。
代码:
$code$
#include<iostream>
#include<set>
#define int long long
using namespace std;
const int N=1e5+5;
int T,n,m,x,a[N],p[N],t[N],b[N],maxn;multiset<int> s;
inline void exgcd(int a,int b,int &x,int &y,int &gcd){
if(!b) x=1,y=0,gcd=a;
else exgcd(b,a%b,y,x,gcd),y-=(a/b)*x;
}
inline int excrt(){
int ans=0,lcm=1,x,y,A,B,C,gcd;
for(int i=1;i<=n;i++){
A=(__int128)b[i]*lcm%p[i];
B=p[i];
C=(a[i]-b[i]*ans%p[i]+p[i])%p[i];
exgcd(A,B,x,y,gcd);
x=(x%p[i]+p[i])%p[i];
if(C%gcd) return -1;
ans=(ans+(__int128)(C/gcd)*x%(B/gcd)*lcm%(lcm*=B/gcd))%lcm;
}if(ans<maxn) ans+=((maxn-ans-1)/lcm+1)*lcm;
return ans;
}
signed main(){
ios::sync_with_stdio(false);
cin>>T;
while(T--){
s.clear();maxn=-1;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<=n;i++) cin>>t[i];
for(int i=1;i<=m;i++) cin>>x,s.insert(x);
for(int i=1;i<=n;i++){
auto x=s.upper_bound(a[i]);
if(x!=s.begin()) x--;
b[i]=*x;s.erase(x);s.insert(t[i]);
maxn=max(maxn,(a[i]-1)/b[i]+1);
}cout<<excrt()<<'\n';
}
return 0;
}
【模板】扩展欧几里得
思路:
其实并不需要。
代码:
$code$
#include<iostream>
#define int long long
using namespace std;
const int N=1e6+5;
int m,n,mod,now,ans,p[N],a[N];
inline int qpow(int a,int b,int p){
int res=1;
while(b){
if(b&1) res=(res*a)%p;
a=(a*a)%p;
b>>=1;
}return res;
}
inline void exgcd(int a,int b,int &x,int &y){
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
inline int inv(int a,int p){
int x,y;
exgcd(a,p,x,y);
return (x%p+p)%p;
}
inline int fac(int n,int p,int pk){
if(!n) return 1;
int ans=1;
for(int i=1;i<pk;i++) if(i%p) ans=(ans*i)%pk;
ans=qpow(ans,n/pk,pk);
for(int i=1;i<=n%pk;i++) if(i%p) ans=(ans*i)%pk;
return ans*fac(n/p,p,pk)%pk;
}
inline int C(int n,int m,int p,int pk){
if(n<m) return 0;
int f1=fac(n,p,pk),f2=fac(m,p,pk),f3=fac(n-m,p,pk),cnt=0;
int t1=n,t2=m,t3=n-m;
while(t1) cnt+=t1/p,t1/=p;
while(t2) cnt-=t2/p,t2/=p;
while(t3) cnt-=t3/p,t3/=p;
return (f1*inv(f2,pk)%pk*inv(f3,pk)%pk)*qpow(p,cnt,pk)%pk;
}
inline int exlucas(){
int res=mod;
for(int i=2;res!=1;i++){
if(res%i) continue;
int tmp=1;
p[++now]=i;
while(!(res%i)) res/=i,tmp*=i;
a[now]=C(n,m,p[now],tmp);
p[now]=tmp;
}for(int i=1;i<=now;i++) ans=(ans+(mod/p[i]*a[i])%mod*inv(mod/p[i],p[i])%mod)%mod;
return ans;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m>>mod;
cout<<exlucas()<<'\n';
return 0;
}