PAT甲级学习笔记(一)

##以下PAT链表数据结构相关题目
1.A1032Sharing。本题要求你写一个程序找出两个链表中第一个相同的结点的位置,先用list存储结点,遍历第一个链表(注意遍历链表的写法)将所有结点标记为true,再遍历第二个链表,当出现第一个true的时候打印此时的结点地址,也就是list的下标,相对简单,注意这道题目中主函数中有两个返回出口

#include <iostream>
#include <vector>
using namespace std;
struct node{
    char data;
    int next;
    bool flag;
} list[100000];
int main(){
    int start1, start2, n;
    scanf("%d %d %d", &start1, &start2, &n);
    
    for(int i = 0; i < n; i++){
        char c;
        int a, b;
        scanf("%d %c %d",&a, &c, &b);
        list[a] = {c, b, false};
    }
    for(int i = start1; i != -1; i = list[i].next){
        list[i].flag = true;
    }
    for(int i = start2; i != -1; i = list[i].next){
        if(list[i].flag == true){
            printf("%05d",i);
            return 0;
        }
    }
    printf("-1");
    return 0;
}   

2.A10521052 Linked List Sorting。注意链表元素不在结构中的情况一定要考虑进去,这道题没做出来是因为不会链表的赋值操作,注意柳神代码中的list[a] = {a, b, c, false}的写法。本题的第四个测试点考察如果链表元素为0的时候应该输出0 -1,所以一定要考虑进去

#include <iostream>
#include <algorithm>
using namespace std;
struct node{
    int address, key, next;
    bool flag;
}list[100005];
bool cmp(node a, node b){
    return !a.flag || !b.flag ? a.flag > b.flag : a.key < b.key;
}
int main(){
    int n, first, a, b, c, sum = 0;
    scanf("%d %d", &n, &first);
    for(int i = 0; i < n; i++){
        scanf("%d %d %d", &a, &b, &c);
        list[a] = {a, b, c, false};
    }
    for(int p = first; p != -1; p = list[p].next ){
        sum++;
        list[p].flag = true;
    }
    sort(list, list + 100000, cmp);
    if(sum == 0) printf("0 -1");
    else{
        printf("%d %05d\n", sum, list[0].address);
        for(int i = 0; i < sum; i++){
            if(i == 0){
                printf("%05d %d", list[i].address, list[i].key);
            }else{
                printf(" %05d\n", list[i].address);
                printf("%05d %d",list[i].address, list[i].key);
            }
        } 
        printf(" -1");
    }
    
    return 0;
} 

3.A1097 Deduplication on a Linked List.这道题先输入链表,然后用flag进行标记,已经输出过的地址的flag标记为1,先输出统计没有重复的,然后把重复的赋值给ans,最后再输出。注意题目中的数组大小,避免如果ans数组开小了,会出现段错误

#include <iostream>
#include <algorithm>
using namespace std;
struct node {
    int data,next;
}list[100005];

struct node1{
    int address, data;
}ans[100005];

int flag[10005] = {0};
int main(){
    int first, n;
    scanf("%d %d", &first, &n);
    for(int i = 0; i < n; i++){
        int temp;
        scanf("%d", &temp);
        scanf("%d %d",&list[temp].data, &list[temp].next);
    }
    int sgn = 0, sum = 0;
    for(int p = first; p != -1; p = list[p].next){
        if(sgn == 0){
            printf("%05d %d ", p, list[p].data);
            flag[abs(list[p].data)] = 1;
            sgn = 1; 
        }else{
            if(flag[abs(list[p].data)] == 0){
                printf("%05d\n", p);
                printf("%05d %d ", p, list[p].data);
                flag[abs(list[p].data)] = 1;
            }
            else {
                ans[sum].address = p;
                ans[sum].data = list[p].data;
                sum++;

            }    
        }
    }
    printf("-1\n");
    
    int sgn1 = 0;
    for(int i = 0; i < sum ; i++){
        if(sgn1 == 0){
            printf("%05d %d", ans[i].address,ans[i].data);
            sgn1 = 1; 
        }else{
            printf(" %05d\n", ans[i].address);
            printf("%05d %d", ans[i].address, ans[i].data);
        }
    }
    if(sum != 0) printf(" -1");
    return 0;
}  

柳神的代码使用定义结构体中,增加一个num标记,不需要删除的结点num等于cnt1,需要删除的结点等于cnt2+max,然后按照num大小进行排序,再输出就会得到最后的结果。真的很巧妙。
4.A1074Reversing Linked List。这道题有定义变量的时候,next可能是头文件的关键字,所有如果把next数组定义在main函数外边会出现编译不通过的现象。题目中说的是每k个元素反转一次,认真读题,使用数组的一个好处是可以使用sort函数,更加方便,柳神甲级和乙级的题解不太一样,甲级没有使用sort函数,比较麻烦。

