Pro练习

本文主要探讨了图论中的一个重要算法——迪杰斯特拉算法,详细阐述了其在分段问题中的应用。通过实例解析,帮助读者深入理解算法的工作原理及其在实际问题中的解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.分段

#include<iostream>
#include<cmath>
using namespace std;

//int aN = 200002;

int aSubscriber[200002] = { 0 };//记录数组元素对应的值
int L[450], R[450];//记录每块内左右端点的下标,最大块数448
int pos[200002];//记录第i个元素的块号
int sumK[450];//记录第k块的元素总和
int maxK[450];//记录第k块的最大值
int minK[450];//记录第k块的最小值

void init(int N, int mSubscriber[]) {
	//aN = N;
	aSubscriber[0] = 0;
	for (int i = 0; i < N; i++)
	{
		pos[i] = 0;
		aSubscriber[i + 1] = mSubscriber[i];
	}
	
	for (int i = 1; i <450; i++)
	{
		maxK[i] = -1;//每个块最大值
		minK[i] = 10001;//每个块最小值
		sumK[i] = 0;
		L[i] = 0;
		R[i] = 0;
	}

	int t = sqrt(N*1.0);//每个块内元素个数
	int num = N / t;//块数
	if (N%t)//余数不为0,说明有不能整除的块
	{
		num++;
	}

	for (int i = 1; i <= num; i++)//i遍历块号
	{
		L[i] = (i - 1)*t + 1;//第i块左端点下标
		//(i-1)*t
		//i*t-1
		R[i] = i * t;//第i块右端点下标
	}
	//这里要注意 当上面处理完第num块时 R[num]是按照长度为t进行分配的右端点
    //但是这个右端点有可能比n还大 然而我们只需要处理到n就可以了
    //因此这里还要特殊处理最后一段的右端点 取到n即可 不一定取最后一段分配到的右端点
	R[num] = N;

	for ( int i = 1;  i <=num;  i++)//i遍历块数
	{
		//int maxi=-1, mini=10001;//初始化每个块的最大值,最小值
		for (int j = L[i]; j <= R[i]; j++)//j遍历这个块的元素
		{
			pos[j] = i;//下标j对应元素 属于块号i
			sumK[i] += aSubscriber[j]; //i块元素总和
			if (minK[i] > aSubscriber[j])
			{
				minK[i] = aSubscriber[j];//更新第i块的最小值
			}
			if (maxK[i] < aSubscriber[j])
			{
				maxK[i] = aSubscriber[j];//更新第i块的最大值
			}
		}
	}
	
	return;
}

int updateKuai(int mId)//更新块内维护最值数组,返回块号
{
	int kuai = pos[mId];//确定mId的块号
	//初始化块内的最值,块内元素有变动,则从头遍历重新排序
	minK[kuai] = 10001;
	maxK[kuai] = -1;

	for (int j = L[kuai]; j <= R[kuai]; j++)//遍历这个块,更新最大/小值
	{
		if (minK[kuai] > aSubscriber[j])
		{
			minK[kuai] = aSubscriber[j];//更新第i块的最小值
		}
		if (maxK[kuai] < aSubscriber[j])
		{
			maxK[kuai] = aSubscriber[j];//更新第i块的最大值
		}
	}
	return kuai;
}

int subscribe(int mId, int mNum) {
	aSubscriber[mId] += mNum;
	int kuai = updateKuai(mId);
	sumK[kuai] += mNum; //更新第kuai块元素总和
	return aSubscriber[mId];
	/*
	int kuai = pos[mId];//确定mId的块号
	minK[kuai] = 10001;//如果加的是之前最小的哪个,比较原来的最小值永远不会更新(所有的值都比他大),而实际的最小值已经变大了,所以直接从头遍历,重置这个块的最小值
	//如果加的是最大值,那肯定会更新
	for (int j = L[kuai]; j <= R[kuai]; j++)//遍历这个块,更新最大/小值
	{
		if (minK[kuai] > aSubscriber[j])
		{
			minK[kuai] = aSubscriber[j];//更新第i块的最小值
		}
		if (maxK[kuai] < aSubscriber[j])
		{
			maxK[kuai] = aSubscriber[j];//更新第i块的最大值
		}
	}
	*/
	//cout << "subscribe:" << aSubscriber[mId] << endl;

}

int unsubscribe(int mId, int mNum) {//如果减的最小的哪个,那最小值可能会变化成之前第二小的,需要比较
	aSubscriber[mId] -= mNum;
	int kuai = updateKuai(mId);
	sumK[kuai] -= mNum; //更新第kuai块元素总和
	return aSubscriber[mId];
	/*
	int kuai = pos[mId];//确定mId的块号
	maxK[kuai] = -1;//如果减少的是之前最大的哪个值,比较原来的最大值永远不会更新(所有的值都比他小),而实际的最大值已经变小了,所以直接从头遍历,重置这个块的最大值
	for (int j = L[kuai]; j <= R[kuai]; j++)//遍历这个块,更新最大/小值
	{
		if (minK[kuai] > aSubscriber[j])
		{
			minK[kuai] = aSubscriber[j];//更新第i块的最小值
		}
		if (maxK[kuai] < aSubscriber[j])
		{
			maxK[kuai] = aSubscriber[j];//更新第i块的最大值
		}
	}
	*/
	
	//cout << "unsubscribe:" << aSubscriber[mId] << endl;

}

