PAT甲级

 

此源码来源为柳婼 の blog

 

 

pat甲级 1003 Emergency

#include <iostream>
#include <algorithm>
using namespace std;
int n, m, c1, c2;
int e[510][510], weight[510], dis[510], num[510], w[510];
bool visit[510];
const int inf = 99999999;
int main() {
    scanf("%d%d%d%d", &n, &m, &c1, &c2);
    for(int i = 0; i < n; i++)
        scanf("%d", &weight[i]);
    fill(e[0], e[0] + 510 * 510, inf);
    fill(dis, dis + 510, inf);
    int a, b, c;
    for(int i = 0; i < m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        e[a][b] = e[b][a] = c;
    }
    dis[c1] = 0;
    w[c1] = weight[c1];
    num[c1] = 1;
    for(int i = 0; i < n; i++) {
        int u = -1, minn = inf;
        for(int j = 0; j < n; j++) {
            if(visit[j] == false && dis[j] < minn) {
                u = j;
                minn = dis[j];
            }
        }
        if(u == -1) break;
        visit[u] = true;
        for(int v = 0; v < n; v++) {
            if(visit[v] == false && e[u][v] != inf) {
                if(dis[u] + e[u][v] < dis[v]) {
                    dis[v] = dis[u] + e[u][v];
                    num[v] = num[u];
                    w[v] = w[u] + weight[v];
                } else if(dis[u] + e[u][v] == dis[v]) {
                    num[v] = num[v] + num[u];
                    if(w[u] + weight[v] > w[v])
                        w[v] = w[u] + weight[v];
                }
            }
        }
    }
    printf("%d %d", num[c2], w[c2]);
    return 0;
}

该题目是迪杰斯特拉算法的应用

变量意义:

e[i][j]: 代表i点到j的边权,在这里是距离。若无法到达用大数inf表示

weight[u]: 代表u点的点权,在这里是救护人员的数量

dis[u]:  代表某个起点到u点的最小边权和,不能到达用大数inf表示

num[u]:  代表到u点的最短路径的数目

w[u]: 代表最后抵达终点u救护人员的数量(点权和)

分析:

这个无向带权图的存储用的是邻接矩阵法

    scanf("%d%d%d%d", &n, &m, &c1, &c2);
    for(int i = 0; i < n; i++)
        scanf("%d", &weight[i]);
    fill(e[0], e[0] + 510 * 510, inf);
    fill(dis, dis + 510, inf);
    int a, b, c;
    for(int i = 0; i < m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        e[a][b] = e[b][a] = c;
    }
    dis[c1] = 0;
    w[c1] = weight[c1];
    num[c1] = 1;

上图是初始化阶段,将邻接矩阵赋值,将dis[]的起点赋值为0,将救护人员的总数(点权和)赋值为起点的点权,将最短路径数量赋值为1。

    for(int i = 0; i < n; i++) {
        int u = -1, minn = inf;
        for(int j = 0; j < n; j++) {
            if(visit[j] == false && dis[j] < minn) {
                u = j;
                minn = dis[j];
            }
        }
        if(u == -1) break;
        visit[u] = true;
}

上图是用贪心法寻找路权最短的下一个点,若找到则在visit打上标记。

        for(int v = 0; v < n; v++) {
            if(visit[v] == false && e[u][v] != inf) {
                if(dis[u] + e[u][v] < dis[v]) {
                    dis[v] = dis[u] + e[u][v];
                    num[v] = num[u];
                    w[v] = w[u] + weight[v];
                } else if(dis[u] + e[u][v] == dis[v]) {
                    num[v] = num[v] + num[u];
                    if(w[u] + weight[v] > w[v])
                        w[v] = w[u] + weight[v];
                }
            }
        }

上图代码的目的主要是更新dis[]。因为上上图代码找到了一个路权最小的节点,所以这里的目的是要更新dis[],因为新加入了一个节点,dis[]的值会发生改变,原来可能访问不到的节点现在可能访问的到了,或者原来dis[]中的某个最短路权和出现了更小的情况。这就是上图第二个if里面的条件的内容。单纯的迪杰斯特拉算法就只有这个if了,但是该题要统计最短路径的数量和统计出救护人员的最大值(点权和),所以就有了第三个if的条件。

