2 出色的物理引擎
卡羅拉最近沉迷於ark游戲,游戲中的地圖上有n個浮空的石頭圍成了一圈,在優秀的物理引擎支持下,這些石頭會自動落下。她發現石頭落下的順序是有規律的。一共有n個石頭,從第一塊石頭開始數,數到第m個石頭,那塊就是第一個落下的石頭;之后從第一個落下的石頭后一個重新從1開始數,同樣數到第m個石頭,那個就是第二個落下的石頭;以此類推。為了方便,對這些石頭從1開始編號。卡羅拉現在想知道最后落下的是那一塊石頭?
輸入格式
輸入包含兩個整數n和m (1<=m,n<=1000)。
輸出格式
輸出一 個整數,代表最后落下的石頭的編號。
輸入樣例
10 3
輸出樣例
4
Accepted
這一題一開始我想用鏈表的方法解決,因為在鏈表中,我們刪去其中一個石子的操作會顯得很便捷,那么具體方法可以見下方的鏈接。
最后也是我覺得這題我應該學到的東西,那就是使用約瑟夫環解決問題。但是說真的,在理解的時候還是費了好一番功夫。這里截取了一些,上述原文的分析過程。不得不說這是一篇寶藏文章,分析的很透徹。老規矩還是先上代碼:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
int n;
int m;
int i;
int ans=0;
cin >> n >> m;
for(i=1;i<=n;i++){
ans=(ans+m)%i;//約瑟夫環
}
cout << ans+1;//注意這里的輸出
return 0;
}
真的,比上面兩種方法所需要寫的代碼量少了超級多,直接一個公式就解決了問題。
下面開始進行分析:
例如有十個石子,編號0~9(為什么?理由是由於m是可能大於n的,而當m大於等於n時,那么第一個出列的人編號是m%n,而m%n是可能等於0的,這樣編號的話能夠簡化后續出列的過程)
注意 以下圖示中的環數字排列都是順序的,且從編號0開始。
由圖知,10人環中最后入海的是4號,現由其在1人環中的對應編號0來求解。
3 開機方案
h學長有個機器用來完成任務。現在有n個任務,第i個任務(1<= i <= n)在ti時刻開始,並在ti + 1時刻結束。同一時刻不會有多個任務。 h學長可以在任何時刻開啟機器,不過每一次開啟機器都會消耗1點能量。h學長只有k點能量可以用於開啟機器。但是機器開着的時候需要消耗燃料,顯然讓機器一直開着並不一定是最好的選擇。現在h學長想利用自己具備的k點能量,有效的控制機器的開啟,使得機器完成n個任務消耗的燃料盡可能的少。那么對應給出的n個任務以及h學長擁有的能量數,你能幫幫他嗎? 提示:消耗的燃料要盡可能的少,即機器工作的時間盡可能的短。
輸入格式
第一行包括兩個整數 n和k(1<= n <= 1e5, 1<= k <=n) ,表示有 n個任務和h學長擁有k點能量。
接下來 n行,每行一個整數ti(1<= ti <=1e9),表示第 i 個任務在ti 時刻開始,並在ti + 1時刻結束 。
輸出格式
輸出一行包含一個整數,表示機器工作的最少時間。
輸入樣例1
3 2
1
3
6
輸出樣例1
4
樣例1說明:
h學長有2點能量,可以用於兩次機器的開啟。 h學長會在時刻1 即第一個任務開始時開啟機器,並在第二個任務結束時關閉機器; h學長會在時刻6 即第三個任務開始時開啟機器,並在第三個任務結束時關閉機器。 機器總工作時間為 (4-1)+(7-6)=4 。
輸入樣例2
10 5
1
2
5
6
8
11
13
15
16
20
輸出樣例2
12
樣例2說明: h學長有5點能量,可以用於5次機器的開啟。 h學長會在時刻1 即第1個任務開始時開啟機器,並在第2個任務結束時刻3關閉機器; h學長會在時刻5 即第3個任務開始時開啟機器,並在第4個任務結束時刻7關閉機器; h學長會在時刻8 即第5個任務開始時開啟機器,並在第5個任務結束時刻9關閉機器; h學長會在時刻11 即第6個任務開始時開啟機器,並在第9個任務結束時刻17關閉機器; h學長會在時刻20 即第10個任務開始時開啟機器,並在第10個任務結束時刻21關閉機器; 機器總工作時間為 (3-1)+(7-5)+(9-8)+(17-11)+(21-20)=12 。 開機、關機時刻不唯一。
Accepted
做到這題的時候,我其實想到了我們第一次上機練習的兼容任務那一題,那么那一題的思想是我把結束時間根據大小從小到大進行排序,然后在一定的時間內完成多個任務。那我這題也使用類似的思想,假設現在我從第一個任務開始的時間打開機器(k--),並且保持開機狀態直到最后一個任務結束,算出總共需要的時間。然后在k仍然還大於0的情況下,我根據前后任務時間間隔大小由大到小排序,然后在k還有剩余的情況下,依次扣除這些間隔,這樣答案就得到了。這題的思想主要是逆序扣除的思想,因為如果從頭開始算的話,並不是特別好算,但是我現在倒一頭來算的話,反而是問題簡單化。代碼如下:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
struct machine{
int begin;
int end;
int diff;
}t[1000005];
bool cmp1(machine a,machine b){
return a.begin <b.begin;
}
bool cmp2(machine a,machine b){
return a.diff > b.diff;
}
int main(){
int n;
int k;
int i=1;
int ans=0;
cin >> n >> k;
for(i=1;i<=n;i++){
cin >> t[i].begin;
t[i].end=t[i].begin+1;
}
sort(t+1,t+1+n,cmp1);
ans=t[n].end-t[1].begin;
k--;
for(i=1;i<n;i++){
t[i].diff=t[i+1].begin-t[i].end;
}
t[n].diff=0;
sort(t+1,t+1+n,cmp2);
for(i=1;i<=n&&k!=0;i++){
ans-=t[i].diff;
k--;
}
cout << ans;
return 0;
}
4 特殊的翻譯
小明的工作是對一串英語字符進行特殊的翻譯:當出現連續且相同的小寫字母時,須替換成該字母的大寫形式,在大寫字母的后面緊跟該小寫字母此次連續出現的個數;與此同時,把連續的小寫字母串的左側和右側的字符串交換位置;重復該操作,直至沒有出現連續相同的小寫字母為止。現在小明想請你幫他完成這種特殊的翻譯。
輸入格式
輸入一串由小寫字母構成的字符串。(字符串長度不大於250)
輸出格式
輸出翻譯后的字符串。
輸入樣例1
dilhhhhope
輸出樣例1
在這里給出相應的輸出。例如:
opeH4dil
輸入樣例2
lodnkmgggggggoplerre
輸出樣例2
在這里給出相應的輸出。例如:
eG7lodnkmR2ople
Accepted
對於這一題的處理,讓我再一次感覺到
string
在處理字符串的方便性。但是特別要注意的是,在處理字符串的時候,不要忽略連續字母個數可能超過10,這也是我一開始錯的地方。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
int main(){
string s;
char str[10];
cin >> s;
int i,j,k,t;
int flag=0;
int num=0,temp;//num出現次數
for(i=0;i<s.size();i++){
string s1;
t=0;
if(s[i]==s[i+1]&&s[i]>='a'&&s[i]<='z'){//出現連續相同的小寫字母
num=1;
for(j=i+1;j<s.size();j++){
if(s[j]==s[i]) num++;
else break;
}
s[i]-=32;//小寫->大寫
if(num<10)s[i+1]=num+'0'; //如果num值小於10
else{//如果num值大於10
temp=num;
while(temp){
str[t]=temp%10+'0';
temp/=10;
t++;
}
}
for(;j<s.size();j++){
s1+=s[j];
// cout << j << endl;
}
s1+=s[i];
if(num<10)s1+=s[i+1];
else
for(t--;t>=0;t--)
s1+=str[t];
for(k=0;k<i;k++)
s1+=s[k];
s=s1;
flag=1; //發現字符串做了改變,所以從頭開始重新掃
}
if(flag==1){
flag=0;
i=-1;
}
}
cout << s << endl;
return 0;
}
5 好吃的巧克力
超市正在特價售賣巧克力,正好被貪吃的Lucky_dog看見了。巧克力從左到右排成一排,一共有N個,M種。超市有一個很奇怪的規定,就是你在購買巧克力時必須提供兩個數字a和b,代表你要購買第 a 個至第 b 個巧克力(包含 a 和 b)之間的所有巧克力。假設所有巧克力的單價均為1元。Lucky_dog想吃所有種類的巧克力,但又想省錢。作為Lucky_dog的朋友,他請你來幫他決定如何選擇購買巧克力時的 a 和 b。
輸入格式
第一行包含兩個正整數 N 和 M(M<=N, N<=10^6 , M<=2000),分別代表巧克力的總數及種類數。
第二行包含 N 個整數,這些整數均在1 至 M 之間,代表對應的巧克力所屬的種類。
輸出格式
輸出僅一行,包含兩個整數a和 b(a<=b) ,由一個空格隔開,表示花費最少且包含所有種類巧克力的購買區間。
數據保證有解,如果存在多個符合條件的購買區間,輸出a最小的那個。
輸入樣例
12 5
2 5 3 1 3 2 4 1 1 5 4 3
輸出樣例
在這里給出相應的輸出。例如:
2 7
Accepted
這一題剛開始就覺得直接用暴力遍歷去尋找最優解就可以了,但是后面會發現有的點運行超時了,所以很顯然暴力求解法是行不通的。那么這里要引入一種方法尺取法
這里引用原文部分解釋,來進行一個大概的介紹:
尺取法:顧名思義,像尺子一樣取一段,借用挑戰書上面的話說,尺取法通常是對數組保存一對下標,即所選取的區間的左右端點,然后根據實際情況不斷地推進區間左右端點以得出答案。之所以需要掌握這個技巧,是因為尺取法比直接暴力枚舉區間效率高很多,尤其是數據量大的時候,所以尺取法是一種高效的枚舉區間的方法,一般用於求取有一定限制的區間個數或最短的區間等等。當然任何技巧都存在其不足的地方,有些情況下尺取法不可行,無法得出正確答案。
使用尺取法時應清楚以下四點:
1、 什么情況下能使用尺取法? 2、何時推進區間的端點? 3、如何推進區間的端點? 3、何時結束區間的枚舉?
尺取法通常適用於選取區間有一定規律,或者說所選取的區間有一定的變化趨勢的情況,通俗地說,在對所選取區間進行判斷之后,我們可以明確如何進一步有方向地推進區間端點以求解滿足條件的區間,如果已經判斷了目前所選取的區間,但卻無法確定所要求解的區間如何進一步得到根據其端點得到,那么尺取法便是不可行的。首先,明確題目所需要求解的量之后,區間左右端點一般從最整個數組的起點開始,之后判斷區間是否符合條件在根據實際情況變化區間的端點求解答案。
很顯然對於這一題來說,我們就是先找到包括m種巧克力的區間的右端點,然后有左端點逐步逼近右端點,答案就可以解出來了,注意在我們比較是否為最少的時候,我們需要引用到區間長度,此時是否加1或減1這些小細節會影響結果,所以一定要細心思考。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
int ch[1000005]={0};
int q[10000005]={0};
int main(){
int n,m; //巧克力的總數,種類數
int i,l=1,r=0;
int num=0;
cin >> n >> m;
for(i=1;i<=n;i++) cin >> ch[i];
int a,b,len=n;
while(l<=n&&r<=n){
while(num<m){
r++;
q[ch[r]]++;
if(q[ch[r]]==1) num++;
} //r值固定不變,接下來找出l的位置
if(num<m) break;
if(len>r-l+1&&num==m){
len=r-l+1;
a=l;
b=r;
}
q[ch[l]]--;
if(q[ch[l]]==0) {num--;}
l++;
}
cout << a <<" "<< b <<endl;
return 0;
}
6 下次一定(續)
你是一個bilibili的六級號,由於經常一鍵三連,所以一個硬幣都沒有,現在你又做了個夢,在夢中你制定了一個硬幣增加規則:
第一天登陸后硬幣總數1個,第二天登陸后硬幣總數112個,第三天登陸硬幣總數112123個......,以此類推,夢中不知日月,你瞬間擁有了11212312341234512345612345671234567812345678912345678910123456789101112345678910......無窮多個硬幣。
常年維護B站秩序的百漂怪看不下去了,決定隨機挑一個數x,作為這個無窮數的第x位,如果你能正確答對這第x位上的數是什么,就贈送給你與這個數等量的硬幣。
百漂怪總共會挑t次。
你決定編程模擬一下,搏一搏,單車變摩托。
輸入格式
第一行包含一個正整數t(1≤t≤10),表示百漂怪挑了t次。 接下來t行,每行一個正整數x (1 ≤ x≤ 2^31-1),表示第i次,百漂怪挑的是第x位。
輸出格式
輸出共t行,每行輸出對應第x位上的數。
輸入樣例1
2
3
8
輸出樣例1
2
2
輸入樣例2
6
222
66666
99999999
66656565
22222
2
輸出樣例2
6
4
9
4
1
1
Accepted
我做這一題的主要思路是,將硬幣數變化的規則分為i組,然后我們要找到x的位置對應的組數,然后根據這組所對應的字符串,尋找x對應的字符。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
long long digi[100000]={0};//digi存位數
int main(){
int i,t,j=1;
long long sum=0;
char ans;
long long x;
int pos;
for(i=1;i<=32000;i++) //統計位數,由已知x≤2^31-1,可知最多可分31268組,即i的范圍;
digi[i]=digi[i-1]+(int)(log10(i)+1);
cin >> t;
for(i=1;i<=t;i++){
cin >> x;
j=0;
sum=0;
while(1){
j++;
sum+=digi[j];
if(sum>=x) break;
}
string str;
for(int k=1;k<=j;k++){
char st[100000];
sprintf(st,"%d",k);
str+=st;
}
pos=(int)((digi[j]-1)-(sum-x));
ans=str[pos];
cout << ans << endl;
}
return 0;
}
7 小明選蛋糕
小明舍友生日快到了,他決定背着舍友偷偷為他准備一個蛋糕,但是蛋糕款式眾多,他也很難選擇,於是他決定根據蛋糕擺放的位置來縮小蛋糕選擇的范圍。
已知小明從第a個蛋糕開始選擇,他規定一個比a大的數m(1<=a<m<=10^10),小明只對滿足下式的x(0<=x<m)位置上的蛋糕感興趣,gcd(a,m)=gcd(a+x,m)經過這樣的篩選后,可供小明選擇的蛋糕款式還剩多少呢?
輸入格式
第一行包含一個整數T(1<=T<=50),表示輸入數據共T組;
接下來共有T行,每行包含兩個整數a和m (1<=a<m<=10^10)。
輸出格式
輸出共T行,每行一個整數k,表示經過篩選后可供小明選擇的蛋糕款式的數量。
輸入樣例
3
4 9
5 10
42 9999999967
輸出樣例
6
1
9999999966
Wrong Answer
一開始我拿到題目,以為這一題非常簡單,只不過就是寫一個gcd函數就可以解決問題。但是當我提交代碼的時候有四個點顯示運行超時,然后我注意到題目m范圍<=10^10,所以只能另外找一個方法,簡化循環次數。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
long long gcd(long long m,long long n)
{
if(m%n==0) return n;
else return gcd(n,m%n);
}
int main(){
long long m,a,j,b;
int i,t;
cin >> t;
for(i=1;i<=t;i++){
cin >> a >> m;
long long num=0;
b=gcd(a,m);
for(j=0;j<m;j++){
if(b==gcd(a+j,m))num++;
}
cout << num << endl;
}
return 0;
}
Accepted
既然模擬這條路走不通,那么顯然,我們需要把角度放在另一種思路,我們注意到這一題很特別的條件就是
gcd(a,m)=gcd(a+x,m)
,那么我們如何利用這個關系來解決問題呢?通過搜索資料,我們了解到在解決求最大公約數的方法中,有一種簡單且快捷的方法,那就是使用歐拉函數。這里簡要介紹一下歐拉函數。(思路來源:yjs)
歐拉函數:就是對於一個正整數n,小於n且和n互質的正整數(包括1)的個數,記作\(\varphi(n)\)。
歐拉函數的通式:\(\varphi(n)=n(1-\frac{1}{p_1})(1- \frac{1}{p_2} )(1-\frac{1}{p_3})(1-\frac{1}{p_4})\cdots(1-\frac{1}{p_n})=n\displaystyle \prod^{n}_{i=1}(1-\frac{1}{p_i})\)
但我們注意到歐拉函數的使用要求是a和m互質,但是在本題中a與m不一定存在互質關系,因此需要我們進行進一步的轉化,將a與m轉化為互質數。也就是當我們用gcd函數求出這兩個數之間的最大公約數n后,就可以得出\(gcd(\frac{a}{n},\frac{m}{n})=gcd(\frac{a+x}{n},\frac{m}{n})=1\)那么,現在\(\frac{a+x}{n},\frac{m}{n}\)互質。但是,我們需要注意的是\(\frac{a+x}{n}\)的值有可能大於\(\frac{m}{n}\),那么大於\(\frac{m}{n}\)的部分就不能使用歐拉函數求出了,並且小於\(\frac{a}{n}\)的部分會被記錄。由gcd函數概念我們可以知道\(gcd(\frac{a+x}{n},\frac{m}{n})=gcd((\frac{a+x}{n})\%\frac{m}{n},\frac{m}{n})\),可以知道超出的部分恰好由小於的部分所記錄,因此我們只需求解\(\varphi(\frac{m}{n})\).
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
long long gcd(long long m,long long n)
{
if(m%n==0) return n;
else return gcd(n,m%n);
}
long long calculate(long long m){
long long ans=m;
for(long long i=2;i*i<=m;i++){
if(m%i==0){
ans=ans/i*(i-1);
while(m%i==0)m/=i;
}
}
if(m>1) ans=ans/m*(m-1); //因為數據范圍為longlong,若先乘后除可能會導致數據溢出,故采用先除后乘
return ans;
}
int main(){
long long m,a,j,b;
int i,t;
cin >> t;
for(i=1;i<=t;i++){
cin >> a >> m;
long long num=0;
b=gcd(a,m);
/*for(j=0;j<m;j++){
if(b==gcd(a+j,m))num++;
}
num=calculate(m , a);*/
m/=b;
num=calculate(m);
cout << num << endl;
}
return 0;
}
8 走迷宮
你正在玩一個迷宮游戲,迷宮有n×n格,每一格有一個數字0或1,可以從某一格移動到相鄰四格中的一格上。為了消磨時間,你改變了玩法,只許從0走到1或者從1走到0。
現在給你一個起點,請你計算從這個格子出發最多能移動多少個格子(包含自身)。
輸入格式
第1行包含兩個正整數n和m(1≤n≤1000,1≤m≤10000)。
接下來n行,對應迷宮中的n行,每行n個字符,字符為0或者1,字符之間沒有空格。
接下來m行,表示m次詢問。每行2個正整數i,j,表示從迷宮中第i行第j列的格子開始走。
輸出格式
輸出共m行,每行一個整數,分別對應於輸入數據中的m次詢問,給出最多能移動的格子數。
輸入樣例
2 2
01
10
1 1
2 2
輸出樣例
4
4
Accepted
(來源:洛谷)
這一題題目很好理解,但是做的時候確實一臉懵,不知道從何入手,這時候看到網上有一種叫DFS的做法也是第一次接觸到這種算法,讓我簡略的碼一下它的概念:
深度優先搜索算法(Depth First Search,簡稱DFS):一種用於遍歷或搜索樹或圖的算法。 沿着樹的深度遍歷樹的節點,盡可能深的搜索樹的分支。當節點v的所在邊都己被探尋過或者在搜尋時結點不滿足條件,搜索將回溯到發現節點v的那條邊的起始節點。整個進程反復進行直到所有節點都被訪問為止。屬於盲目搜索,最糟糕的情況算法時間復雜度為\(O(n!)\)。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
char a[1005][1005];
int ans[100005],pos[1005][1005],n,m,x,y;
void DFS(int x,int y,int z,int i){
if(x<0||x>=n||y<0||y>=n||pos[x][y]!=-1||a[x][y]-'0'!=z) return ;
pos[x][y]=i; //越界,已被搜索過,不滿足01關系
ans[i]++;
DFS(x-1,y,!z,i);
DFS(x+1,y,!z,i);
DFS(x,y-1,!z,i);
DFS(x,y+1,!z,i);
}
int main(){
int i;
cin >> n >>m;
for(i=0;i<n;i++){
cin >> a[i];
}
memset(pos,-1,sizeof(pos));
for(i=0;i<m;i++){
cin>> x >> y;
x--;
y--;
if(pos[x][y]==-1){
DFS(x,y,a[x][y]-'0',i);
}
else ans[i]=ans[pos[x][y]];
}
for(i=0;i<m;i++){
cout << ans[i]<< endl;
}
return 0;
}