int count(int sId, int eId) {
	int res = 0;
	/*无脑遍历会超时,需要分块优化
	for (int i = sId; i <= eId; i++)
	{
		res += aSubscriber[i];
	}*/
	int kuais = pos[sId];
	int kuaie = pos[eId];
	if (kuais == kuaie)//如果在同一个块里
	{
		for (int i = sId; i <= eId; i++)//复杂度不高,直接累加
		{
		    res += aSubscriber[i];
		}
	}
	else//不在同一个块,说明跨越了不同的块,左边半块,中间是[kuais+1,kuaie-1]块,右边半块
	{
		//先处理中间整块,直接将分块求和数组 取对应块 累加
		for (int i = kuais + 1; i <= kuaie - 1; i++)
		{
			res += sumK[i];
		}
		//处理左半块累加
		for (int i = sId; i <= R[kuais]; i++)//块头是给定的起点sid,块尾是R[pos[sid]]
		{
			res += aSubscriber[i];
		}
		//处理右边半块累加
		for (int i = L[kuaie]; i <=eId; i++)
		{
			res += aSubscriber[i];
		}
	}
	//cout << "count:" << res << endl;
	return res;
}

int calculate(int sId, int eId) {

	/*无脑遍历会超时,需要分块优化
	for (int i = sId; i <= eId; i++)
	{
		if (min > aSubscriber[i])
		{
			min = aSubscriber[i];
		}
		if (max < aSubscriber[i])
		{
			max = aSubscriber[i];
		}
	}*/
	int res = 0;
	int kuais = pos[sId];
	int kuaie = pos[eId];
	int min = 10001, max = -1;
	if (kuais == kuaie)//如果在同一个块里
	{
		for (int i = sId; i <= eId; i++)//复杂度不高,直接比较求最值,相减
		{
			if (min > aSubscriber[i])
			{
				min = aSubscriber[i];
			}
			if (max < aSubscriber[i])
			{
				max = aSubscriber[i];
			}
		}
		res = max - min;
	}

	else//不在同一个块,说明跨越了不同的块,左边半块,中间是[kuais+1,kuaie-1]块,右边半块
	{
		//先处理中间整块,找出最大,最小值
		for (int i = kuais + 1; i <= kuaie - 1; i++)
		{
			if (min > minK[i])
			{
				min = minK[i];
			}
			if (max < maxK[i])
			{
				max = maxK[i];
			}
		}
		//处理左半块
		for (int i = sId; i <= R[kuais]; i++)
		{
			if (min > aSubscriber[i])
			{
				min = aSubscriber[i];
			}
			if (max < aSubscriber[i])
			{
				max = aSubscriber[i];
			}
		}
		//处理右边半块
		for (int i = L[kuaie]; i <= eId; i++)
		{
			if (min > aSubscriber[i])
			{
				min = aSubscriber[i];
			}
			if (max < aSubscriber[i])
			{
				max = aSubscriber[i];
			}
		}
		res = max - min;
	}
	//cout << "calculate:" << res << endl;
	return res;
}

2.迪杰斯特拉

#include<bits/stdc++.h>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>

using namespace std;
int INF = 0x3f3f3f3f;
const int MAXN = 20;
bool visit[MAXN];
int mDis[MAXN];
int mPrev[MAXN]; // 用于记录最短路径上的前一个顶点



struct point {
	int vec, dis;
	point(int v, int d)
	{
		vec = v;
		dis = d;
	}
	bool operator < (const point &other)const {
		if (dis != other.dis)
		{
			return dis > other.dis;//dis从大到小降序,但是优先队列中dis越大优先级越低,排在队尾
			//return dis < other.dis;//dis从小到大升序
		}
	}
};

vector <point> Graph[MAXN];
void djs(int start)
{
	//1priority_queue<point,vector<point>,less<point>> q;== priority_queue<point>; return dis < other.dis; //以dis大的为队首
	//2如果以disx小的为队首(最短路径),则需要在结构体修改 <的逻辑return dis > other.dis

	memset(mPrev, -1, sizeof(mPrev)); // 初始化为-1,表示没有前一个顶点
	memset(visit, false, sizeof(visit));//初始默认没访问过
	memset(mDis, INF, sizeof(mDis));//初始起点到达其他点距离,设为无穷大代表全部不可达
	mDis[start] = 0;//起点到自己的距离为0
	mPrev[start] = start; // 起点到自己的前一个顶点是它自己
	priority_queue<point> q;
	q.push({ start,0 });//起点入队
	while (!q.empty())
	{
		point temp = q.top();
		int midV = temp.vec;//本次循环最短边终点的编号,可能的中间节点mid
		int midD = temp.dis;//代表s到mid的距离
		q.pop();


		/*if (mDis[vec] < dis)//如果起点直接到当前队首顶点的距离 比 记录默认dis[]的最短距离(初始为INF)大,则break
		{
			continue;
		}
		*/
		if (visit[midV])//如果此点已经被访问过加入集合,则跳过
		{
			continue;
		}
		visit[midV] = true;//本次循环确认该点的最短距离


		for (int i = 0; i < Graph[midV].size(); i++)
		{
			int eV = Graph[midV][i].vec;//本次循环目标点
			int eD = Graph[midV][i].dis;
			if (!visit[eV] && eD + mDis[midV] < mDis[eV])
			{
				mDis[eV] = eD + mDis[midV];
				mPrev[eV] = midV; // 更新最短路径上的前一个顶点
				//q.push({ ivec,idis });注意 不是更新idis 这个是起点到ivec的距离是固定的,应该更新最短距离

				q.push({ eV,mDis[eV] });//更新到ivec的最短距离
			}
		}
	}


}