pat甲级 1013 Battle Over Cities

#include <cstdio>
#include <algorithm>
using namespace std;
int v[1010][1010];
bool visit[1010];
int n;
void dfs(int node) {
    visit[node] = true;
    for(int i = 1; i <= n; i++) {
        if(visit[i] == false && v[node][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);
        v[a][b] = v[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;
}

首先,这是一个连通图的问题。若V(G)中任意两个不同的顶点vi和vj都连通(即有路径),则称G为连通图(Connected Graph)。这句话的意思是这个图没有“孤立”的点,每个点都可以直接或间接的被访问。

无向图G的极大连通子图称为G的最强连通分量(Connected Component)。这句话可以理解为两个连通图放在一张图片里面,两个连通图之间没用连接,此时这两个连通图就是图G的连通分量。

那么我们想要让这两个连通图(图G的连通分量)相互之间连接起来怎么办呢?肯定是随便在这两个连通图分别随便找一点连接起来就好了。这就有了另一个结论,n个独立的连通分量要变成一个连通图时,我们需要用n-1条线进行连接。

代码分析:
int v[1010][1010];
bool visit[1010];

int m, k, a, b;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 0; i < m; i++) {
        scanf("%d%d", &a, &b);
        v[a][b] = v[b][a] = 1;
    }

v[][]: 存图的邻接矩阵,如果i点和j点相连,则v[i][j]为1,反之为0

visit[]: 每个节点的访问情况

m: 节点数量    n: 路径数量  k: 会被去掉的节点的数量

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);
    }

a: 要去除的节点,这里将要去除的节点事先打上visit标志。在打之前,首先要将visit表清空,防止上一次结果干扰,这是fill函数的作用。

第二个for循环是关键代码,它的功能是查找未被访问的节点,并将其深度优先搜索(dfs),cnt代表连通分量的个数,显而易见,每次dfs完之后,可以知道已经访问的节点是构成了连通分量的。所以下一个循环再次从为被访问的节点中dfs。当循环结束时,打印这个去除这个节点导致的结果 cnt-1(需要连多少条线)

void dfs(int node) {
    visit[node] = true;
    for(int i = 1; i <= n; i++) {
        if(visit[i] == false && v[node][i] == 1)
            dfs(i);
    }
}

这个dfs非常普通,给每个被访问过的节点打上标记什么的。

 

pat甲级 1010  Radix

这是一道关于进制的题目,大意就是给出两个数和其中一个数的进制,让你去求另外一个数的进制。我看来还是太菜了,题目都有点搞不清,这道题大致的做法是,从2进制开始试起,把两个数都转化成十进制,如果未知进制的数比已知进制的数要小,就未知进制就加1。当然这是最暴力的解法了,对与这种需要从有序数列里找到一个数的方法我们可以考虑用二分法。这道题其实挺简单的,就是我太菜了。

#include <iostream>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
long long convert(string n, long long radix) {
    long long sum = 0;
    int index = 0, temp = 0;
    for (auto it = n.rbegin(); it != n.rend(); it++) {
        temp = isdigit(*it) ? *it - '0' : *it - 'a' + 10;
        sum += temp * pow(radix, index++);
    }
    return sum;
}
long long find_radix(string n, long long num) {
    char it = *max_element(n.begin(), n.end());
    long long low = (isdigit(it) ? it - '0': it - 'a' + 10) + 1;
    long long high = max(num, low);
    while (low <= high) {
        long long mid = (low + high) / 2;
        long long t = convert(n, mid);
        if (t < 0 || t > num) high = mid - 1;
        else if (t == num) return mid;
        else low = mid + 1;
    }
    return -1;
}
int main() {
    string n1, n2;
    long long tag = 0, radix = 0, result_radix;
    cin >> n1 >> n2 >> tag >> radix;
    result_radix = tag == 1 ? find_radix(n2, convert(n1, radix)) : find_radix(n1, convert(n2, radix));
    if (result_radix != -1) {
        printf("%lld", result_radix);
    } else {
        printf("Impossible");
    }   
    return 0;
}

