PTA天梯20+深度优先搜索及动态规划
2022年4月17日下午13:30-16:30,模擬賽出現(xiàn)了手機小程序經(jīng)常重連、PC端提交代碼時服務(wù)器崩掉及排隊時間過長的情況,只希望考試時不被誤判作弊+順利發(fā)揮得國獎,國二國三都可以,這一周盡力刷掉L3把往年例題吸煙刻肺(這個成語應(yīng)該是這么用吧,書讀的少沒什么文化)。
DFS適用于計數(shù)及函數(shù)遞推
1、特立獨行的幸福 DFS遞歸基礎(chǔ)+素數(shù)判斷
對一個十進制數(shù)的各位數(shù)字做一次平方和,稱作一次迭代。如果一個十進制數(shù)能通過若干次迭代得到 1,就稱該數(shù)為幸福數(shù)。1 是一個幸福數(shù)。此外,例如 19 經(jīng)過 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1。則 19 就是幸福數(shù)。顯然,在一個幸福數(shù)迭代到 1 的過程中經(jīng)過的數(shù)字都是幸福數(shù),它們的幸福是依附于初始數(shù)字的。例如 82、68、100 的幸福是依附于 19 的。而一個特立獨行的幸福數(shù),是在一個有限的區(qū)間內(nèi)不依附于任何其它數(shù)字的;其獨立性就是依附于它的的幸福數(shù)的個數(shù)。如果這個數(shù)還是個素數(shù),則其獨立性加倍。例如 19 在區(qū)間[1, 100] 內(nèi)就是一個特立獨行的幸福數(shù),其獨立性為 2×4=8。
另一方面,如果一個大于1的數(shù)字經(jīng)過數(shù)次迭代后進入了死循環(huán),那這個數(shù)就不幸福。例如 29 迭代得到 85、89、145、42、20、4、16、37、58、89、…… 可見 89 到 58 形成了死循環(huán),所以 29 就不幸福。
【輸入格式】
輸入在第一行給出閉區(qū)間的兩個端點:1<A<B≤10 ^4
【輸出格式】
按遞增順序列出給定閉區(qū)間 [A,B] 內(nèi)的所有特立獨行的幸福數(shù)和它的獨立性。每對數(shù)字占一行,數(shù)字間以 1 個空格分隔。
如果區(qū)間內(nèi)沒有幸福數(shù),則在一行中輸出 SAD。
【輸入樣例 1】10 40
【輸出樣例 1】
19 8
23 6
28 3
31 4
32 3
注意:樣例中,10、13 也都是幸福數(shù),但它們分別依附于其他數(shù)字(如 23、31 等等),所以不輸出。其它數(shù)字雖然其實也依附于其它幸福數(shù),但因為那些數(shù)字不在給定區(qū)間 [10, 40] 內(nèi),所以它們在給定區(qū)間內(nèi)是特立獨行的幸福數(shù)。
【輸入樣例 2】110 120
【輸出樣例 2】SAD
思路:寫dfs記錄迭代次數(shù)及范圍內(nèi)的非獨立幸福數(shù),最后判斷是否質(zhì)數(shù)后輸出獨立幸福數(shù)即可。
int visit[10001],a,b; struct node{int num,cnt;}; int dfs(int x,int cnt){//遞歸求解if (x==1) return cnt;if (cnt>1000) return -1;int res=0;while (x){//迭代一次res += (x%10)*(x%10);x/=10;}if (res>=a&&res<=b) visit[res]=1;//標記非獨立幸福數(shù)return dfs(res,cnt+1); } int zhi(int x){//是否素數(shù)if (x==1) return 1;if (x<4) return true;for (int i=2;i<int(sqrt(x))+1;i++){if (x%i==0) return 1;}return 2; } int main(){//輸入處理int t=0,c=0;cin>>a>>b;node ans[b-a+1];for (int i=a;i<=b;i++) visit[i]=0; for (int i=a;i<=b;i++){t = dfs(i,0);if(t!=-1) ans[c++]={i,t*zhi(i)};}//結(jié)果輸出,因為dfs過程中不能判斷是否為獨立幸福數(shù),所以輸出時才通過visit判斷for (int i=0;i<c;i++){if (visit[ans[i].num]==0) cout<<ans[i].num<<" "<<ans[i].cnt<<endl;//獨立}if (!c) cout<<"SAD";return 0; }2、深入虎穴 求和找起點+DFS求最大距離
已知情報藏在一個地下迷宮里,迷宮只有一個入口,里面有很多條通路,每條路通向一扇門。每一扇門背后或者是一個房間,或者又有很多條路,同樣是每條路通向一扇門…… 他的手里有一張表格,是其他間諜幫他收集到的情報,他們記下了每扇門的編號,以及這扇門背后的每一條通路所到達的門的編號。007 發(fā)現(xiàn)不存在兩條路通向同一扇門。
內(nèi)線告訴他,情報就藏在迷宮的最深處。但是這個迷宮太大了,他需要你的幫助 —— 請編程幫他找出距離入口最遠的那扇門。
【輸入格式】輸入首先在一行中給出正整數(shù) N(<10^5)是門的數(shù)量。最后 N 行,第 i 行(1≤i≤N)按以下格式描述編號為 i 的那扇門背后能通向的門:
K D[1] D[2] … D[K]
其中 K 是通道的數(shù)量,其后是每扇門的編號。
【輸出格式】在一行中輸出距離入口最遠的那扇門的編號。題目保證這樣的結(jié)果是唯一的。
【輸入樣例】
13
3 2 3 4
2 5 6
1 7
1 8
1 9
0
2 11 10
1 13
0
0
1 12
0
0
【輸出樣例】12
注意題目沒有提到起點門是誰,所以要自己算——沒有被當作通向門的那個編號!然后vector[]存儲每個門的通向信息,DFS搜索最大深度即可。
int n,res,maxi=0;//結(jié)果 int visit[100001];//訪問 vector<int> door[100001];//門的信息 void dfs(int x,int dep){if (dep>maxi){//最遠res=x;maxi=dep;}for (int i=0;i<door[x].size();i++){//遞歸搜索if (!visit[door[x][i]]){visit[door[x][i]]=1;dfs(door[x][i],dep+1);visit[door[x][i]]=0;}} } int main(){int k,in,r=0,s=0;cin>>n;map<int,int> have;for (int i=1;i<=n;i++,r+=i){cin>>k;for (int j=0;j<k;j++){cin>>in;door[i].push_back(in);if (have.find(in)==have.end()){//可被通的不是起點have[in]=1;s+=in;}}visit[i]=0;}s=r-n-s; //沒有提到的就是起點visit[s]=1;dfs(s,1);cout<<res;return 0; }3、功夫傳人
考察某一位祖師爺門下的徒子徒孫家譜:假設(shè)家譜中的每個人只有1位師傅(除了祖師爺沒有師傅);每位師傅可以帶很多徒弟;并且假設(shè)輩分嚴格有序,即祖師爺這門武功的每個第i代傳人只能在第i-1代傳人中拜1個師傅。我們假設(shè)已知祖師爺?shù)墓αχ禐閆,每向下傳承一代,就會減弱r%,除非某一代弟子得道則功力乘N。
【輸入樣例】
10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3
【輸出樣例】404
現(xiàn)給出師門譜系關(guān)系,要求你算出所有得道者的功力總值。
思路: 先用vector[]存儲門派關(guān)系,double[]存儲是否得到秘籍功力加倍,后DFS遞歸得總和。
#include <vector> #define maxn 100001 double secret[maxn],result=0,z,r; //記錄功力乘數(shù) vector<int> info[maxn]; //記錄家譜 void dfs(int num,double power){if (secret[num]){result+=power*secret[num];return;}for (int i=0;i<info[num].size();i++) dfs(info[num][i],power*(100-r)/100); } int main(){int n,k,in;cin>>n>>z>>r;for (int i=0;i<n;i++) secret[i]=0; //秘籍=0for (int i=0;i<n;i++){cin>>k;if (k){for (int j=0;j<k;j++){cin>>in;info[i].push_back(in);}}else cin>>secret[i]; //乘數(shù)}dfs(0,z);cout<<int(result);return 0; }4、愿天下有情人終成兄妹
【輸入樣例】
24
00001 M 01111 -1
00002 F 02222 03333
00003 M 02222 03333
00004 F 04444 03333
00005 M 04444 05555
00006 F 04444 05555
00007 F 06666 07777
00008 M 06666 07777
00009 M 00001 00002
00010 M 00003 00006
00011 F 00005 00007
00012 F 00008 08888
00013 F 00009 00011
00014 M 00010 09999
00015 M 00010 09999
00016 M 10000 00012
00017 F -1 00012
00018 F 11000 00013
00019 F 11100 00018
00020 F 00015 11110
00021 M 11100 00020
00022 M 00016 -1
00023 M 10012 00017
00024 M 00022 10013
9
00021 00024
00019 00024
00011 00012
00022 00018
00001 00004
00013 00016
00017 00015
00019 00021
00010 00011
【輸出樣例】
Never Mind
Yes
Never Mind
No
Yes
No
Yes
No
No
思路: 先用vector[]存儲父母信息,int[]存儲性別:初始化為0,男=1女=2,題目可能會給無性別的人(?)。記得存儲父母信息時賦予性別,父母離異再婚家庭樹會變化。DFS每次先初始化標記數(shù)組,判斷到第4輩祖上因為同輩已占了一代。
#include <bits/stdc++.h> #include <vector> #define maxn 1000000 vector<int> info[maxn];//父母 int gender[maxn],visit[maxn],flag;//性別,五代標記,是否可婚 void dfs(int x,int depth){if (depth==4) return; //判斷到第4輩for (int i=0;i<info[x].size();i++){if (visit[info[x][i]]==0){visit[info[x][i]]=1; //走過此親戚dfs(info[x][i],depth+1);//下一代}else flag=1;}return; } int main(){int n,a,c,d;cin>>n;char b;memset(gender,0,sizeof(gender)); //性別初始化for (int i=0;i<n;i++){cin>>a>>b>>c>>d;if (b=='F') gender[a]=2;//女else if (b=='M') gender[a]=1;//男else continue;//不知道給的什么鬼if (c!=-1){//有父親info[a].push_back(c); gender[c]=1;}if (d!=-1){//有母親info[a].push_back(d); gender[d]=2;}}cin>>a;for (int i=0;i<a;i++){cin>>c>>d;if (gender[c]==gender[d]) cout<<"Never Mind\n";else{memset(visit,0,sizeof(visit));visit[c]=1,visit[d]=1;flag=0;dfs(c,0),dfs(d,0);if (flag) cout<<"No\n";else cout<<"Yes\n";}}return 0; }5、冰島人(判斷好繞TAT錯了測試3、6就得了20分,考試撈點分就撤,題目有問題)
冰島人沿用的是維京人古老的父系姓制,孩子的姓等于父親的名加后綴,如果是兒子就加 sson,女兒則加 sdottir。因為冰島人口較少,為避免近親繁衍,本地人交往前先用個 App 查一下兩人祖宗若干代有無聯(lián)系。本題就請你實現(xiàn)這個 App 的功能。
【輸入格式】輸入首先在第一行給出一個正整數(shù) N(1<N≤10^5),為當?shù)厝丝跀?shù)。隨后 N 行,每行給出一個人名,格式為:名 姓(帶性別后綴),兩個字符串均由不超過 20 個小寫的英文字母組成。維京人后裔是可以通過姓的后綴判斷其性別的,其他人則是在姓的后面加 m 表示男性、f 表示女性。題目保證給出的每個維京家族的起源人都是男性。
隨后一行給出正整數(shù) M,為查詢數(shù)量。隨后 M 行,每行給出一對人名,格式為:名1 姓1 名2 姓2。注意:這里的姓是不帶后綴的。四個字符串均由不超過 20 個小寫的英文字母組成。
題目保證不存在兩個人是同名的。
【輸出格式】
對每一個查詢,根據(jù)結(jié)果在一行內(nèi)顯示以下信息:
若兩人為異性,且五代以內(nèi)無公共祖先,則輸出 Yes;
若兩人為異性,但五代以內(nèi)(不包括第五代)有公共祖先,則輸出 No;
若兩人為同性,則輸出 Whatever;
若有一人不在名單內(nèi),則輸出 NA。
所謂“五代以內(nèi)無公共祖先”是指兩人的公共祖先(如果存在的話)必須比任何一方的曾祖父輩分高。
【輸入樣例】
15
chris smithm
adam smithm
bob adamsson
jack chrissson
bill chrissson
mike jacksson
steve billsson
tim mikesson
april mikesdottir
eric stevesson
tracy timsdottir
james ericsson
patrick jacksson
robin patricksson
will robinsson
6
tracy tim james eric
will robin tracy tim
april mike steve bill
bob adam eric steve
tracy tim tracy tim
x man april mikes
【輸出樣例】
Yes
No
No
Whatever
Whatever
NA
去掉后綴,用數(shù)組存信息、性別、男性名及出現(xiàn)過的人的哈希表。之后再遍歷找父親存入數(shù)組。分情況判斷,這個公共祖先太搞了(A是B的祖輩、AB的公共祖先并不都高于AB五代就是有公共祖先,AB無公共祖先或公共祖先都高于AB五代才沒有公共祖先),考試時撈15+就撤,現(xiàn)在寫的也許之后就看不懂了。根據(jù)過題得分推測題意,這題目也就。。。。。。
#include <vector> #include <map> string a,b,c,d; map<string,int> name,id;//姓氏及姓名哈希表 int parent[100005],visit[100005],flag;//記錄父母,五代標記及是否有共同祖輩 int dfs(int x,int depth){visit[x]=depth;if (parent[x]==-1) return depth;//結(jié)束else if ((name.find(a)!=name.end()&&parent[x]==name[a])||(name.find(c)!=name.end()&&parent[x]==name[c])){//嫡親return 0;}else if (visit[parent[x]]==0){//可往上追溯return dfs(parent[x],depth+1);}else{//公共祖先if (visit[parent[x]]>3&&depth+1>3) return 1;//五代內(nèi)無公共祖先return 0;} }; int main(){int n,m,x,y;cin>>n;string info[100005];int sex[100005];//性別表for (int i=0;i<n;i++){sex[i]=0;parent[i]=-1;}for (int i=0;i<n;i++){cin>>a>>b;//去掉后綴留名和姓d=b[b.size()-1];if (d=="m"){//男sex[i]=1;c=b.substr(0,b.size()-1);}else if (d=="f"){//女sex[i]=2;c=b.substr(0,b.size()-1);}else if (d=="n"){//冰島男sex[i]=1;c=b.substr(0,b.size()-4);}else{//冰島女sex[i]=2;c=b.substr(0,b.size()-7);}id[a+c]=i;//唯一身份標識符if (sex[i]==1) name[a]=i;//記錄男人的名info[i]=c;//先把姓都記錄下來,再看有無祖輩}for (int i=0;i<n;i++){if (name.find(info[i])!=name.end())//有父親parent[i]=name[info[i]];}cin>>m;while (m--){cin>>a>>b>>c>>d;if (id.find(a+b)==id.end()||id.find(c+d)==id.end())cout<<"NA\n";//有人沒性別,不在名單上else{x=sex[id[a+b]],y=sex[id[c+d]];if (x==y) cout<<"Whatever\n";//同性else{for (int i=0;i<n;i++) visit[i]=0;//初始化flag=1;x=dfs(name[b],1);y=dfs(name[d],1);//cout<<x<<" "<<y<<endl;if (x*y==0) cout<<"No\n";else cout<<"Yes\n";//無公共祖先/有五代外的公共祖先}}}return 0; }6、病毒溯源
【輸入樣例】
10
3 6 4 8
0
0
0
2 5 9
0
1 7
1 2
0
2 3 1
【輸出樣例】
4
0 4 9 1
思路:先用vector< int>[]存儲數(shù)據(jù),用int類型加減找出沒有作為變異病毒的源頭(題目說保證為1個),記得每組升序則替換一定為更長序列(保證輸出序列最小)。dfs時先push再dfs后pop,vector類型不僅能直接比大小,還能直接=賦值!
#include <bits/stdc++.h> #include <algorithm> #include <vector> #include <map> #define maxn 10000 vector<int> info[maxn]; //存儲信息 map<int,int> have;//是否作為變異病毒 vector<int> res,t; //結(jié)果列表和暫存 int maxi=0; void dfs(int root,int depth){if (depth>maxi){ //更長maxi = depth;res = t;}for (int i=0;i<info[root].size();i++){t.push_back(info[root][i]); //路徑加入dfs(info[root][i],depth+1);t.pop_back(); //再pop出來} } int main(){//輸入int n,k,in,sum=0;cin>>n;for (int i=0;i<n;i++){cin>>k;sum += i;for (int j=0;j<k;j++){cin>>in;info[i].push_back(in);if (have.find(in)==have.end()){//非源頭sum-=in;have[in]=1;}}sort(info[i].begin(),info[i].end()); //從小到大排列則替換一定變成更長序列}dfs(sum,0); //從源頭開始找cout<<1+res.size()<<"\n"<<sum;for (int i=0;i<maxi;i++) cout<<" "<<res[i];return 0; }7、湊零錢 01背包DP問題(DFS寫法-1分TAT)
你可以用任何星球的硬幣付錢,但是絕不找零,當然也不能欠債。韓梅梅手邊有 10^4枚來自各個星球的硬幣,需要請你幫她盤算一下,是否可能精確湊出要付的款額。
先把輸入的零錢存入vector并求和,若和小于m則直接輸出無解(一個測試點巨長!);否則升序排列找出可用的最大硬幣作為結(jié)束標志。dfs分情況:當前硬幣湊不出結(jié)果時,再不用當前硬幣湊結(jié)果,因為當前硬幣比下一枚小、更可能得到最小序列。vector好在可以直接賦值和比較大小,簡化判定過程。
【輸入樣例 1】
8 9
5 9 8 7 2 3 4 1
【輸出樣例 1】1 3 5
【輸入樣例 2】
4 8
7 2 4 3
【輸出樣例 2】No Solution
DP都可用遞歸解決,只是存在重疊子問題用DP更簡潔快速,大佬的碼有空學(xué)一下
8、至多刪三個字符
給定一個全部由小寫英文字母組成的字符串,允許你至多刪掉其中 3 個字符,結(jié)果可能有多少種不同的字符串?
【輸入格式】輸入在一行中給出全部由小寫英文字母組成的、長度在區(qū)間 [4, 10^6 ] 內(nèi)的字符串。
【輸出格式】在一行中輸出至多刪掉其中 3 個字符后不同字符串的個數(shù)。
【輸入樣例】ababcc
【輸出樣例】25
【提示】
刪掉 0 個字符得到 “ababcc”。
刪掉 1 個字符得到 “babcc”, “aabcc”, “abbcc”, “abacc” 和 “ababc”。
刪掉 2 個字符得到 “abcc”, “bbcc”, “bacc”, “babc”, “aacc”, “aabc”, “abbc”, “abac” 和 “abab”。
刪掉 3 個字符得到 “abc”, “bcc”, “acc”, “bbc”, “bac”, “bab”, “aac”, “aab”, “abb” 和 “aba”。
我用dfs+map記錄,常規(guī)操作就得了18分。。。這道題長得就像dp可我不會
#include <iostream> #include <map> using namespace std; map<string,int> res; void dfs(string in,int dep){if (dep==4) return; //至多三個int x=in.size();string str=in.substr(1,x-1);//去掉第一個字符if (res.find(str)==res.end()){//沒有出現(xiàn)過res[str]=1;dfs(str,dep+1);}for (int i=1;i<x;i++){if (in[i]!=in[i-1]){//去掉這個字符是新效果str=in.substr(0,i)+in.substr(i+1,x-i-1);if (res.find(str)==res.end()){//沒有出現(xiàn)過res[str]=1;dfs(str,dep+1);}}} } int main(){string s; cin>>s;res[s]=1; dfs(s,1);cout<<res.size();return 0; }9、球隊食物鏈 學(xué)習(xí)DFS剪枝
【輸入樣例1】
5
-LWDW
W-LDW
WW-LW
DWW-W
DDLW-
【輸出樣例1】1 3 5 4 2
【輸入樣例2】
5
-WDDW
D-DWL
DD-DW
DDW-D
DDDD-
【輸出樣例2】No Solution
想著是簡單DFS,把所有W找出來搜索,結(jié)果只得了16分TAT。原因在于此矩陣并不對稱,所以W和L都有用,n最大是20則采用有向圖存儲戰(zhàn)勝邊。此時DFS超時,進行剪枝——剩下的隊伍要能戰(zhàn)勝首支才繼續(xù)進行這個方案,而按0-n的順序循環(huán)最先得到的結(jié)果一定是字典序最小的解,找到答案則不再搜索。
#include <vector> #define maxn 20 int n,s,visit[maxn],e[maxn][maxn]; vector<int> res; bool cut(){//剪枝for (int i=0;i<n;i++){if (!visit[i]&&e[i][s]) return true;//剩下的隊伍有可以戰(zhàn)勝首支隊伍的}return false;//都戰(zhàn)勝不了首支,那就不循環(huán)了 } bool dfs(int x,int dep){if (dep==n) return true;//有結(jié)果for (int i=0;cut()&&i<n;i++){if (!visit[i]&&e[x][i]){//戰(zhàn)勝visit[i]=1;res.push_back(i);if (dfs(i,dep+1)) return true;//已找到res.pop_back();visit[i]=0;}}return false;//沒有找到方法 } int main(){//輸入cin>>n;string in;for (int i=0;i<n;i++){cin>>in;for (int j=0;j<n;j++){if (in[j]=='W') e[i][j]=1;//i戰(zhàn)勝jelse if (in[j]=='L') e[j][i]=1;//j戰(zhàn)勝i}visit[i]=0;}//深搜for (int i=0;i<n;i++){s=i;//起點visit[s]=1;res.push_back(i);if (dfs(s,1)) break;//已找到答案就是最小字典序res.pop_back();visit[s]=0;}//輸出結(jié)果if (res.size()==n){for (int i=0;i<n-1;i++) cout<<res[i]+1<<" ";cout<<res[n-1]+1;}else cout<<"No Solution";return 0; }總結(jié)
以上是生活随笔為你收集整理的PTA天梯20+深度优先搜索及动态规划的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 获取农历日期二十四节气以及节假日的js包
- 下一篇: 【Golang项目实战】手把手教你写一个