void initEdge() {
	for (int u = 0; u < MAXN; u++)
	{
		for (int v = 0; v < MAXN; v++)
		{
			if (u == v)
			{
				Graph[u].push_back({ v, 0 });
				Graph[v].push_back({ u, 0 });
			}
			if (u != v)
			{
				Graph[u].push_back({ v, INF });
				Graph[v].push_back({ u, INF });
			}
		}
	}
	
}

// 辅助函数,用于添加边到图中
void addEdge(int u, int v, int w) {
	Graph[u].push_back({ v, w });

	// 如果是无向图,还需要添加反向边
	// Graph[v].push_back({u, w});
}

// 辅助函数,用于打印最短路径结果
void printShortestPaths(int start) {
	cout << "Shortest paths from node " << start << ":" << endl;
	for (int i = 0; i < MAXN; ++i) {
		if (mDis[i] != INF) {
			cout << "Distance to node " << i << ": " << mDis[i] << endl;
		}
		else {
			cout << "Node " << i << " is unreachable from node " << start << endl;
		}
	}
}

void printPath(int start, int end) {
	if (mPrev[end] == -1) {
		cout << "No path from " << start << " to " << end << endl;
		return;
	}

	vector<int> path;
	for (int v = end; v != start; v = mPrev[v]) {
		path.push_back(v);
	}
	path.push_back(start); // 添加起点到路径中
	reverse(path.begin(), path.end()); // 反转路径,使其从起点到终点

	cout << "Path from " << start << " to " << end << ": ";
	for (size_t i = 0; i < path.size(); ++i) {
		cout << path[i];
		if (i < path.size() - 1) cout << " -> ";
	}
	cout << endl;
}


int main() {
	// 构造测试用例图
	//初始化
	initEdge();
	// 例如:一个包含5个顶点的简单图
	addEdge(0, 1, 3);
	addEdge(0, 2, 1);
	addEdge(0, 3, 5);
	addEdge(1, 2, 2);
	addEdge(1, 3, 4);
	addEdge(1, 4, 1);
	addEdge(2, 4, 6);
	addEdge(3, 4, 2);

	// 计算从顶点0开始的最短路径
	djs(0);

	// 打印结果
	printShortestPaths(0);
	for (int i = 0; i < 5; i++)
	{
		printPath(0, i); // 打印从顶点0到顶点i的最短路径
	}

	return 0;
}


//另一种
vector<pair<int, int>> g[500005];//用于存放图信息
int dis[500005];
void dijkstra(int s)        //(m)log(m) n=1e5 m=1e6
{
	memset(dis, 0x3f3f3f3f, sizeof(dis));
	dis[s] = 0;
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;//小顶堆,队首是最小的距离
	q.push({ 0, s });
	while (!q.empty())
	{
		pair<int, int> p = q.top();
		q.pop();
		int u = p.second;//u是顶点
		int d = p.first;//d是距离,优先按照距离比较
		if (dis[u] < d) continue;
		for (int i = 0; i < g[u].size(); i++)//遍历u的也就是队首的各个相邻接点
		{
			int v = g[u][i].second;
			int w = g[u][i].first;
			if (dis[v] > dis[u] + w)//如果通过s->u->v间接的 距离比 当前s->v直接到相邻节点v的最短距离 短
			{
				dis[v] = dis[u] + w;//更新s->v最短距离
				q.push({ dis[v], v });//{v和,s->v的距离}入队
			}
		}
	}
}


int main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)//初始化图的各个顶点间权值
	{
		int u, v, w;
		scanf("%d %d %d", &u, &v, &w);
		g[u].push_back({ w, v });
		g[v].push_back({ w, u });
	}
	dijkstra(1);
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", dis[i]);
	}

}

3.BFS+DJS

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

#define MAX_MAP_SIZE 350

#define MAXMAPSIZE 355
#define MAXGATENUM 205

#define INF 0x3f3f3f3f


int gN = 0;//图的size
int cnt = 0;//门id的个数
int HP = 0;//生命值


int mp[MAXMAPSIZE][MAXMAPSIZE];//保存初始化的时候map信息

//int gates[MAXMAPSIZE][MAXMAPSIZE];//保存每个gate Id

int isRemoved[MAXGATENUM];//标记是否删除

//用于bfs遍历的结构体
struct node
{
	int r;//row
	int c;//colume
	int dis;//distance from gate A to gate B
};