由于非常简单,就不在把代码详细拆分介绍了。不过需要记住的是这里引入了cctype这个库,这是一个功能齐全的字符处理库,基本上你想到的字符处理都在这里面了。第一个函数是convert,也就是将n进制转化成十进制的函数,由于在转化期间可能发生移除,就使用了long long来避免这个问题。

 

pat甲级  1048  Fild Coins

#include <iostream>
using namespace std;
int a[1001];
int main() {
    int n, m, temp;
    scanf("%d %d", &n, &m);
    for(int i = 0; i < n; i++) {
        scanf("%d", &temp);
        a[temp]++;
    }
    for(int i = 0; i < 1001; i++) {
        if(a[i]) {
            a[i]--;
            if(m > i && a[m-i]) {
                printf("%d %d", i, m - i);
                return 0;
            }
            a[i]++;
        }
    }
    printf("No Solution");
    return 0;
}

这道题同样非常简单,在第二个for循环里有a[i]--,然后一个if后出现了a[i]++,先减防止出现相同的数字,也可以说是标记吧,标记这个数字已经被使用过一次了。

 

pat甲级 1098  Insertion or Heap Sort

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> a, b;
void downAdjust(int low, int high) {
    int i = 1, j = i * 2;
    while(j <= high) {
        if(j + 1 <= high && b[j] < b[j + 1])
            j = j + 1;
        if(b[i] < b[j]) {
            swap(b[i], b[j]);
            i = j; j = i * 2;
        } else {
            break;
        }
    }
}
int main() {
    int n, p = 2;
    scanf("%d", &n);
    a.resize(n + 1), b.resize(n + 1);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        scanf("%d", &b[i]);
    while(p <= n && b[p - 1] <= b[p]) p++;
    int index = p;
    while(p <= n && a[p] == b[p]) p++;
    if(p == n + 1) {
        printf("Insertion Sort\n");
        sort(b.begin() + 1, b.begin() + index + 1);
        printf("%d", b[1]);
        for(int i = 2; i <= n; i++)
            printf(" %d", b[i]);
    } else {
        printf("Heap Sort\n");
        p = n;
        while(p >= 2 && b[p] >= b[p - 1]) p--;
        swap(b[1], b[p]);
        downAdjust(1, p - 1);
        printf("%d", b[1]);
        for(int i = 2; i <= n; i++)
            printf(" %d", b[i]);
    }
    return 0;
}

这道题考的是对于插入排序和堆排序原理的了解。首先我们需要了解什么是堆,堆是一种特殊的完全二叉树(从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐),它的特殊性在于所有父节点都要比其叶子节点要小或者大,小的叫做最小顶堆,大的叫做最大顶堆。堆的存储非常简单,可以放在一个普通的数组里面,我们所做的只是定义了数据的访问规则。

堆排序

具体可以参见这个文章  堆排序的步骤 但是要注意的一点是,每次完成一次交换(将所有节点重新调整,符合堆的规则)时,要考虑交换节点数据之后,结果会不会导致被交换节点的子树不符合堆的规则,所以要注意检查。

插入排序

这里的插入排序是从前面开始的,从前面扫描元素,验证这个元素是否大于后面这个元素,若大于,则将这个元素插入已经排好序的表里。

3 1 2 8 7 5 9 4 6 0

这里就可以看出来,从3开始,3比1大,选中3,向后扫描,寻找比3大的元素,插入到前面

1 2 3 8 7 5 9 4 6 0

这时候,前面四个元素变为已经排好序的表,此时从7开始继续向后选取元素,选中7,比较后面元素,发现7比5小,这个时候,将7插入到已经排好序的1 2 3 8中,此时变为

1 2 3 7 8 5 9 4 6 0

以此类推。

 

所以这就可以发现一个规律,在这些排序的中间步骤中,堆排序后面几个数字是排好序的,插入排序前面几个数字是排好序的。这就给出了这道题目第一个问题的答案,给定排序的中间结果,如何分辨插入排序和堆排序。

第二个问题就是两种排序的原理问题了,上文讲的已经很清楚了。 

代码分析:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> a, b;

int n, p = 2;
    scanf("%d", &n);
    a.resize(n + 1), b.resize(n + 1);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        scanf("%d", &b[i]);

a: 存放原始序列    b: 存放序列排序的中间结果。要注意的是这里a,b的长度设置为n+1是因为这里的序列下标打算从1开始。

