AOE学习笔记

算法背景

拓扑排序处理的是有向无环图,当图中的边被赋予权重时,图就变成了网,若这些带权边表示工程中的某项活动,这个网就称AOV(Adversity on edge)网,与之相对的一个概念是赋予图中顶点以权重,并将顶点视为某项活动,这就构成了AOV(Adversity on vertex)网。AOV网可以容易地转化为AOE网,只要将顶点视为一个带权边,边的两端分别表示活动的开始与结束,边的权重正是原来点的权重,一般表示活动用时,原来网格中的边视为空活动,删去即可,这就将AOV网转化成为了AOE网,所以我们将着重探讨用AOE网模型解决工程最短用时问题。
若要完成某项工程,无疑要完成工程中的所有活动,我们已在AOE网中用边来表示,注意AOE网是从有向无环图演变来的,所以活动与活动之间存在先后关系,那么完成某项活动势必走出一条单向路径,完成工程就会形成多条这样的路径,而工程的耗时正是由这些路径中最长的(代表用时最长)的那条决定的,毕竟可以同时进行多项活动。所以如何求出AOE网中的最长路径,成为算法要解决的关键问题,这最长路径,也被称为该工程的关键路径。
虽然前面已学习了求解图中最短路径的算法,但遗憾的是那些算法,诸如Dijkstra、BF和SPFA均无法应用到求解关键路径的过程中来,原因各异。因而求解关键路径,需要研究出一种全新的算法,这算法以时间为关键词,并引入“事件”这一新概念。其实事件就是由网中的顶点表示的,前面在谈到将AOV转化为AOE网时,有讲过边的两个端点分别表示活动的开始和结束,这正是AOE网中顶点的意义,可以表示其前面连接边活动(如果有的话)的结束,也可以表示其后面连接边活动(如果有的话)的开始。
在这里插入图片描述

在上面给出的丑图中,可以看到事件E2前面连着活动A1,后面连着活动A2,要注意到这样一个事实:只有A1结束了,E2才能发生;只有E2发生了,A2才能开始。然而讨论点(事件)的最早发生时间,和最晚发生事件,会比讨论边(活动)的最早结束时间和最晚开始时间来得直观,原因是对于点的时间可以由其后继点递推计算,但对于边的时间,你可以用另一条边递推计算得到吗?答案显然是否定的,但边的时间可以由点的时间确定,下面具体谈谈点与点之间是如何递推的:
1.事件的最早发生时间ve:它的计算基于这样一个事实,只有点的所有前驱点都已发生,该点才能发生。所以若要求某个v结点的ve[v],须保证v的所有前驱结点的ve已计算完成,并在其中找到最大值ve[u](即前驱中最晚发生的),加上u–>v的活动用时,就求出了ve[v]。那么这求解顺序便可以由拓扑排序来保证。
2.事件的最晚发生事件vl:有一个显然的事实,某个点u,即某事件u,虽然可以拖延发生时间,但不能拖延到使得其后继点无法发生,即vl[u]+活动用时,不能超过vl[u的后继],假设这些后继中vl最小的点为v,就有:vl[u]+AOE[u][v]<=vl[v],即vl[u]<=vl[v]-AOE[u][v],vl又代表u的最迟发生时间,于是有vl[u]=vl[v]-AOE[u][v]。所以求解一个点的vl值时,须保证该结点的后继已全部计算完成,求解顺序可以由拓扑排序的逆序列保证,只需要将上面的拓扑排序序列反转一下即可。
求出ve数组和vl数组,在求活动的最早开始时间(记e)和最晚开始时间(记l)就很容易了,其前端事件最早发生时间(ve[u])就是活动的最早开始时间(e),其后端事件的最晚发生时间(vl[v])剪掉活动用时(AOE[u][v]),就是活动最晚开始时间(l)。显然关键路径上所有活动必须紧凑进行,不能拖延,这样才能保证整个工程用时最少对吗?所以有e==l,这就是判断一个活动是否在关键路径上的依据。
至此,算法总体内容就介绍完了,下面上代码。