#include <iostream>
#include <algorithm>
using namespace std;
int list[100000], data[100000], ans[100000];
int next0[100000];
int main(){
    int first, n, k;
    int temp;
    
    scanf("%d %d %d", &first, &n, &k);
    for(int i = 0; i < n; i++){
        cin >> temp;
        cin >> data[temp] >> next0[temp];
    }
    int sum = 0;
    while(first != -1){
        list[sum++] = first;
        first = next0[first];
    }
    for(int i = 0; i < sum; i++) ans[i] = list[i];
    for(int i = 0; i < (sum - sum % k); i += k){//每k个结点一逆转 
        reverse(begin(list) + i, begin(list) + i + k ); 
    } 
    for(int i = 0; i < sum - 1; i++)
        printf("%05d %d %05d\n", list[i], data[list[i]], list[i + 1]);
    printf("%05d %d -1",list[sum - 1], data[list[sum - 1]]);    
    return 0;
}   

##以下是PAT甲级dfs题目
1.A1013 Battle Over Cities.这道题注意理解dfstrave的部分直接放在了main()函数当中。这是求连通分量的一个模板解法,要记下来

#include <iostream>
#include <algorithm>
using namespace std;
int G[1010][1010];
bool visit[1010];
int n;
void dfs(int u){
    visit[u] = true;
    for(int i = 1; i <= n; i++){
        if(visit[i] == false && G[u][i] == 1)//结点没有被访问,并且结点到下一个结点有路径 
            dfs(i);
    } 
}
int main(){
    int m, k, a, b;
    scanf("%d%d%d",&n, &m, &k);
    for(int i = 0; i < m; i++){
        scanf("%d%d",&a, &b);
        G[a][b] = G[b][a] = 1;
    }
    for(int i = 0; i < k; i++){
        fill(visit, visit + 1010, false);
        scanf("%d", &a);
        int cnt = 0;
        visit[a] = true;
        for(int j = 1; j <= n; j++){
            if(visit[j] == false){
                dfs(j);
                cnt++;
            }
        }
        printf("%d\n", cnt - 1);
    }
    return 0;
}

2.A1021 Deepest Root。这道题注意用vector存储图的情况。连通分量的求法同1013.两次遍历就可以求出来所有最大深度的根

#include <iostream>
#include <vector>
#include <algorithm> 
#include <set>
using namespace std;
int n, maxheight = 0;
vector<vector<int> > v;//用来存储图 
bool visit[10010];
set<int> s;
vector<int> temp;
void dfs(int node, int height){
    if(height > maxheight){
        temp.clear();
        temp.push_back(node);
        maxheight = height;
    }else if(height == maxheight){
        temp.push_back(node);
    }
    visit[node] = true;
    for(int i = 0; i < v[node].size(); i++){
        if(visit[v[node][i]] == false){
            dfs(v[node][i], height + 1);
        }
    }
}
int main(){
    scanf("%d", &n);
    v.resize(n + 1);//必须要有 
    int cnt = 0, s1 = 0, a, b;
    for(int i = 0; i < n - 1; i++){
        scanf("%d%d", &a, &b);
        v[a].push_back(b);
        v[b].push_back(a);
    }
    for(int i = 1; i <= n; i++){
        if(visit[i] == false){
            dfs(i, 1);
            if(i == 1){
                if(temp.size() != 0) s1 = temp[0];
                for(int j = 0; j < temp.size(); j++)
                    s.insert(temp[j]);
            }
            cnt++;
        }
    }
    if(cnt >= 2){
        printf("Error: %d components", cnt);
    }else{
        temp.clear();
        maxheight = 0;
        fill(visit, visit + 10010, false);
        dfs(s1, 1);
        for(int i = 0; i < temp.size(); i++)
            s.insert(temp[i]);
        for(auto it = s.begin(); it != s.end(); it++)
            printf("%d\n", *it);
    } 
    return 0;
}  

