“大材小用”——甲级1032,1049

下面是PAT甲级的大纲要求:

  1. 具有充分的英文阅读理解能力;
  2. 理解并掌握基础数据结构,包括:线性表、树、图;
  3. 理解并熟练编程实现经典高级算法,包括哈希映射、并查集、最短路径、拓扑排序、关键路径、贪心、深度优先搜索、广度优先搜索、回溯剪枝等;
  4. 具备较强的问题抽象和建模能力,能实现对复杂实际问题的模拟求解。

这从表面来看,其实就是数据结构的教学内容,其实只要把数据结构学会了就可以考甲满了

而我们此次主题是从“高” 往 “低” 看PAT甲级,用最花里胡哨的算法实现最朴实无华的模拟题,在备战甲级枯燥的刷题中也有属于自己的一点小乐趣,话不多说,让我们开始吧。

首先是甲级1032 Sharing (25分)

To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the same suffix. For example, loading and being are stored as showed in Figure 1.

fig.jpg

Figure 1

You are supposed to find the starting position of the common suffix (e.g. the position of i in Figure 1).

Input Specification:

Each input file contains one test case. For each case, the first line contains two addresses of nodes and a positive N (≤105), where the two addresses are the addresses of the first nodes of the two words, and N is the total number of nodes. The address of a node is a 5-digit positive integer, and NULL is represented by −1.

Then N lines follow, each describes a node in the format:

Address Data Next

whereAddress is the position of the node, Data is the letter contained by this node which is an English letter chosen from { a-z, A-Z }, and Next is the position of the next node.

Output Specification:

For each case, simply output the 5-digit starting position of the common suffix. If the two words have no common suffix, output -1 instead.

Sample Input 1:

11111 22222 9
67890 i 00002
00010 a 12345
00003 g -1
12345 D 67890
00002 n 00003
22222 B 23456
11111 L 00001
23456 e 67890
00001 o 00010

Sample Output 1:

67890

Sample Input 2:

00001 00002 4
00001 a 10001
10001 s -1
00002 a 10002
10002 t -1

Sample Output 2:

-1

这一题乍一看 模拟链表遍历就可以轻松AC,代码量控制在30行左右。

而我们这次的主人公倍增法LCA就出现了。

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点X和Y最近的公共祖先。 ———来自百度百科

2

例如在这棵树中 9和10的LCA就是2, 8和12的LCA就是3。

在明白了LCA是啥之后,我们就要知道LCA怎么求了。

首先能想到的就是暴力解法,就是从给定的点开始一步一步往上跳,第一次相遇的点就是他们的LCA

例如9, 10两点:

9 -> 5 ->3 -> 2

10 -> 6 -> 4 -> 2