while(p <= n && b[p - 1] <= b[p]) p++;
    int index = p;
    while(p <= n && a[p] == b[p]) p++;
    if(p == n + 1) {
        printf("Insertion Sort\n");
        sort(b.begin() + 1, b.begin() + index + 1);
        printf("%d", b[1]);
        for(int i = 2; i <= n; i++)
            printf(" %d", b[i]);
    } 

上图代码是在验证是否为插入排序。p的意义在于它要去验证这个序列是否符合上文讲的插入排序中间结果的规律,即是否前面的几个数字是有序的,后面几个数组没有改变。在验证中途,index存下了前面有序的子序列的长度(也是离有序子序列最近的一个没有纳入进有序子序列的数,比如序列 1 2 3 8 7 5 9 4 6 0时,这个index指向的就是7),在if中,这是为了用sort方法得出插入排序的下一次结果是什么。

else {
        printf("Heap Sort\n");
        p = n;
        while(p >= 2 && b[p] >= b[p - 1]) p--;
        swap(b[1], b[p]);
        downAdjust(1, p - 1);
        printf("%d", b[1]);
        for(int i = 2; i <= n; i++)
            printf(" %d", b[i]);
    }

这里是在得出下一步堆排序的结果,因为这个题目只有两个选择,不是插入就是堆。这里的p存的是前面无序子序列的最后一个值的下标。在进行根节点的值与最后一个叶子节点的交换之后,就要重新调整这个序列以满足堆的规则。

void downAdjust(int low, int high) {
    int i = 1, j = i * 2;
    while(j <= high) {
        if(j + 1 <= high && b[j] < b[j + 1])
            j = j + 1;
        if(b[i] < b[j]) {
            swap(b[i], b[j]);
            i = j; j = i * 2;
        } else {
            break;
        }
    }
}

上图代码展示的就是如何调整序列重新变成堆的。这里有两个规律就是,第i个节点的两个子节点序号分别为2*i和2*i+1(这里根节点是从1开始计数的,从0开始计数的话就是2*i+1和2*i+2),和堆最后一个非叶子节点的序号为i/2(从0计数的话是i/2-1)

 

pat甲级 1146   Topological Order

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m, a, b, k, flag = 0, in[1010];
    vector<int> v[1010];
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++) {
        scanf("%d %d", &a, &b);
        v[a].push_back(b);
        in[b]++;
    }
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        int judge = 1;
        vector<int> tin(in, in+n+1);
        for (int j = 0; j < n; j++) {
            scanf("%d", &a);
            if (tin[a] != 0) judge = 0;
            for (int it : v[a]) tin[it]--;
        }
        if (judge == 1) continue;
        printf("%s%d", flag == 1 ? " ": "", i);
        flag = 1;
    }
    return 0;
}

这个问题是关于拓扑排序的,拓扑排序原理非常简单,就是一个完全有向图,从入度为0的点开始,去掉这个点以及其关联的路径,并输出这个点。以此类推。这道题让我们判断这个排序是不是拓扑排序,那解法就很清晰了,就是照着给出的排序一个一个代进去,如果发现当前节点入度不为0,则输出这个排序的序号。

简单分析一下,这里的vector tin是从in中拷贝过来的vector(iterator,iterator+x)就可以拷贝这段序列过来。每次移除一个节点,都要在它减去它关联的路径。v[]是一个邻接矩阵。

 

pat甲级 1135  Is It A Red-Black Tree

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
vector<int> arr;
struct node {
    int val;
    struct node *left, *right;
};
node* build(node *root, int v) {
    if(root == NULL) {
        root = new node();
        root->val = v;
        root->left = root->right = NULL;
    } else if(abs(v) <= abs(root->val))
        root->left = build(root->left, v);
    else
        root->right = build(root->right, v);
    return root;
}
bool judge1(node *root) {
    if (root == NULL) return true;
    if (root->val < 0) {
        if (root->left != NULL && root->left->val < 0) return false;
        if (root->right != NULL && root->right->val < 0) return false;
    }
    return judge1(root->left) && judge1(root->right);
}
int getNum(node *root) {
    if (root == NULL) return 0;
    int l = getNum(root->left);
    int r = getNum(root->right);
    return root->val > 0 ? max(l, r) + 1 : max(l, r);
}
bool judge2(node *root) {
    if (root == NULL) return true;
    int l = getNum(root->left);
    int r = getNum(root->right);
    if(l != r) return false;
    return judge2(root->left) && judge2(root->right);
}
int main() {
    int k, n;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        scanf("%d", &n);
        arr.resize(n);
        node *root = NULL;
        for (int j = 0; j < n; j++) {
            scanf("%d", &arr[j]);
            root = build(root, arr[j]);
        }
        if (arr[0] < 0 || judge1(root) == false || judge2(root) == false)
            printf("No\n");
        else
            printf("Yes\n");
    }
    return 0;
}

