南京大学2020机试题-电路切断最优解问题-空间复杂度o(n)解法

本文介绍了南京大学2020年复试机试中的一道电路切断问题,涉及数据结构与算法。作者给出了利用C语言解决的思路,包括城市连接特点、切断点确定方法和恢复策略。算法主要处理敌对国家军队在不同城市出现和消失的情况,目标是切断最少的电线,影响最少的正常城市。
摘要由CSDN通过智能技术生成

前言

本人为数据结构初学者,前几天偶然在网上看到南京大学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++) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值