重要模块

拓扑排序并求解ve

stack<int> topOrder;
bool topologicalSort(){
	queue<int> q;
	for(int i=0;i<vnum;i++){
		//ve初始化为0
		ve[i] = 0;
		if(inDegree[i]==0){
			//每次把入度为0的点加入队列 
			q.push(i); 
		}
	}
	while(!q.empty()){
		int u = q.front();
		q.pop();
		//将队首元素加入拓扑序列 
		topOrder.push(u);
		for(int i=0;i<G[u].size();i++){
			//遍历u的后继点,进行入度-1操作 
			int v = G[u][i].v;
			//边的权值 
			int weight = G[u][i].w;
			if(--inDegree[v]==0)
			//入度减为0的点入队 
				q.push(v);
			//根据前驱的ve算后继,取最大的 
			ve[v] = max(ve[v],ve[u] + weight);
		}
	}
	if(topOrder.size()==vnum)
		return true;//所有结点都排好序
	else
		return false;//图中有环 
}

逆拓扑排序并求解vl

前面用栈存拓扑序列,弹栈就能得到逆拓扑序列。

void reTopoSort(){
	//将vl初始化为终点(这里终点编号为vnum-1)ve值
	//终点不存在拖延发生一说,其ve==vl,且即为工程最短用时 
	fill(vl,vl+vnum,ve[vnum-1]);
	while(topOrder.size()!=0){
		int u = topOrder.top();
		topOrder.pop();
		for(int i=0;i<G[u].size();i++){
			int v = G[u][i].v;
			int weight = G[u][i].w;
			//用u的后继更新其vl值,取最小 
			vl[u] = min(vl[v]-weight,vl[u]);
		}
	} 
}

依据ve、vl确定关键路径

void criticalPath(){
	for(int u=0;u<vnum;u++){
		for(int i=0;i<G[u].size();i++){
			int v = G[u][i].v;
			int weight = G[u][i].w;
			//求活动最早最迟开始时间e和l
			int e = ve[u];
			int l = vl[v] - weight;
			if(e==l){
				//是关键路径上的活动,输出 
				printf("(%d,%d),",u,v); 
			} 
		}
	}
} 

实际应用

题目

  • 输入
    第一行输入一个正整数n(1<=n<=5),其代表测试数据数目,即图的数目
    第二行输入x(1<=x<=15)代表顶点个数,y(1<=y<=19)代表边的条数
    第三行给出图中的顶点集,共x个小写字母表示顶点
    接下来每行给出一条边的始点和终点及其权值,用空格相隔,每行代表一条边。
  • 输出
    第一个输出是图的关键路径(用给出的字母表示顶点, 用括号将边括起来,顶点逗号相隔)
    第二个输出是关键路径的长度
    每个矩阵对应上面两个输出,两个输出在同一行用空格间隔,每个矩阵的输出占一行。

代码

要注意的是题目中源点是要自己再确定的,源点就是ve==0的点。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
const int maxV = 25;
struct node{
	int v;
	int w;
	node() {}
	node(int _v,int _w){
		v = _v;
		w = _w;
	}
};
map<char,int> P;//顶点集与编号的映射
char V[maxV];//编号与顶点集的映射 
vector<node> G[maxV];//图
stack<int> topOrder;//拓扑序列
int inDegree[maxV];//顶点入度
//ve和vl数组
int ve[maxV];
int vl[maxV];

void init(int x){
	//每一轮测试前清空
	for(int i=0;i<x;i++)
		G[i].clear();
	fill(inDegree,inDegree+x,0);
	fill(ve,ve+x,0);
	P.clear(); 
} 