int visited[MAXMAPSIZE][MAXMAPSIZE];//BFS遍历的时候标志位

//方向数组
int dx[] = { 1,-1,0,0 };
int dy[] = { 0,0,1,-1 };
int edge[MAXGATENUM][MAXGATENUM];//保存 gate之间的距离 
node positions[MAXGATENUM];//保存每个gate的x y坐标信息

//如果在函数体内部定义MyQueue 会导致segementation fault
node MyQueue[MAXMAPSIZE*MAXMAPSIZE + 1];
//440 + ms
//BFS函数,用于计算从指定门到所有可达位置的距离
void bfsMyQueue(int mGateID, int mRow, int mCol)
{
	memset(visited, 0, sizeof(visited));

	int head = 0, tail = 0; // 队列的头尾指针
	MyQueue[tail].r = mRow;  // 起点行
	MyQueue[tail].c = mCol;  // 起点列
	MyQueue[tail++].dis = 0; // 起点的距离为0
	while (head < tail)
	{
		// 从队列中取出一个点
		int curDis = MyQueue[head].dis;  // 当前点的距离
		int row = MyQueue[head].r;       // 当前点的行
		int col = MyQueue[head++].c;     // 当前点的列,注意这里先增加head再取col

		// 如果当前距离达到了某个预设的最大值(HP),则停止搜索
		if (curDis >= HP)
		{
			break;
		}

		// 遍历当前点的四个方向
		for (int i = 0; i < 4; i++)
		{
			int tx = row + dx[i];  // 目标点的行
			int ty = col + dy[i];  // 目标点的列
			// 注意:这里原代码中的td变量未使用,可能是用于存储目标点的距离,但在这里直接计算即可

			// 检查目标点是否在网格范围内
			if (tx < 0 || tx > gN - 1 || ty < 0 || ty > gN - 1)
			{
				continue;
			}

			// 检查目标点是否可达(非障碍物且未访问过)
			if (mp[tx][ty] == 1 || visited[tx][ty] == 1)
			{
				continue;
			}

			// 标记目标点为已访问
			visited[tx][ty] = 1;

			// 如果目标点是特殊点(比如门),则更新门与起点之间的距离关系
			if (mp[tx][ty] > 1)
			{
				int gateNum = mp[tx][ty] / 10; // 假设门的编号是mp值除以10的结果
				// 更新门与起点(mGateID)之间的最短距离
				edge[gateNum][mGateID] = curDis + 1;
				edge[mGateID][gateNum] = curDis + 1;
			}

			// 将目标点加入队列,准备进一步搜索
			MyQueue[tail].r = tx;
			MyQueue[tail].c = ty;
			MyQueue[tail++].dis = curDis + 1;
		}
	}

}

// 创建一个 STL 队列来存储待访问的节点
queue <node> STLQueue;
//900+ ms
void bfsSTLQueue(int mGateID, int mRow, int mCol)
{
	// 初始化访问数组为未访问
	memset(visited, 0, sizeof(visited));

	//清空队列,确保从给定的起点开始
	while (!STLQueue.empty())
	{
		STLQueue.pop();
	}

	// 将起始节点加入队列,距离初始化为 0
	STLQueue.push({ mRow,mCol,0 });

	// BFS 循环
	while (!STLQueue.empty())
	{
		// 从队列中取出当前节点
		node t = STLQueue.front();
		STLQueue.pop();

		// 如果当前节点的距离已经超过了一个预设的阈值 HP,则停止搜索
		if (t.dis >= HP)
		{
			break;
		}
		/*
				// 如果当前节点已被访问过,则跳过
				if (visited[t.r][t.c])
				{
					continue;
				}

				// 标记当前节点为已访问
				visited[t.r][t.c] = 1;
				在遍历方向并找到下一个有效节点时,应该标记visited[x][y]为已访问,而不是visited[t.r][t.c]。
				这是一个常见的错误,可能会导致程序逻辑出错
		*/
		// 遍历当前节点的四个方向
		for (int i = 0; i < 4; i++)
		{
			int x = t.r + dx[i]; // 计算新节点的行
			int y = t.c + dy[i]; // 计算新节点的列
			int d = t.dis + 1;   // 计算新节点的距离

			// 检查新节点是否有效且未访问过,且不是障碍物
			if (x >= 0 && x < gN && y >= 0 && y < gN && !visited[x][y]
				&& mp[x][y] != 1 && mp[x][y] != -1)
			{
				// 注意:这里应该是标记新节点为已访问,而不是当前节点
				visited[x][y] = 1; // 标记新节点为已访问
				STLQueue.push({ x,y,d }); // 将新节点加入队列

				// 如果是门,更新邻接矩阵
				if (mp[x][y] > 1)
				{
					int gateNum = mp[x][y] / 10; // 提取门的编号是其值的整数部分
					edge[gateNum][mGateID] = d; // 更新门之间的最短距离
					edge[mGateID][gateNum] = d; // 双向更新 从当前门出发到达此门的距离,无向图需要更新2个方向
				}
			}
		}
	}
}

