『NOIP普及:数论组题训练』

NOIP普及:数论组题训练

        • T1 埃匹希斯水晶
        • 解析
        • T2 要塞任务
        • 解析
        • T3 奶牛的卧室
        • 解析
        • T4 fuse
        • 解析

T1 埃匹希斯水晶

题目描述

大家都知道,卡德加是个神奇的法师。 有一天,他发现了一种可以作用在埃匹希斯水晶上的魔法:在左右两个祭坛上放一定量的水晶,然后施放一个法术,左边一堆的水晶数量会变成原来两个祭坛上水晶之和,右边一堆会变成两个祭坛上水晶数量之差。

卡德加现在有两堆水晶,分别有 A 个和 B 个。他打算集中精神连续 释放 N次法术,但不知道最后能拿到多少水晶,于是他找到了要塞指挥官(就是你了)。
输入格式

三个整数 A, B, N ,表示祭坛上刚开始的水晶数,和法术的释放次数。
输出格式

两个数,祭坛上最后的水晶数。输出模 (10e8 + 7)。
样例数据

input

1 2 3

output

6 2

数据规模与约定

50%: N ≤ 10e6 。 100%: A, B ≤ 10e8 , N ≤ 10e18 。

时间限制:1s

空间限制:256MB

解析

简单的思考本题,就会发现有一个很简单的规律:

第一堆 第二堆
A B
A+B A-B
2*A 2*B
2n/2*A 2n/2*B

每操作两次,左边的水晶数和右边的水晶数就会翻一倍。
那么用快速乘和快速幂做2的整次方就可以解决这个问题。如果n为奇数,那就先手动操作一个,再乘2的整次方。

#include
using namespace std;
long long A,B,n,ansa,ansb;
const long long P=10e8+7;
inline void input(void)
{
	scanf("%lld%lld%lld",&A,&B,&n);
	if(A<B)swap(A,B);
}
inline long long Max(long long a,long long b){return a>b?a:b;}
inline long long Min(long long a,long long b){return a<b?a:b;}
inline long long mul(long long a,long long b) 
{ 
	long long result=0; 
	while(b>0) 
	{ 
		if(b&1)result+=a,result%=P; 
		b>>=1; 
		a+=a,a%=P; 
	} 
	return result; 
}
inline long long power(long long a,long long b) 
{ 
	long long result=1; 
	while(b>0) 
	{ 
		if(b&1)result=mul(result,a); 
		b>>=1; 
		a=mul(a,a); 
	} 
	return result; 
}

inline void work(void)
{
	if(n%2==0)ansa=mul(A%P,power(2,n/2)%P),ansb=mul(B%P,power(2,n/2)%P);
	else ansa=mul(A%P+B%P,power(2,n/2)%P),ansb=mul(A%P-B%P,power(2,n/2)%P);
}
int main(void)
{
	freopen("apexis.in","r",stdin);
	freopen("apexis.out","w",stdout);
	input();
	work();
	printf("%lld %lld\n",ansa%P,ansb%P);
	return 0;
}

T2 要塞任务

题目描述

你的要塞⾥有 N 名随从,每名随从有⼀个战⽃⼒值 Ai ,不同随从的战⽃⼒可以相同,且永远不超过 N 。⼀个要塞任务需要恰好 M 个随从参与。

要塞任务的奖励取决于随从们配合的程度。(显⽽易见地),M 个随 从的联合战⽃⼒ A 为它们战⽃⼒的最大公约数,⽽任务的奖励分数定义为ϕ(A)。

求最大可能的奖励分数。
输入格式

本题有多组数据,第⼀⾏为数据组数 T 。 接下来每组数据有两⾏,第⼀⾏两个整数 N, M ,第⼆⾏ N 个整数 Ai 。
输出格式

一行,一个整数,表示答案。
样例数据

input

1
5 2
1 4 6 9 12

output

2

数据规模与约定

20%:N M ≤ 10e5 。 60%:N, M, Ai ≤ 100。 100%:N, M, Ai ≤ 100000,T ≤ 10。

时间限制:1s

空间限制:256MB

解析