3.A1103Integer Factorization 这道题要注意剪枝

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int n, k, p, maxFacSum = -1;
vector<int> v, ans, tempAns;
void init(){
    int temp = 0, index = 1;
    while(temp <= n){
        v.push_back(temp);
        temp = pow(index, p);
        index++;
    }
}
void dfs(int index, int tempSum, int tempK, int facSum){
    if(tempK == k){
        if(tempSum == n && facSum > maxFacSum){
            ans = tempAns;//两个容器直接相等了
            maxFacSum = facSum; 
        }
        return;//表明得到了结果直接退出dfs 
    }
    while(index >= 1){
        if(tempSum + v[index] <= n){
            tempAns[tempK] = index;
            dfs(index, tempSum + v[index], tempK + 1, facSum + index);
        }
        if(index == 1) return;
        index--;
    } 
}
int main(){
    scanf("%d%d%d",&n, &k, &p);
    init();
    tempAns.resize(k);
    dfs(v.size() - 1, 0, 0, 0);
    if(maxFacSum == -1){
        printf("Impossible");
        return 0;
    }
    printf("%d = ", n);
    for(int i = 0; i < ans.size(); i++){
        if(i != 0) printf(" + ");
        printf("%d^%d", ans[i], p);
    }
    return 0;
}

4.A1131Subway Map 代码都看不懂

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
vector<vector<int> > v(10000);
int visit[10000], minCnt, minTransfer, start, end1;
unordered_map<int, int> line;
vector<int> path, tempPath;
int transferCnt(vector<int> a){
    int cnt = -1, preLine = 0;//两个站相邻也需要一次换乘,preLine为0 
    for(int i = 1; i < a.size(); i++){
        if(line[a[i-1]*10000+a[i]] != preLine) cnt++;
        preLine = line[a[i-1]*10000+a[i]];
    }
    return cnt;
} 
void dfs(int node, int cnt){
    if(node == end1 && (cnt < minCnt || (cnt == minCnt && transferCnt(tempPath) < minTransfer))){
        minCnt = cnt;
        minTransfer = transferCnt(tempPath);
        path = tempPath;
    }
    if(node == end1) return;//到了最后一个结点,直接退出
    for(int i = 0; i < v[node].size(); i++){
        if(visit[v[node][i]] == 0){
            visit[v[node][i]] = 1;
            tempPath.push_back(v[node][i]);
            dfs(v[node][i], cnt + 1);
            visit[v[node][i]] = 0; 
            tempPath.pop_back();
        }
    } 
}
int main(){
    int n, m, k, pre, temp;
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
        scanf("%d%d", &m, &pre);
        for(int j = 1; j < m; j++){
            scanf("%d", &temp);
            v[pre].push_back(temp);
            v[temp].push_back(pre);
            line[pre*10000+temp] = line[temp*10000+pre] = i+1;
            pre = temp;
        }
    }
    scanf("%d", &k);
    for(int i = 0; i < k; i++){
        scanf("%d%d", &start, &end1);
        minCnt = 99999, minTransfer = 99999;
        tempPath.clear();
        tempPath.push_back(start);
        visit[start] = 1;//设置第一项为已经访问,之后要清空 
        dfs(start, 0);
        visit[start] = 0;
        printf("%d\n", minCnt);
        int preLine = 0, preTransfer = start;
        for(int j = 1; j < path.size(); j++){
            if(line[path[j-1]*10000+path[j]] != preLine){
                if(preLine != 0){
                    printf("Take Line#%d from %04d to %04d.\n", preLine, preTransfer, path[j-1]);
                }
                preLine = line[path[j-1]*10000+path[j]];
                preTransfer = path[j-1];
            }
        }
         printf("Take Line#%d from %04d to %04d.\n", preLine, preTransfer,end1);
    }
    return 0;
}  

5.1130 Infix Expression (25分)

#include <iostream>
using namespace std;
struct node {
    string data;
    int l, r;
}a[100];//任何一个结点都包括一个string数据,和两个int型孩子
string dfs(int root){
    
    if(a[root].l == -1 && a[root].r != -1) return "(" + a[root].data + dfs(a[root].r) + ")";
    
    if(a[root].l != -1 && a[root].r != -1) return "(" + dfs(a[root].l) + a[root].data + dfs(a[root].r) + ")";
    if(a[root].l == -1 && a[root].r == -1) return a[root].data;//表示从叶子结点网上返回 
}

int main(){
    int n, judge[100] = {0}, root = 1;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i].data >> a[i].l >> a[i].r;
        if(a[i].l != -1) judge[a[i].l] = 1;
        if(a[i].r != -1) judge[a[i].r] = 1;
    }
    while(judge[root] == 1) root++;//用来计算根节点的序号。即所有的孩子结点都没有它 
    string ans = dfs(root);
    if(ans[0] == '(') ans = ans.substr(1, ans.size() - 2);
    cout << ans;
    
    return 0;
}   

6.A1142 Maximal Clique (25分)

