『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就可以了。怎麼判斷它至少是m個數的最大公約數呢?我們先用桶記錄每一個數出現的次數,當列舉到一個數時,顯然這些數的最大公約數是,那麼我們累加陣列,得到的值,就可以判斷了,如果這個值大於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,使得任何一個各不相同。那麼,顯然有的是:對於任意,不能作為的因數,否則,他們一定同餘。那麼,我們可以用桶標記任意兩個數的差,代表存在兩個數的差為i。那麼我們可以直接列舉從小到大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
解析
首先,我們需要用桶記錄每一個數出現的次數,即代表中出現的次數。那麼,我們就可以求出每一個數作為別的數的因子的次數,即。由乘法原理可得:每一個數做為其他數對的公約數的次數,但是,這只是公約數,我們要求的是每一個數作為其他數對的最大公約數的次數,設i作為其他數對的最大公約數的次數為,那麼其實等於i作為其他數對的公約數的次數減掉i整數倍作為其他數對的最大公約數的次數,即