我们可以先利用线性筛法预处理出1~n的欧拉函数。其具体步骤不再详解,可以参照以前数论博客。
有了欧拉函数后,我们的问题就转变为了一个数A,他是数列中至少m个数的最大公约数,且使得 ϕ ( A ) \phi(A) ϕ(A)最大。
那么思维的转换就在这个地方,我们其实直接枚举A就可以了。怎么判断它至少是m个数的最大公约数呢?我们先用桶记录每一个数出现的次数 c n t [ i ] cnt[i] cnt[i],当枚举到一个数 A ′ A' A时,显然 A ′ , 2 A ′ , 3 A ′ , . . . , k A ′ A',2A',3A',...,kA' A,2A,3A,...,kA这些数的最大公约数是 A ′ A' A,那么我们累加 c n t cnt cnt数组,得到 Σ i = 1 i ∗ A ′ ≤ m a x { a i } c n t [ i ∗ A ′ ] \Sigma_{i=1}^{i*A'≤max\{a_i\}}cnt[i*A'] Σi=1iAmax{ai}cnt[iA]的值,就可以判断了,如果这个值大于m,显然他是符合要求的,我们可以利用预处理的欧拉函数尝试更新答案,反之,跳过就可以了。

#include
using namespace std;
#define mset(k) memset(k,0,sizeof(k))
int n,m,Combatpower[100080]={},cnt[100080]={},Max=-1,ans=-1;
int phi[100080]={},prime[100080]={},flag[100080]={},Cnt=0;
inline void input(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&Combatpower[i]),cnt[Combatpower[i]]++;
		Max=max(Max,Combatpower[i]);
	}
}
inline void eular()
{
	for(int i=2;i<=n;i++)
	{
		if(!flag[i]){prime[++Cnt]=i;phi[i]=i-1;}
		for(int j=1;j<=Cnt;j++)
		{
			if(prime[j]*i>n)break;
			flag[prime[j]*i]=true;
			if(i%prime[j]==0){phi[i*prime[j]]=phi[i]*prime[j];break;}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
inline void solve(void)
{
	for(int i=1;i<=Max;i++)
	{
		if(ans>=phi[i])continue;//优化:如果符合要求也更新不了答案,直接跳过即可
		int t=0;
		for(int j=i;j<=Max;j+=i)
		{
			if(cnt[j])t+=cnt[j];
			if(t>=m)break;//优化:如果已经符合要求,就不用再统计了
		}
		if(t>=m)ans=max(ans,phi[i]);
	}
	printf("%d\n",ans);
}
int main(void)
{
	freopen("quest.in","r",stdin);
	freopen("quest.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		mset(flag);mset(prime);mset(phi);mset(cnt);mset(Combatpower);
		Cnt=0;Max=ans=-1;
		input();
		eular();
		solve();
	}
	return 0;
}

T3 奶牛的卧室

题目描述

奶牛们有一个习惯,那就是根据自己的编号选择床号。如果一头奶牛编号是a,并且有0…k-1一共k张床,那么她就会选择a mod k号床作为她睡觉的地点。显然,2头牛不能睡在一张床上。那么给出一些奶牛的编号,请你为她们准备一间卧室,使得里面的床的个数最少。
输入格式

第一行是奶牛的个数n(1<=n<=5000);第2到第n+1行是每头奶牛的编号Si(1<=Si<=1000000)。
输出格式

仅一行,是最少的床的数目。

input

5
4
6
9
10
13

output

8

数据规模与约定

1s

未知

时间限制:1s

空间限制:256MB

解析

题目要求我们求解一个k,使得任何一个 a i % k a_i \% k ai%k各不相同。那么,显然有的是:对于任意 a i , a j a_i,a_j ai,aj k k k不能作为 a i − a j a_i-a_j aiaj的因数,否则,他们一定同余。那么,我们可以用桶标记任意两个数的差, d i f [ i ] = t r u e dif[i]=true dif[i]=true代表存在两个数的差为i。那么我们可以直接枚举从小到大k,并判断 d i f [ k ] dif[k] dif[k]是否为假,如果是假,继续判断 d i f [ k ] dif[k] dif[k]的整数倍是否为假,如果都为假,那就说明这个k符合要求,直接输出即可。

#include
using namespace std;
int n,num[5080]={},dif[1000080]={},Max=-1;
inline void input(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&num[i]),Max=max(Max,num[i]);
}
inline void Marking(void)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dif[abs(num[i]-num[j])]=true;
		}
	}
}
inline void Find(void)
{
	for(int i=n;i<=Max;i++)
	{
		if(!dif[i])
		{
			for(int j=i;j<=Max;j+=i)
			{
				if(dif[j]==true)
				{
					dif[i]=true;
				}
			}
			if(!dif[i])
			{
				printf("%d\n",i);
				break;
			}
		}
	}
}
int main(void)
{
	freopen("bed.in","r",stdin);
	freopen("bed.out","w",stdout);
	input();
	Marking();
	Find();
	return 0;
}