void removeGate(int mGateID)
{
	int x = positions[mGateID].r;
	int y = positions[mGateID].c;

	//gates[x][y] = 0;
	mp[x][y] = 0;
	isRemoved[mGateID] = 1;
	//cnt--;
}



struct  dijNode
{
	int id;
	int dist;
	bool operator < (dijNode z) const
	{
		return dist > z.dist;
	}
};
int Dist[MAXGATENUM];//保存dijstra最短路径
bool djsVisit[MAXGATENUM];//标记djs各个Node是否被访问过
void dijstrak(int startID, int endID)
{

	memset(Dist, INF, sizeof(Dist));
	memset(djsVisit, false, sizeof(djsVisit));
	//int disVist[MAXGATENUM] = { 0 };

	Dist[startID] = 0;
	priority_queue <dijNode> myPQ;
	myPQ.push({ startID,0 });
	while (!myPQ.empty())
	{
		dijNode t = myPQ.top();
		int midV = t.id;
		int midD = t.dist;
		myPQ.pop();

		if (midV == endID)
		{
			return;
		}
		if (djsVisit[midV])
		{
			continue;
		}
		djsVisit[midV] = true;

		//cnt其实是已存在gate的id,不是数量,删除后不需要减少,跳过即可
		for (int i = 1; i <= cnt; i++)
		{
			//如果是已经被删除的gate,不访问
			if (isRemoved[i])continue;
			//如果符合djs要求,更新dis和pq
			if (Dist[i] > midD + edge[midV][i])
			{
				Dist[i] = midD + edge[midV][i];
				myPQ.push({ i,Dist[i] });
			}
		}
	}
}

void init(int N, int mMaxStamina, int mMap[MAX_MAP_SIZE][MAX_MAP_SIZE])
{
	gN = N;
	cnt = 0;
	HP = mMaxStamina;

	memset(mp, -1, sizeof(mp));
	memset(edge, INF, sizeof(edge));
	memset(isRemoved, 0, sizeof(isRemoved));
	memset(positions, -1, sizeof(positions));

	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			mp[i][j] = mMap[i][j];
		}
	}

}

void addGate(int mGateID, int mRow, int mCol)
{
	cnt++;
	//gateid*10 保存在图中
	mp[mRow][mCol] = mGateID * 10;

	edge[mGateID][mGateID] = 0;
	positions[mGateID].r = mRow;
	positions[mGateID].c = mCol;
	//使用自定义的queue比STL定义的快400ms
	//bfsMyQueue(mGateID, mRow, mCol);
	bfsSTLQueue(mGateID, mRow, mCol);
}


int getMinTime(int mStartGateID, int mEndGateID)
{

	if (edge[mStartGateID][mEndGateID] != INF)//edge 里面的点都是通过bfs遍历过了,所以存在边的话,一定是最短距离,如果没有进过bfs存储的edge,就不能这么直接判断返回
	{
		return edge[mStartGateID][mEndGateID];
	}

	dijstrak(mStartGateID, mEndGateID);
	if (Dist[mEndGateID] != INF)
	{
		return Dist[mEndGateID];
	}

	return -1;
}

4.hash

/*解题思路‌:‌
1.‌初始化与遍历‌
使用init函数初始化平面信息。‌
遍历整个平面,‌检查每个5*5的区域。‌
2.‌检查区域‌
对于每个5*5的区域,‌计算其中的星星数量。‌
如果星星数量为7,‌进一步检查是否符合瓷砖的特性。‌
3.‌特性验证‌
确保每个5*5区域的四个象限中至少有一个星星。‌
如果符合条件,‌计数增加。‌
add_tile用于
//1添加一个新的瓷砖类型到tile_hash_table中,‌并更新counter和up_left_most_position数组。‌如果瓷砖已经存在,‌则只更新counter
//‌2旋转处理‌:‌为了识别旋转后的瓷砖,‌函数计算了瓷砖0度、‌90度、‌180度和270度的哈希码,‌并将它们都添加到tile_hash_table中。‌
4.‌返回结果‌
根据传入数组的hash_code使用counter数组返回每种类型(包含不同方向)瓷砖的数量。
通过position_plane,记录棋盘上所有坐标对应Position的值,查询的时候直接返回
‌*/

#include <unordered_map>

using namespace std;
#define MAX_SIZE 1000
#define TILE_SIZE 5

unordered_map<int, int> tile_hash_table;//存储瓷砖的hash_code及其类型
int tile_type_id;//记录瓷砖类型‌,4个方向只有一个id
int counter[MAX_SIZE * MAX_SIZE / (TILE_SIZE * TILE_SIZE)]; //记录每种类型(不同方向)瓷砖的数量。‌
int up_left_most_position[MAX_SIZE * MAX_SIZE / (TILE_SIZE * TILE_SIZE)];//记录每一种瓷砖类型(4个方向视为一种)的position值。‌
int tile_data[TILE_SIZE][TILE_SIZE];
int(*position_plane)[MAX_SIZE]; //开一个二维数组,记录棋盘上所有坐标对应Position的值,查询的时候直接返回