而这样的暴力解法在CA的题都会TLE(超时),所以我们就要进行优化,于是就有了倍增法优化的LCA算法。

  • 倍增算法

    倍增法也适用于树状动态规划,st表等算法。

    倍增法字面意思就是说成倍增长,也就是成倍增长的跳跃,按2的次方跳跃,1,2,4,8,16……而倍增的LCA则是从大到小选择跳的大小。

    例如 35 = 32 + 2 + 1.

    从这棵树 9和10两个节点来看

    9 -> 3 -> 2

    10 ->4 -> 2

    可以与上面暴力解法比较,大大减少了时间复杂度,时间复杂度已经减为O(nlogn),已经可以解决大部分问题。

    实现这个算法的预处理,是要在初始的时候更新每个节点 2的i次的父亲节点(初始的时候也要把每个log(i)+1的值存在数组里,是一步优化的预处理)

    void dfs(int now, int fath) {
        fa[now][0] = fath;
        depth[now] = depth[fath] + 1;
        for (register int i = 1; i <= lg[depth[now]]; ++i)
            fa[now][i] = fa[fa[now][i - 1]][i - 1];//更新倍增父亲节点
        for (register int i = head[now]; i; i = ed[i].next)
            if (ed[i].to != fath) dfs(ed[i].to, now);//每个节点都要更新
    }
    

    实现这个算法还要将这两个节点首先放到同一高度再统一开始跳。

    这个算法还有一个小问题,例如在查询3和6两个节点的LCA的时候,程序会误认为1是3,6节点的LCA,而其实2节点才是,所以我们在实现LCA查询的时候不能着急的让节点“往上爬”,只需要跳到目标的下一层即可。

    完整LCA代码实现甲级1032

    #include <iostream>
    #include <cstring>
    //#include <algorithm>
    #include <cstdio>
    using namespace std;
    
    const int maxn = 5 * 1e5 + 10;
    struct node {
        int to, next;
    } ed[maxn << 1];
    
    int num, n, m, s, st, goal;
    int head[maxn], fa[maxn][22], depth[maxn], lg[maxn];
    
    void add_edge(int x, int y) {
        ed[++num].next = head[x];
        ed[num].to = y;
        head[x] = num;
    }
    
    void dfs(int now, int fath) {
        fa[now][0] = fath;
        depth[now] = depth[fath] + 1;
        for (register int i = 1; i <= lg[depth[now]]; ++i)
            fa[now][i] = fa[fa[now][i - 1]][i - 1];
        for (register int i = head[now]; i; i = ed[i].next)
            if (ed[i].to != fath) dfs(ed[i].to, now);
    }
    
    int lca(int x, int y) {
        if (depth[x] < depth[y]) swap(x, y);
        while (depth[x] > depth[y])
            x = fa[x][lg[depth[x] - depth[y]] - 1];//将两个节点跳到同一高度
        if (x == y) return x;
        for (register int i = lg[depth[x]] - 1; i >= 0; --i) {
            if (fa[x][i] != fa[y][i]) {
                x = fa[x][i];
                y = fa[y][i];//LCA的算法实现,跳到目标的下一层即可
            }
        }
        return fa[x][0];
    }
    
    int main() {
        scanf("%d%d%d", &st, &goal, &n);
        for (register int i = 1; i <= n; ++i) {
            int a, b;
            char ch;
            cin >> a >> ch >> b;
            add_edge(b, a);
        }
        for (register int i = 1; i <= n; ++i)
            lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);//预处理log(i) + 1的值
        dfs(-1, 0);
        int ans = lca(st, goal);
        if(ans == 0 || ans == -1) cout << -1;
        else printf("%05d", ans);
        return 0;
    }
    

    下面再提供一道用数位动态规划来解决简单的模拟题(甲级1049 Counting Ones (30分))

    有兴趣的可以研究学习一下,动态规划可简单了

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include <iostream>
    
    using namespace std;
    
    typedef long long LL;
    LL ans = 0;
    int len = 0, b;
    int x[15][15];
    int t[15];
    
    LL DFS(int cnt, int pre, int limit) {
        if (cnt < 1) return 0;
        if (!limit && x[cnt][pre] != -1) return x[cnt][pre];
        LL anw = 0;
        int rest = 0;
        if (limit) rest = t[cnt];
        else rest = 9;
        for (int i = 0; i <= rest; i++) {
            if (i == b && limit && i == rest) {
                LL temp = 1;
                for (int tt = cnt - 1; tt >= 1; tt--) {
                    temp += t[tt] * pow(10, tt - 1);
                }
                anw += temp;
            } else if (i == b) {
                anw += pow(10, cnt - 1);
            }
            anw += DFS(cnt - 1, i, limit && i == rest);
        }
        if (!limit) x[cnt][pre] = anw;
        return anw;
    }
    
    LL DP(LL a) {
        int i;
        for (i = 1; a; i++) {
            t[i] = a % 10;
            a /= 10;
        }
        len = i - 1;
        memset(x, -1, sizeof(x));
        return DFS(len, 12, 1);
    }
    
    int main() {
        LL a;
        scanf("%lld %d", &a);
        b = 1;
        printf("%lld", DP(a));
        return 0;
    }
    

    谢谢大家~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值