bool topologicalSort(int x){
	queue<int> q;
	for(int i=0;i<x;i++){
		if(inDegree[i]==0)
			q.push(i);
	}
	while(!q.empty()){
		int u = q.front();
		q.pop();
		topOrder.push(u);
		for(int i=0;i<G[u].size();i++){
			int v = G[u][i].v;
			int weight = G[u][i].w;
			if(--inDegree[v]==0)
				q.push(v);
			ve[v] = max(ve[v],ve[u]+weight);
		}
	}
	if(topOrder.size()==x)
		return true;
	else
		return false;
}

void criticalPath(int x){
	if(topologicalSort(x)==false){
		puts("ERROR");
		return;
	}
	fill(vl,vl+x,ve[x-1]);
	while(topOrder.size()!=0){
		int u = topOrder.top();
		topOrder.pop();
		for(int i=0;i<G[u].size();i++){
			int v = G[u][i].v;
			int weight = G[u][i].w;
			vl[u] = min(vl[u],vl[v]-weight);
		}
	}
	//找关键路径
	int next[maxV];
	for(int u=0;u<x;u++){
		for(int i=0;i<G[u].size();i++){
			int v = G[u][i].v;
			int weight = G[u][i].w;
			int e = ve[u];
			int l = vl[v] - weight;
			if(e==l)
				next[u] = v;
		}
	}
	//找源点
	int s;
	for(int i=0;i<x;i++){
		if(ve[i]==0)
			s = i;
	}
	//输出关键路径
	while(s!=x-1){
		printf("(%c,%c) ",V[s],V[next[s]]);
		s = next[s];
	}
	//输出工程最短用时
	printf("%d\n",ve[x-1]);
}

int main(){
	int n,x,y;
	cin>>n;
	while(n--){
		cin>>x>>y;
		init(x);
		cin>>V;
		for(int i=0;i<strlen(V);i++)
		//建立字符映射 
			P[V[i]] = i; 
		while(y--){
			int u,v,w;
			char s,t;
			cin>>s>>t>>w;
			//将顶点转化为编号 
			u = P[s],v = P[t];
			//建立边结构体加入图 
			node edge(v,w);
			G[u].push_back(edge);
			//终点v的入度+1 
			inDegree[v]++;
		}
		criticalPath(x);
	}
}

结果

在这里插入图片描述

反思总结

本来没想花这么多时间在AOE上面的,但是我的记忆力可能真的变差了(不知道是不是喝酒喝的T^T),今天发现拿到题目根本无从下手,完全想不起来如何处理,所以又一通啃书,顺便记录一下学习过程。写博客的时候发现自己有好多地方都没搞清楚,彻彻底底梳理了一遍,应该印象会比之前深刻。

Unity中的AOE技能是指Area of Effect技能,即范围效果技能。在Unity中,可以通过使用特定的图形或算法来实现AOE技能的范围指示效果。 引用提到了一个Demo,该Demo实现了类似王者荣耀的技能范围指示效果,包括普攻、定向、AOE范围圈、扇形模式和假扇形模式等几种效果。 引用提到了MOBA游戏中每个英雄都有自己的技能攻击范围方式,包括圆形范围、直线范围、扇形方天戟范围和矩形范围等等。这些范围形状可以通过Unity的图形绘制和碰撞检测来实现。 至于具体实现AOE技能的范围指示效果,可以使用Unity的碰撞检测功能结合特定的图形绘制方式来实现。例如,可以使用Unity的Collider组件来检测技能范围内的敌人,然后在场景中绘制出对应的范围效果,如圆形、扇形或矩形。另外,也可以使用Shader来实现特殊的效果,如光照或扭曲。 总之,Unity中的AOE技能的范围指示效果可以通过使用碰撞检测和图形绘制来实现,具体的实现方式可以根据游戏需求和设计来定制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [扇形、箭头、圆形范围AOE技能圈释放、控制模型](https://download.csdn.net/download/qq_23680543/10157976)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Unity 攻击范围检测](https://blog.csdn.net/a592733740/article/details/106291936)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值