//此函数用于添加一个新的瓷砖类型到tile_hash_table中,‌并更新counter和up_left_most_position数组。‌如果瓷砖已经存在,‌则只更新counter
//‌旋转处理‌:‌为了识别旋转后的瓷砖,‌函数计算了瓷砖0度、‌90度、‌180度和270度的哈希码,‌并将它们都添加到tile_hash_table中。‌
int add_tile(int data[5][5], int row, int col) {
	int hash_code = 0;
	//0度,计算初始的hash_code
	/*
	[0,0]          [0,4]

	[4,0]          [4,4]
	*/


	for (int i = 0; i < 5; ++i)
		for (int j = 0; j < 5; ++j) {
			hash_code = hash_code * 2 + data[i][j];
		}
	// 瓷砖已经存在
	if (tile_hash_table.find(hash_code) != tile_hash_table.end()) {
		int type = tile_hash_table[hash_code];
		++counter[type];//瓷砖个数++
		return type;
	}

	// add new 瓷砖不存在,新增,tile_type_id默认是0开始编号,计算保存4个角度的hash_code
	tile_hash_table[hash_code] = tile_type_id;
	// 90 度
	/*
	[4,0]     ...    [0,0]
	 .					.
	 .					.
	[4,4]     ...    [0,4]
	*/

	hash_code = 0;
	for (int j = 0; j <= 4; j++)
	{
		for (int i = 4; i >= 0; i--)
		{
			hash_code = hash_code * 2 + data[i][j];
		}
	}
	tile_hash_table[hash_code] = tile_type_id;
	// 180度
	/*
	[4,4]     ...    [4,0]
	 .					.
	 .					.
	[0,4]     ...    [0,0]
	*/
	hash_code = 0;
	for (int i = 4; i >= 0; i--)
	{
		for (int j = 4; j >= 0; j--)
		{
			hash_code = hash_code * 2 + data[i][j];
		}
	}
	tile_hash_table[hash_code] = tile_type_id;
	// 270度
	/*
	[0,4]     ...    [4,4]
	 .					.
	 .					.
	[0,0]     ...    [4,0]
	*/
	hash_code = 0;
	for (int j = 4; j >= 0; j--)
	{
		for (int i = 0; i <= 4; i++)
		{
			hash_code = hash_code * 2 + data[i][j];
		}
	}
	tile_hash_table[hash_code] = tile_type_id;

	//左上角行和列+2,计算中心点的值,保存
	up_left_most_position[tile_type_id] = (row + 2) * 10000 + (col + 2);
	counter[tile_type_id] = 1;
	return tile_type_id++;
}

void init(int N, int mPlane[MAX_SIZE][MAX_SIZE]) {
	tile_hash_table = unordered_map<int, int>();
	tile_type_id = 0;
	position_plane = mPlane;//初始化为plane默认值
	//mPlane[y][x] 对应坐标(x,y)
	for (int y = 0; y < N - 4; ++y) {//5*5的方格,最左/上侧,最远从N-5开始遍历,后面的不需要考虑
		for (int x = 0; x < N - 4; ++x) {
			if (mPlane[y][x] > 1) {
				x += 4;
				continue;
			}

			//统计从(y,x)开始的5*5的方格星星个数
			int star = 0;
			for (int i = y; i < y + 5; ++i)
				for (int j = x; j < x + 5; ++j)
					star += mPlane[i][j];
			//恰好==7颗才符合
			if (star == 7) {
				for (int i = y; i < y + 5; ++i)
					for (int j = x; j < x + 5; ++j) {
						//给5*5的tile_data赋值保存
						tile_data[i - y][j - x] = mPlane[i][j];
					}

				//00,01,02,03,04
				//yx,yx+1,yx+2
				//返回当前瓷砖的类型,由于从左到右,从上到下遍历,所以可以保证第一次添加的瓷砖一定是同类型最上且最左的那一块
				int type = add_tile(tile_data, y, x);

				//方块内所有的坐标对应的position都是type对应的
				for (int i = y; i < y + 5; ++i)
					for (int j = x; j < x + 5; ++j)
						position_plane[i][j] = up_left_most_position[type];

				//如果找到有效5*5的方格,因为不会重合,所以直接访问下一个5*5的格子即可
				x += 4;
			}
		}
	}
}

//返回找到此类型瓷砖的数量
int getCount(int mPiece[5][5]) {
	int hash_code = 0;
	for (int i = 0; i < 5; ++i)
		for (int j = 0; j < 5; ++j) {
			hash_code = hash_code * 2 + mPiece[i][j];
		}
	if (tile_hash_table.find(hash_code) != tile_hash_table.end())
	{
		int type = tile_hash_table[hash_code];
		return counter[type];
	}
	return 0;
}

//返回中心点position
int getPosition(int mRow, int mCol) {
	return position_plane[mRow][mCol];
}

二分

#include <queue> // 引入队列库
using namespace std; // 使用标准命名空间
#define MAX_N 10001 // 定义最大道路数量
#define MAX_POP 1000 // 定义最大人口数量
#define f __attribute((optimize("Ofast"))) // 定义优化属性

int population[MAX_N]; // 定义人口数组
int travelTime[MAX_N]; // 定义旅行时间数组
int lanes[MAX_N]; // 定义道路容量数组
int n; // 定义道路数量

