1. 程式人生 > >『NOIP普及:數論組題訓練』

『NOIP普及:數論組題訓練』

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<bits/stdc++.h>
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就可以了。怎麼判斷它至少是m個數的最大公約數呢?我們先用桶記錄每一個數出現的次數cnt[i]cnt[i],當列舉到一個數AA&#x27;時,顯然A,2A,3A,...,kAA&#x27;,2A&#x27;,3A&#x27;,...,kA&#x27;這些數的最大公約數是AA&#x27;,那麼我們累加cntcnt陣列,得到Σi=1iAmax{ai}cnt[iA]\Sigma_{i=1}^{i*A&#x27;≤max\{a_i\}}cnt[i*A&#x27;]的值,就可以判斷了,如果這個值大於m,顯然他是符合要求的,我們可以利用預處理的尤拉函式嘗試更新答案,反之,跳過就可以了。

#include<bits/stdc++.h>
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,使得任何一個ai%ka_i \% k各不相同。那麼,顯然有的是:對於任意ai,aja_i,a_jkk不能作為aiaja_i-a_j的因數,否則,他們一定同餘。那麼,我們可以用桶標記任意兩個數的差,dif[i]=truedif[i]=true代表存在兩個數的差為i。那麼我們可以直接列舉從小到大k,並判斷dif[k]dif[k]是否為假,如果是假,繼續判斷dif[k]dif[k]的整數倍是否為假,如果都為假,那就說明這個k符合要求,直接輸出即可。

#include<bits/stdc++.h>
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

解析

首先,我們需要用桶記錄每一個數出現的次數,即cnt[i]cnt[i]代表AAii出現的次數。那麼,我們就可以求出每一個數作為別的數的因子的次數,即num[i]=Σj=1ijmax{Ai}cnt[ij]num[i]=\Sigma_{j=1}^{i*j≤max\{A_i\}}cnt[i*j]。由乘法原理可得:每一個數做為其他數對的公約數的次數N[i]=num[i](num[i]1)N[i]=num[i]*(num[i]-1),但是,這只是公約數,我們要求的是每一個數作為其他數對的最大公約數的次數,設i作為其他數對的最大公約數的次數c[i]c[i],那麼c[i]c[i]其實等於i作為其他數對的公約數的次數減掉i整數倍作為其他數對的最大公約數的次數,即c[i]=N[i]Σj=2ijmax{Ai}c[ij]c[i]=N[i]-\Sigma_{j=2}^{i*j≤max\{A_i\}}c[i*j]