这道题考查的是红黑树的构造条件。红黑树是一种特殊的二叉搜索树,满足3个条件

  • 根节点为黑色
  • 如果一个节点是红色,那么它的孩子节点全部都是黑色
  • 满足从任意一个节点访问叶子节点,所经过的黑色节点数目相等

代码分析:

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
vector<int> arr;
struct node {
    int val;
    struct node *left, *right;
};
int main() {
    int k, n;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        scanf("%d", &n);
        arr.resize(n);
        node *root = NULL;
        for (int j = 0; j < n; j++) {
            scanf("%d", &arr[j]);
            root = build(root, arr[j]);
        }
        if (arr[0] < 0 || judge1(root) == false || judge2(root) == false)
            printf("No\n");
        else
            printf("Yes\n");
    }
    return 0;
}

上图是代码的主要框架。该红黑树是用链表存储。

node* build(node *root, int v) {
    if(root == NULL) {
        root = new node();
        root->val = v;
        root->left = root->right = NULL;
    } else if(abs(v) <= abs(root->val))
        root->left = build(root->left, v);
    else
        root->right = build(root->right, v);
    return root;
}

上图代码是红黑树的构建过程。由于题目给出的输入是按照先序遍历,且上文所说红黑树是一中特殊的二叉树,所以就像代码中的elseif一样,判断如果该节点的孩子节点大于自身的值,就把它放在右孩子节点,反之,放进左孩子节点。

bool judge1(node *root) {
    if (root == NULL) return true;
    if (root->val < 0) {
        if (root->left != NULL && root->left->val < 0) return false;
        if (root->right != NULL && root->right->val < 0) return false;
    }
    return judge1(root->left) && judge1(root->right);
}

上图代码是进行第二个条件的判断,使用了递归。非常巧妙,至少对我这个菜鸡来说是这样。

int getNum(node *root) {
    if (root == NULL) return 0;
    int l = getNum(root->left);
    int r = getNum(root->right);
    return root->val > 0 ? max(l, r) + 1 : max(l, r);
}

上图是去获取从任意节点去访问叶子节点要经过的黑色节点数量,同样采用了递归。

bool judge2(node *root) {
    if (root == NULL) return true;
    int l = getNum(root->left);
    int r = getNum(root->right);
    if(l != r) return false;
    return judge2(root->left) && judge2(root->right);
}

这是对第三个条件的判断,同样是使用递归,递归的思想虽然听起来很简单,一个基本条件,然后保证所有递归都能到达这个基本条件上,但是换我实在是不太好想出来。

 

 