struct pairr { // 定义一个结构体,包含旅行时间和道路索引
    int time;
    int ids;
    bool operator < (const pairr& other)const {
        //最长时间优先,时间相同返回最短id
        if (time != other.time) 
            return time < other.time;
        if (ids != other.ids)
        {
           return ids > other.ids;
        }
    }
};

struct comp { // 定义一个比较函数,用于比较两个结构体的大小
    bool operator()(pairr& a, pairr& b)const {
        //最长时间优先,时间相同返回最短id
        if (a.time == b.time) 
            return a.ids > b.ids;
        return a.time < b.time;
    }
};
//priority_queue<pairr, vector<pairr>, comp> pq; // 定义一个优先队列,用于保存每个道路的旅行时间和索引
priority_queue<pairr> pq; // 定义一个优先队列,用于保存每个道路的旅行时间和索引

f void init(int N, int mPopulation[]) // 初始化交通流量信息
{
    n = N; // 保存道路数量
    pq = {}; // 清空优先队列
    for (register int i = 0; i < N; i++) // 保存人口分布
        population[i] = mPopulation[i];
    for (register int i = 0; i < N - 1; i++) { // 初始化每个道路的容量和旅行时间,并将它们插入优先队列中
        lanes[i] = 1;
        travelTime[i] = population[i] + population[i + 1];
        pq.push({ travelTime[i], i });
    }
    return;
}
  
f int expand(int M) // 扩展道路容量
{
    int lastTime; // 保存最后一次扩展后的旅行时间
    while (M--) { // 扩展M次
        pairr temp = pq.top(); // 取出优先队列中最长的旅行时间和索引
        pq.pop();
        int id = temp.ids; // 保存道路索引
        lanes[id]++; // 扩展道路容量
        travelTime[id] = (population[id] + population[id + 1]) / lanes[id]; // 重新计算旅行时间
        pq.push({ travelTime[id], id }); // 将新的旅行时间和索引插入优先队列中
        lastTime = travelTime[id]; // 保存最后一次扩展后的旅行时间
    }
    return lastTime; // 返回最后一次扩展后的旅行时间
}
  
f int calculate(int mFrom, int mTo) // 计算旅行时间
{
    int sum = 0; // 保存旅行时间之和
    if (mFrom > mTo) swap(mFrom, mTo); // 确保mFrom <= mTo
    for (register int i = mFrom; i < mTo; i++) // 遍历所有道路,累加旅行时间
        sum += travelTime[i];
    return sum; // 返回旅行时间之和
}
  
f inline bool isPossible(int mFrom, int mTo, int K, int mid) { // 判断是否可以将从mFrom到mTo之间的所有道路分成K个子区域,且每个子区域的旅行时间之和不超过mid
    int sum = 0, cnt = 1; // 初始化累加和和计数器
    for (register int i = mFrom; i <= mTo; i++) { // 遍历所有道路
        if (cnt > K) return false; // 如果计数器大于K,则说明无法将所有道路分成K个子区域,返回false
        sum += population[i]; // 累加人口数量
        if (sum > mid) { // 如果累加和大于mid,则说明需要增加一个子区域,计数器加1,并重置累加和
            cnt += 1;
            sum = population[i];
        }
    }
    if (cnt > K) return false; // 如果计数器大于K,则说明无法将所有道路分成K个子区域,返回false
    return true; // 如果可以将所有道路分成K个子区域,且每个子区域的旅行时间之和不超过mid,则返回true
}
  
f int divide(int mFrom, int mTo, int K) // 分区计算,从mFrom到mTo之间的所有道路分成K个子区域,并返回每个子区域的旅行时间之和的最大值
{
    int low = 0, high = (mTo - mFrom + 1) * MAX_POP; // 初始化二分查找的范围
    int res = -1; // 保存结果
    while (low < high) { // 执行二分查找
        int mid = low + (high - low) / 2; // 计算中间值
        if (isPossible(mFrom, mTo, K, mid)) { // 如果可以将所有道路分成K个子区域,且每个子区域的旅行时间之和不超过mid,则将high更新为mid
             
            high = mid; 
        }
        else low = mid + 1; // 如果无法将所有道路分成K个子区域,或者每个子区域的旅行时间之和超过mid,则将low更新为mid+1
    }
    return high; // 返回high,即每个子区域的旅行时间之和的最大值
}

djs 变种 Spot Travel

#include<iostream>
#include<vector>
#include<queue>
#include<climits>
using namespace std;
 
// 定义三种交通方式
#define WALK 0
#define BIKE 1
#define TAXI 2
 
// 邻接表,存储每个节点的相邻节点及其权重(时间或费用)
vector<pair<int, int>> adj[101]; // {travel, wt}
// 标记是否有自行车停车点
bool hasCycleStand[101];
int n; // 节点数量
 
// 定义一个结构体来表示旅行状态
struct Travel {
    int spot; // 当前节点
    int time; // 当前时间
    int cost; // 当前花费
    int mode; // 当前交通方式
};
 