#include <iostream>
#include <vector>
using namespace std;
int e[210][210];
int main(){
    int nv, ne, m, ta, tb, k;
    scanf("%d%d",&nv, &ne);
    for(int i = 0; i < ne; i++){
        scanf("%d%d", &ta, &tb);
        e[ta][tb] = e[tb][ta] = 1; 
    }
    scanf("%d", &m);
    for(int i = 0; i < m; i++){
        scanf("%d", &k);
        vector<int> v(k);//注意学习这种存储方式,表示大小为k 
        int hash[210] = {0}, isclique = 1, ismaximal = 1;
        for(int j = 0; j < k; j++){
            scanf("%d",&v[j]);//不需要设置临时变量,直接读入容器 
            hash[v[j]] = 1;//相当于vis 
        }
        
        for(int j = 0; j < k; j++){
            if(isclique == 0) break;//再跳出来上一层循环 
            for(int l = j + 1; l < k; l++){
                if(e[v[j]][v[l]] == 0){
                    isclique = 0;
                    printf("Not a Clique\n");
                    break;//只能跳出一层循环 
                }
            }
        } 
        
        if(isclique == 0) continue;//表示判断结束了,直接下一组数据 
        for(int j = 1; j <= nv; j++){
            if(hash[j] == 0){
                for(int l = 0; l < k; l++){
                    if(e[v[l]][j] == 0) break;
                    if(l == k - 1) ismaximal = 0;//表明里面的结点都选完了 
                }
            }
            if(ismaximal == 0){
                printf("Not Maximal\n");
                break;
            }
        }
        if(ismaximal == 1) printf("Yes\n");
    }    
    return 0;
}   

7.1150 Travelling Salesman Problem (25分)

#include <iostream>
#include <vector>
#include <set>
using namespace std;
int e[210][210], n, m, k, ans = 99999999, ansid;
vector<int> v;
void check(int index){
    int sum = 0, cnt, flag = 1;
    scanf("%d", &cnt);
    set<int> s;
    vector <int> v(cnt);
    for(int i = 0; i < cnt; i++){
        scanf("%d", &v[i]);
        s.insert(v[i]);
    } 
    for(int i = 0; i < cnt - 1; i++){
        if(e[v[i]][v[i+1]] == 0) flag =0;
        sum += e[v[i]][v[i+1]];
    }
    if(flag == 0){
        printf("Path %d: NA (Not a TS cycle)\n", index);    
    }else if(v[0] != v[cnt-1] || s.size() != n){
        printf("Path %d: %d (Not a TS cycle)\n", index, sum);
    }else if(cnt != n+1){//真是巧妙 
        printf("Path %d: %d (TS cycle)\n", index, sum);
        if(sum < ans){
            ans = sum;
            ansid = index;
        }
    }else{
        printf("Path %d: %d (TS simple cycle)\n", index, sum);
         if (sum < ans) {
             ans = sum;
             ansid = index;
         }
    }
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++){
        int t1, t2, t;
        scanf("%d%d%d", &t1, &t2, &t);
        e[t1][t2] = e[t2][t1] = t;
    }
    scanf("%d", &k);
    for(int i = 1; i<= k; i++) check(i);
    printf("Shortest Dist(%d) = %d\n", ansid, ans);
    return 0;    
}  

上面使用set来计算访问结点的个数真是巧妙,要学会;本题明显适应于使用函数的方法来解决问题,要有分段处理的思想;对于是不是旅行图的判断要认真学会。

8.1122 Hamiltonian Cycle (25分)

#include <iostream>
#include <vector>
#include <set>
using namespace std;
int G[210][210] = {0};
vector<int> path;
int main(){
    int n, m, ta, tb;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++){
        scanf("%d%d", &ta, &tb);
        G[ta][tb] = G[tb][ta] = 1;
    }
    int k;
    scanf("%d", &k);
    for(int i = 0; i < k; i++){
        int n1, start, t;
        path.clear();
        set<int> s;
        scanf("%d%d", &n1, &start);
        path.push_back(start);
        for(int j = 0; j < n1-1; j++){
            scanf("%d", &t);
            path.push_back(t);
            s.insert(t);
        }
        int flag = 1;
        for(int j = 0; j < n1 - 1; j++){
            if(G[path[j]][path[j+1]] == 0) flag = 0;
        }
//        cout << flag <<"$$" << endl;
        if(flag == 0){
            printf("NO\n");
            continue;
        }
        if(start == path[n1-1] && s.size() == n && n == n1-1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
} 

这是第一道自己做出来的图的题。注意使用数组的时候,一定要注意会不会越界,尤其是path[n-2]这样的情况,否则会出现段错误;这道题需要考虑道路走不通的情况;按照柳神的代码,优化的地方有:1.只有计数功能的循环,可以直接使用while(cnt–)这样的形式。2.分析问题的时候可以画出顺序图,这道题,多走,少走,或者成环设置成flag1, 道路走不通设置成flag2,逻辑就十分清晰。