集训做题集合(未完...)

前言:

基于我的数论烂得没边,所以 (全部) 部分题目会借鉴大佬博客,但是由于今天的网页崩了\(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;
}
posted @ 2025-10-03 19:00  晏清玖安  阅读(24)  评论(0)    收藏  举报