// 初始化函数,设置节点数量和重置邻接表和自行车停车点标记
void init(int N)
{
    n = N;
    for (int i = 0; i < 101; i++) {
        hasCycleStand[i] = 0;
        adj[i].clear();
    }
}
 
// 添加道路信息
void addRoad(int K, int mSpotA[], int mSpotB[], int mDis[])
{
    for (int i = 0; i < K; i++) {
        adj[mSpotA[i]].push_back({ mSpotB[i], mDis[i] });
        adj[mSpotB[i]].push_back({ mSpotA[i], mDis[i] });
    }
}
 
// 标记某个节点有自行车停车点
void addBikeRent(int mSpot)
{
    hasCycleStand[mSpot] = 1;
}
 
// 定义优先队列的比较函数,用于按照花费和时间排序
struct cmp {
    bool operator()(const Travel& a, const Travel& b) const {
        return a.cost == b.cost ? a.time > b.time : a.cost > b.cost;
    }
};
 
// 计算从起点到终点的最小花费
int getMinMoney(int mStartSpot, int mEndSpot, int mMaxTime)
{
    // 用于存储每个节点每种交通方式的最小花费和时间
    int dist[101][3];
    int time[101][3];
    // 使用优先队列实现Dijkstra算法
    priority_queue<Travel, vector<Travel>, cmp> pq;
 
    // 初始化所有节点的花费和时间为无穷大
    for (int i = 0; i < 101; i++) {
        for (int j = 0; j < 3; j++) {
            dist[i][j] = INT_MAX;
            time[i][j] = INT_MAX;
        }
    }
    // 起点只考虑步行开始
    pq.push({ mStartSpot, 0, 0, WALK });
 
    // 初始化起点的花费和时间
    for (int i = 0; i < 3; i++) {
        dist[mStartSpot][i] = 0;
        time[mStartSpot][i] = 0;
    }
 
    // Dijkstra算法主循环
    while (!pq.empty()) {
        Travel travel = pq.top();
        pq.pop();
 
        // 如果当前时间超过了最大允许时间,则跳过
        if (travel.time > mMaxTime) {
            continue;
        }
 
        // 如果当前花费和时间都不是最优的,则跳过
        if (travel.cost > dist[travel.spot][travel.mode] && travel.time > time[travel.spot][travel.mode]) {
            continue;
        }
 
        // 如果已经到达终点
        if (travel.spot == mEndSpot) {
            // 如果骑自行车到达终点且该点有自行车停车点,或不是骑自行车到达的,则返回当前花费
            if (travel.mode != BIKE || hasCycleStand[travel.spot])
                return travel.cost;
        }
 
        // 更新当前节点的最优花费和时间
        dist[travel.spot][travel.mode] = min(travel.cost, dist[travel.spot][travel.mode]);
        time[travel.spot][travel.mode] = min(travel.time, time[travel.spot][travel.mode]);
 
        // 遍历当前位置(travel.spot)的所有相邻节点
        for (auto& it : adj[travel.spot]) {
            // 从相邻节点信息中提取下一个节点编号和距离
            int next = it.first;
            int dis = it.second;
 
            // 根据当前旅行模式(走路、骑自行车、出租车)决定下一步的行动
            if (travel.mode == WALK) { // 当前模式是走路
                // 走路到下一个节点,
                pq.push({ next, travel.time + 17 * dis, travel.cost, WALK });
 
                // 如果当前位置有自行车站,可以考虑骑自行车到下一个节点
                // 骑自行车到下一个节点
                if (hasCycleStand[travel.spot])
                    pq.push({ next, travel.time + 4 * dis, travel.cost + 4 * dis, BIKE });
 
                // 打车到下一个节点,
                pq.push({ next, travel.time + dis + 7, travel.cost + 19 * dis, TAXI });
            }
 
            else if (travel.mode == BIKE) { // 当前模式是骑自行车
                // 如果当前位置没有自行车站,只能继续骑自行车
                if (!hasCycleStand[travel.spot]) {
                    pq.push({ next, travel.time + 4 * dis, travel.cost + 4 * dis, BIKE });
                }
                else { // 如果有自行车站,可以选择多种交通方式
                    // 走路到下一个节点,
                    pq.push({ next, travel.time + 17 * dis, travel.cost, WALK });
 
                    // 骑自行车到下一个节点,
                    pq.push({ next, travel.time + 4 * dis, travel.cost + 4 * dis, BIKE });
 
                    // 打车到下一个节点
                    pq.push({ next, travel.time + dis + 7, travel.cost + 19 * dis, TAXI });
                }
            }
 
            else { // 当前模式是打车
                // 走路到下一个节点,
                pq.push({ next, travel.time + 17 * dis, travel.cost, WALK });
 
                // 如果当前位置有自行车站,可以考虑骑自行车到下一个节点
                // 骑自行车到下一个节点,
                if (hasCycleStand[travel.spot])
                    pq.push({ next, travel.time + 4 * dis, travel.cost + 4 * dis, BIKE });
 
                // 打车到下一个节点,
                pq.push({ next, travel.time + dis, travel.cost + 19 * dis, TAXI });
            }
        }
    }
    return -1;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CP3圣保罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值