目录
前言
本人为数据结构初学者,前几天偶然在网上看到南京大学2020年复试机试题目,心血来潮做了下。因我学识尚浅,网络上该题目的参考资料很少,过程中遇到了许多困难,总归是马马虎虎解决。如果过程中有不当之处,欢迎各位批评指正。
本文所有代码与文字均为本人编写,仅供参考交流,请勿用于商业用途。
题目概述
原题
话不多说,放原题。
A国的电力网络是以有根树的形式分布,中央电站位于根部,各城市位于树叶位置。电力通过各城市延伸的电线从中央电站传输到各城市。各城市均连接了电线。
现在,敌对B国的军队突袭了一些城市。作为A国的国王,你非常担心,想要赶走B国军队,所以你想先切断B国军队占领城市的电力。
要做到这一点,你可能会截断电力系统的一些线路。如果从中央电站到城市的路径包含至少一个截断的线路,城市就无法获得电力。
因为担心国民产生恐慌,所以你决定截断最少数量的电线,让这看起来更像是一场意外。与此同时,您希望尽可能地减少对没有B国军队城市(正常城市)的影响,不幸的是,B国军队可能会在不同的城市出现或者消失。因此,您需要向科学家们询问截断电线所需的最低数量,以及对正常城市的最小影响数量。
输入要求
输入的第一行包含两个正整数n和q,分别代表了树的结点数和B国军队行动的次数。(2≤n≤100000; 1≤q≤100000)。第二行输入n-1个正整数包含对电力网络结点的描述:一个n-1整数序列p2, p3,…,pn,其中 pi为结点i(1≤pi<i)的父结点,中央电站位于结点1处。
下面q行表示B国军队位置的变化,B国军队每一次行动必为两种情况之一,"+v"表示B国军队出现在结点v,"-v"表示B国军队离开了结点v。
起初,所有的城市都没有B国军队出现。输入的B国军队变化情况遵循正确的序列,即:B国军队不能占领已被占领的城市,不能离开未占领的城市。
输出要求
输出应包含2*q个整数,每行两个数:ci ——截断电线线路的最小数量
hi ——被波及影响的正常城市的最小数量(没有敌国军队,但无法获取电力的城市)
程序运行样例
算法思路
城市连接形式
城市之间的链接存在以下特点:
1.若将电路网视作一颗“树”,则城市即为树上所有的叶子结点,即城市不能成为任何结点的父节点或祖先结点。
2.每个结点都有可能有多个孩子,结点数目与其孩子的数目完全由用户输入的结点关联关系(输入样例中第二行)决定,即该树的度并非固定,因此不能用传统的递归方法建立树。
3.每一个叶子结点(城市)都应能快速找到其所有的祖先结点,以便在敌人入侵多个城市时选择出其最近的共同祖先结点,以便实现切断最少电路的题目要求。
综上所述,尽管画出的图很像树,但我们熟悉的链式存储的线索树的结构显然不适用于该问题(T_T)。图结构可以自由设置结点的入度和出度,且方便找寻某结点至主结点(即主发电站)的路径;顺序存储的树(带有指向父节点的指针)具有随机存取的优点,方便随时调整某个结点的电路、是否迎敌等参数。本篇文章选用顺序存储的树结构来解决问题。
切断点确定方法
确定最短切断点的思路如下:
1.记录先前已切断的电站(n1、n2……)。
2.将敌人最近侵入的城市x,与先前每一个已切断点都寻找最近的公共祖先,分以下三种情况:
若公共祖先即为已有切断点,直接将x断电;
若存在高度更低(更接近主站)的公共祖先,则用该公共祖先替代原有n值,并对以n为祖先的所有结点进行断电操作;
若所有比较的公共祖先均为主节点1(主站不能作为切断点),则直接切断x的电路。
公共祖先的寻找方法,在后续会详细讲,个人感觉非常妙XD,请耐心浏览~
城市恢复实现方法
简单粗暴,先将先前敌人已经入侵的城市的编号存放在数组arr中,在p城的敌人撤退时,抹去数组中p城对应的元素,并将所有结点恢复原状,再按照arr的信息依次对每个有敌人的城市进行侵略。最终所得电路切断方案,即为敌人从p城撤退后的最优电路切断方案。
算法实现(C语言)
结构定义
参数解释:
child_num:孩子结点数量,若为0视作城市。
elec:是否有电。断电后设置为0;
enemy:是否有敌人。敌人入侵后设置为1;
highth:结点高度,求解最近祖先节点时用;
num:结点编号;
parent:指向父结点的指针;
typedef struct city {
int child_num;
int elec;
int enemy;
int highth;
int num;
city* parent;
}city, * country;
城市创建
创建城市需要用户输入的节点数量n、城市头结点a与用户输入的城市关联关系数组rela。
首先对城市数据进行初始化,随后按照关联关系使每个节点的parent都指向其对应的父节点,当父节点被指向时,其child_num参数会+1;子结点的高度比父节点高1
int create(int n, city* a, int* rela) {
//根据输入的节点信息建立树
//a->parent = NULL;
city* father;
a->parent = a + n;
for (int i = 0; i < n; i++) {
(a + i)->num = i + 1;
(a + i)->highth = 1;
(a + i)->child_num = 0;
(a + i)->elec = 1;
(a + i)->enemy = 0;
}
for (int i = 1; i < n; i++) {
father = (a + rela[i] - 1);
(a + i)->parent = father;
father->child_num++;
(a + i)->highth = father->highth + 1;
printf("第%d个结点与第%d个结点建立链接!此时结点%d的孩子数量为:%d\n", i + 1, rela[i], rela[i], (a + i)->parent->child_num);
}
printf("城市群创建成功,其信息如下:\n");
printf("结点1没有父节点,其孩子数量为%d ,高度为%d\n", a->child_num, a->highth);
for (int i = 1; i < n; i++)