PTA天梯20+深度优先搜索及动态规划

2022年4月17日下午13:30-16:30,模拟赛出现了手机小程序经常重连、PC端提交代码时服务器崩掉及排队时间过长的情况,只希望考试时不被误判作弊+顺利发挥得国奖,国二国三都可以,这一周尽力刷掉L3把往年例题吸烟刻肺(这个成语应该是这么用吧,书读的少没什么文化)。

DFS适用于计数及函数递推

1、特立独行的幸福 DFS递归基础+素数判断

对一个十进制数的各位数字做一次平方和,称作一次迭代。如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数。1 是一个幸福数。此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1。则 19 就是幸福数。显然,在一个幸福数迭代到 1 的过程中经过的数字都是幸福数,它们的幸福是依附于初始数字的。例如 82、68、100 的幸福是依附于 19 的。而一个特立独行的幸福数,是在一个有限的区间内不依附于任何其它数字的;其独立性就是依附于它的的幸福数的个数。如果这个数还是个素数,则其独立性加倍。例如 19 在区间[1, 100] 内就是一个特立独行的幸福数,其独立性为 2×4=8。
另一方面,如果一个大于1的数字经过数次迭代后进入了死循环,那这个数就不幸福。例如 29 迭代得到 85、89、145、42、20、4、16、37、58、89、…… 可见 89 到 58 形成了死循环,所以 29 就不幸福。
【输入格式】
输入在第一行给出闭区间的两个端点:1 【输出格式】
按递增顺序列出给定闭区间 [A,B] 内的所有特立独行的幸福数和它的独立性。每对数字占一行,数字间以 1 个空格分隔。
如果区间内没有幸福数,则在一行中输出 SAD。
【输入样例 1】10 40
【输出样例 1】
19 8
23 6
28 3
31 4
32 3
注意:样例中,10、13 也都是幸福数,但它们分别依附于其他数字(如 23、31 等等),所以不输出。其它数字虽然其实也依附于其它幸福数,但因为那些数字不在给定区间 [10, 40] 内,所以它们在给定区间内是特立独行的幸福数。
【输入样例 2】110 120
【输出样例 2】SAD

思路:写dfs记录迭代次数及范围内的非独立幸福数,最后判断是否质数后输出独立幸福数即可。

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;//标记非独立幸福数
    return dfs(res,cnt+1);
}
int zhi(int x){//是否素数
    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)};
    }
    //结果输出,因为dfs过程中不能判断是否为独立幸福数,所以输出时才通过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 发现不存在两条路通向同一扇门。
内线告诉他,情报就藏在迷宫的最深处。但是这个迷宫太大了,他需要你的帮助 —— 请编程帮他找出距离入口最远的那扇门。
【输入格式】输入首先在一行中给出正整数 N(<10^5)是门的数量。最后 N 行,第 i 行(1≤i≤N)按以下格式描述编号为 i 的那扇门背后能通向的门:
K D[1] D[2] … D[K]
其中 K 是通道的数量,其后是每扇门的编号。
【输出格式】在一行中输出距离入口最远的那扇门的编号。题目保证这样的结果是唯一的。
【输入样例】
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;//结果
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、功夫传人

考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i代传人只能在第i-1代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z,每向下传承一代,就会减弱r%,除非某一代弟子得道则功力乘N。PTA天梯20+深度优先搜索及动态规划_第1张图片
【输入样例】
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
现给出师门谱系关系,要求你算出所有得道者的功力总值。

思路: 先用vector[]存储门派关系,double[]存储是否得到秘籍功力加倍,后DFS递归得总和。

#include 
#define maxn 100001
double secret[maxn],result=0,z,r; //记录功力乘数
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; //秘籍=0
    for (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]; //乘数
    }
    dfs(0,z);
    cout<<int(result);
    return 0;
}

4、愿天下有情人终成兄妹

PTA天梯20+深度优先搜索及动态规划_第2张图片
【输入样例】
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每次先初始化标记数组,判断到第4辈祖上因为同辈已占了一代。