pat 甲级 1123 Is It a Complete AVL Tree

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct node {
    int val;
    struct node *left, *right;
};
node* leftRotate(node *tree) {
    node *temp = tree->right;
    tree->right = temp->left;
    temp->left = tree;
    return temp;
}
node* rightRotate(node *tree) {
    node *temp = tree->left;
    tree->left = temp->right;
    temp->right = tree;
    return temp;
}
node* leftRightRotate(node *tree) {
    tree->left = leftRotate(tree->left);
    return rightRotate(tree);
}
node* rightLeftRotate(node *tree) {
    tree->right = rightRotate(tree->right);
    return leftRotate(tree);
}
int getHeight(node *tree) {
    if (tree == NULL) return 0;
    int l = getHeight(tree->left);
    int r = getHeight(tree->right);
    return max(l, r) + 1;
}
node* insert(node *tree, int val) {
    if (tree == NULL) {
        tree = new node();
        tree->val = val;
    }else if (tree->val > val) {
        tree->left = insert(tree->left, val);
        int l = getHeight(tree->left), r = getHeight(tree->right);
        if (l - r >= 2) {
            if (val < tree->left->val)
                tree = rightRotate(tree);
            else
                tree = leftRightRotate(tree);
        }
    } else {
        tree->right = insert(tree->right, val);
        int l = getHeight(tree->left), r = getHeight(tree->right);
        if (r - l >= 2) {
            if (val > tree->right->val)
                tree = leftRotate(tree);
            else
                tree = rightLeftRotate(tree);
        }
    }
    return tree;
}
int isComplete = 1, after = 0;
vector<int> levelOrder(node *tree) {
    vector<int> v;
    queue<node *> queue;
    queue.push(tree);
    while (!queue.empty()) {
        node *temp = queue.front();
        queue.pop();
        v.push_back(temp->val);
        if (temp->left != NULL) {
            if (after) isComplete = 0;
            queue.push(temp->left);
        } else {
            after = 1;
        }
        if (temp->right != NULL) {
            if (after) isComplete = 0;
            queue.push(temp->right);
        } else {
            after = 1;
        }
    }
    return v;
}
int main() {
    int n, temp;
    scanf("%d", &n);
    node *tree = NULL;
    for (int i = 0; i < n; i++) {
        scanf("%d", &temp);
        tree = insert(tree, temp);
    }
    vector<int> v = levelOrder(tree);
    for (int i = 0; i < v.size(); i++) {
        if (i != 0) printf(" ");
        printf("%d", v[i]);
    }
    printf("\n%s", isComplete ? "YES" : "NO");
    return 0;
}

这道题目是考查了avl数、层序遍历和avl树的旋转问题。avl数是一种自平衡二叉搜索树,它的特点是:

1.本身首先是一棵二叉搜索树。

2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

当需要在avl树中插入节点时,就可能出现树不平衡的问题,某一个节点可能大于1。这时候就需要进行节点的旋转,使得avl树重新平衡。题目给出了几种例子。

以单右旋为例,最后得到的结果位置会变成:

原先的节点 -> 原先节点的左孩子节点的右孩子节点

原先节点的左孩子节点 ->  原先的节点

原先左孩子的右节点 ->  原先的节点的左孩子节点

至于LR和RL旋转的话,思路是一样的。

这道题目还有问题是,用层序遍历去判断这个树是不是完全二叉树。层序遍历使用队列去存储活节点的,非常类似bfs。

pat 甲级 1112 Stucked Keyboard

#include <iostream>
#include <map>
#include <cstdio>
#include <set>
using namespace std;
bool sureNoBroken[256];
int main() {
    int k, cnt = 1;
    scanf("%d", &k);
    string s;
    cin >> s;
    map<char, bool> m;
    set<char> printed;
    char pre = '#';
    s = s + '#';
    for(int i = 0; i < s.length(); i++) {
        if(s[i] == pre) {
            cnt++;
        } else {
            if(cnt % k != 0) {
                sureNoBroken[pre] = true;
            }
            cnt = 1;
        }
        if(i != s.length() - 1) m[s[i]] = (cnt % k == 0);
        pre = s[i];
    }
    for(int i = 0; i < s.length() - 1; i++) {
        if(sureNoBroken[s[i]] == true)
            m[s[i]] = false;
    }
    for(int i = 0; i < s.length() - 1; i++) {
        if(m[s[i]] && printed.find(s[i]) == printed.end()) {
            printf("%c", s[i]);
            printed.insert(s[i]);
        }
    }
    printf("\n");
    for(int i = 0; i < s.length() - 1; i++) {
        printf("%c", s[i]);
        if(m[s[i]])
            i = i + k - 1;
    }
    return 0;
}

这也是一道水题,题目给出两个数据,一个是坏键出现的特征(按下坏键打出多少个字母),一个是键盘打出的文本。这道题要找出坏键,要有两个条件:第一是这个字母要满足连续出现的次数在3次以上,第二是确认这个字母没有单独出现小于3次的情况(有可能人为按了按键3次以上)。验证第一个条件非常简单,建立一个大小为256的map<char,bool>。并在循环里统计字母连续出现的次数,将其对k取余,就可得到是否坏键。注意这里满足了第一个条件,但是需要考虑,这个字母是否之前或者之后会有连续出现小于3次的情况 (为了验证是否人为输入3次以上)。就用了另外一个保险sureNoBroken数组,有人可能会奇怪为什么要不能只用一个map呢?替换掉第一个for中的第二个if里面的 sureNoBroken[pre] = true;   -> map[pre] = false;