T4 fuse

题目大意

在进入矿洞N久之后,Abigail 已经取得了一大堆各种各样的矿石,并将这些矿石熔化一个个装进了瓶子里了.

这些瓶子都有一个属性Ai,这个属性Ai的所有因数表示了这个瓶子内装的液体的特性.当两个瓶子i和j中的液体要混合时,它们混合的液体属性g就是Ai和Aj都具有的最大液体特性,即g=gcd(Ai,Aj).

现在 Abigail 经过了十分劳累的冶炼,摊在地上突然想到了一个神奇的问题,如果把任意两只瓶子混合起来,得到的液体属性为gi有多少种可能呢.

注意,对于两个编号为i,j(i≠j),(i,j)和(j,i))算成两种不同的方案.
输入格式

输入第1行包括一个整数n,表示 Abigail 得到了n瓶溶液.

接下来n行,每行一个正整数Ai,表示第i瓶液体的属性.

接下来1行,包括一个整数 m,表示询问的 gi的数量.

接下来m行,每行一个正整数 gi,表示询问.
输出格式

输出包括 m行,第i行一个正整数表示第i个询问的答案.
数据范围

对于100%的数据: 1≤Ai1≤Ai 1≤m≤2n

解析

首先,我们需要用桶记录每一个数出现的次数,即 c n t [ i ] cnt[i] cnt[i]代表 A A A i i i出现的次数。那么,我们就可以求出每一个数作为别的数的因子的次数,即 n u m [ i ] = Σ j = 1 i ∗ j ≤ m a x { A i } c n t [ i ∗ j ] num[i]=\Sigma_{j=1}^{i*j≤max\{A_i\}}cnt[i*j] num[i]=Σj=1ijmax{Ai}cnt[ij]。由乘法原理可得:每一个数做为其他数对的公约数的次数 N [ i ] = n u m [ i ] ∗ ( n u m [ i ] − 1 ) N[i]=num[i]*(num[i]-1) N[i]=num[i](num[i]1),但是,这只是公约数,我们要求的是每一个数作为其他数对的最大公约数的次数,设i作为其他数对的最大公约数的次数 c [ i ] c[i] c[i],那么 c [ i ] c[i] c[i]其实等于i作为其他数对的公约数的次数减掉i整数倍作为其他数对的最大公约数的次数,即 c [ i ] = N [ i ] − Σ j = 2 i ∗ j ≤ m a x { A i } c [ i ∗ j ] c[i]=N[i]-\Sigma_{j=2}^{i*j≤max\{A_i\}}c[i*j] c[i]=N[i]Σj=2ijmax{Ai}c[ij]。那么,它询问 g g g,我们输出 c [ g ] c[g] c[g]即可。

#include
using namespace std;
#define mset(name,value) memset(name,value,sizeof(name))
const long long INF=0x3f3f3f3f;
inline void read(long long &k)
{
	long long x=0,w=0;char ch;
	while(!isdigit(ch))w|=ch=='-',ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	k=(w?-x:x);return;
}
long long n,m,a[1000080]={},cnt[1000080]={},num[1000080]={},c[1000080]={},N[1000080]={},Max=-1;
inline void input(void)
{
	read(n);
	for(long long i=1;i<=n;i++)
	{
		read(a[i]);cnt[a[i]]++;
		Max=max(Max,a[i]);
	}
	for(long long i=1;i<=Max;i++)
	{
		for(long long j=i;j<=Max;j+=i)
		{
			num[i]+=cnt[j];
		}
	}
	for(long long i=1;i<=Max;i++)N[i]=num[i]*(num[i]-1);
	for(long long i=Max;i>=1;i--)
	{
		c[i]=N[i];
		for(long long j=i+i;j<=Max;j+=i)
		{
			c[i]-=c[j];
		}
	}
	read(m);
}
inline void solve(void)
{
	for(long long i=1;i<=m;i++)
	{
		long long g;read(g);
		printf("%lld\n",c[g]);
	}
}
int main(void)
{
	freopen("fuse.in","r",stdin);
	freopen("fuse.out","w",stdout);
	input();
	solve();
	return 0;
}

你可能感兴趣的:(『NOIP普及:数论组题训练』)