#include 
#include 
#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 查一下两人祖宗若干代有无联系。本题就请你实现这个 App 的功能。
【输入格式】输入首先在第一行给出一个正整数 N(1 随后一行给出正整数 M,为查询数量。随后 M 行,每行给出一对人名,格式为:名1 姓1 名2 姓2。注意:这里的姓是不带后缀的。四个字符串均由不超过 20 个小写的英文字母组成。
题目保证不存在两个人是同名的。
【输出格式】
对每一个查询,根据结果在一行内显示以下信息:
若两人为异性,且五代以内无公共祖先,则输出 Yes;
若两人为异性,但五代以内(不包括第五代)有公共祖先,则输出 No;
若两人为同性,则输出 Whatever;
若有一人不在名单内,则输出 NA。
所谓“五代以内无公共祖先”是指两人的公共祖先(如果存在的话)必须比任何一方的曾祖父辈分高。
【输入样例】
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

去掉后缀,用数组存信息、性别、男性名及出现过的人的哈希表。之后再遍历找父亲存入数组。分情况判断,这个公共祖先太搞了(A是B的祖辈、AB的公共祖先并不都高于AB五代就是有公共祖先,AB无公共祖先或公共祖先都高于AB五代才没有公共祖先),考试时捞15+就撤,现在写的也许之后就看不懂了。根据过题得分推测题意,这题目也就。。。。。。

#include 
#include 
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;//结束
    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;//五代内无公共祖先
        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<
                if (x*y==0) cout<<"No\n";
                else cout<<"Yes\n";//无公共祖先/有五代外的公共祖先
            }
         }
    }
    return 0;
}

6、病毒溯源

PTA天梯20+深度优先搜索及动态规划_第3张图片
【输入样例】
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>[]存储数据,用int类型加减找出没有作为变异病毒的源头(题目说保证为1个),记得每组升序则替换一定为更长序列(保证输出序列最小)。dfs时先push再dfs后pop,vector类型不仅能直接比大小,还能直接=赋值!

#include 
#include 
#include 
#include 
#define maxn 10000
vector<int> info[maxn]; //存储信息
map<int,int> have;//是否作为变异病毒
vector<int> res,t; //结果列表和暂存
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枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。
PTA天梯20+深度优先搜索及动态规划_第4张图片

先把输入的零钱存入vector并求和,若和小于m则直接输出无解(一个测试点巨长!);否则升序排列找出可用的最大硬币作为结束标志。dfs分情况:当前硬币凑不出结果时,再不用当前硬币凑结果,因为当前硬币比下一枚小、更可能得到最小序列。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

#include 
#include 
vector<int> money,res,v;
int n,m,in,sum=0;
bool dfs(int now,int index,vector<int> t){
    if (index==n||now>m) return false;//没有钱或超过指定数额
    if (now==m){//最小序列
        if (res>t) res=t;
        return true;
    }
    else{//还可以加
        vector<int> p=t;
        t.push_back(money[index]);
        if (!dfs(now+money[index],index+1,t)){//用这个金币凑不齐
            return dfs(now,index+1,p);//所以不能用这个金币
        }
        return true;
    }
}
int main(){
    cin>>n>>m;
    for (int i=0;i<n;i++){
        cin>>in;
        money.push_back(in);
        res.push_back(1e3);
        sum+=in;
    }
    if (sum<m) cout<<"No Solution";//所有硬币都凑不齐m
    else{
        sort(money.begin(),money.end());//升序
        for (int i=0;i<n;i++){//找可用的最大金币
            if (money[i]>m){
                n=i;
                break;
            }
        }
        if (!dfs(0,0,v)) cout<<"No Solution";
        else{
            for (int i=0;i<res.size()-1;i++) cout<<res[i]<<" ";
            cout<<res[res.size()-1];
        }  
    }
    return 0;
}

DP都可用递归解决,只是存在重叠子问题用DP更简洁快速,大佬的码有空学一下

8、至多删三个字符

给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串?
【输入格式】输入在一行中给出全部由小写英文字母组成的、长度在区间 [4, 10^6 ] 内的字符串。
【输出格式】在一行中输出至多删掉其中 3 个字符后不同字符串的个数。
【输入样例】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记录,常规操作就得了18分。。。这道题长得就像dp可我不会

#include 
#include 
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()){//没有出现过
        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()){//没有出现过
                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、球队食物链 学习DFS剪枝

PTA天梯20+深度优先搜索及动态规划_第5张图片
【输入样例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找出来搜索,结果只得了16分TAT。原因在于此矩阵并不对称,所以W和L都有用,n最大是20则采用有向图存储战胜边。此时DFS超时,进行剪枝——剩下的队伍要能战胜首支才继续进行这个方案,而按0-n的顺序循环最先得到的结果一定是字典序最小的解,找到答案则不再搜索。

#include 
#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;//剩下的队伍有可以战胜首支队伍的
    }
    return false;//都战胜不了首支,那就不循环了
}
bool dfs(int x,int dep){
    if (dep==n) return true;//有结果
    for (int i=0;cut()&&i<n;i++){
        if (!visit[i]&&e[x][i]){//战胜
            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战胜j
            else if (in[j]=='L') e[j][i]=1;//j战胜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;
    }
    //输出结果
    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;
}

你可能感兴趣的:(天梯,数据结构,算法,c++)