这样的话,万一某个字母在连续出现3次以上之前,已经在某个位置连续出现了小于3次,这个map还是会被设成false。就像这个序列

Thiss_isss_wrong

s肯定不是坏键。

所以这道题带给我的启示是,如果一个题目的解要求满足两个条件以上时,老老实实写上这两个条件,不要想着把它简化成一个条件,因为有时这样虽然能成功了,但对我这种新手来说,往往会在简化中漏掉几个条件,并且很难找出问题来,所以一定要考虑所有条件尤其是极限条件。

 

pat 甲级 1018 Public Bike Management

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int inf = 99999999;
int cmax, n, sp, m;
int minNeed = inf, minBack = inf;
int e[510][510], dis[510], weight[510];
bool visit[510];
vector<int> pre[510];
vector<int> path, temppath;
void dfs(int v) {
    temppath.push_back(v);
    if(v == 0) {
        int need = 0, back = 0;
        for(int i = temppath.size() - 1; i >= 0; i--) {
            int id = temppath[i];
            if(weight[id] > 0) {
                back += weight[id];
            } else {
                if(back > (0 - weight[id])) {
                    back += weight[id];
                } else {
                    need += ((0 - weight[id]) - back);
                    back = 0;
                }
            }
        }
        if(need < minNeed) {
            minNeed = need;
            minBack = back;
            path = temppath;
        } else if(need == minNeed && back < minBack) {
            minBack = back;
            path = temppath;
        }
        temppath.pop_back();
        return ;
    }
    for(int i = 0; i < pre[v].size(); i++)
        dfs(pre[v][i]);
    temppath.pop_back();
}
int main() {
    fill(e[0], e[0] + 510 * 510, inf);
    fill(dis, dis + 510, inf);
    scanf("%d%d%d%d", &cmax, &n, &sp, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &weight[i]);
        weight[i] = weight[i] - cmax / 2;
    }
    for(int i = 0; i < m; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        scanf("%d", &e[a][b]);
        e[b][a] = e[a][b];
    }
    dis[0] = 0;
    for(int i = 0; i <= n; i++) {
        int u = -1, minn = inf;
        for(int j = 0; j <= n; j++) {
            if(visit[j] == false && dis[j] < minn) {
                u = j;
                minn = dis[j];
            }
        }
        if(u == -1) break;
        visit[u] = true;
        for(int v = 0; v <= n; v++) {
            if(visit[v] == false && e[u][v] != inf) {
                if(dis[v] > dis[u] + e[u][v]) {
                    dis[v] = dis[u] + e[u][v];
                    pre[v].clear();
                    pre[v].push_back(u);
                }else if(dis[v] == dis[u] + e[u][v]) {
                    pre[v].push_back(u);
                }
            }
        }
    }
    dfs(sp);
    printf("%d 0", minNeed);
    for(int i = path.size() - 2; i >= 0; i--)
        printf("->%d", path[i]);
    printf(" %d", minBack);
    return 0;
}

本来是想拿这个题目练练dfs和迪杰斯特拉的,结果没想到碰到一个这么难的.

ps:我之前写的好像没有保存下来,又要重写一遍了。这个题目的主要思路是要先用迪杰斯特拉算法得出从起点到任何一点的最短路径(用pre数组保存),然后通过一些遍历选出到某个特定点的路径(在题目中是问题点,然后得出的路径可能不止一条。),之后通过题目所给条件选出这个路径。由于我实在不太想再重写了,我就稍微总结一下吧。

迪杰斯特拉算法的关键元素有:

road[i][j]: 点i到点j的距离

visit[i]: 点i的访问状态

dst[i]: 从起点到i点的最短路径(算法内要不断更新)

如果你要像这道题目一样,存下如何走最短路径的方法,那就用pre[][]数组,pre[][]数组是从终点朝着起点方向的。

 

转载于:https://my.oschina.net/u/3118955/blog/1941147

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值