泰森多边形半平面求交 - 洛谷 - P3297 [SDOI2013] 逃考

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

往期相关背景半平面求交 点击前往
voronoi 图求解点击前往

题目大意

题目链接
https://www.luogu.com.cn/problem/P3297

小杨家是一个矩阵,所有亲戚都在矩阵里。小杨一开始只有离一个亲戚最近。
小杨在家里可以随意移动,每次移动都会被最近的亲戚监视到,问小杨想要离开家,最少要被几个亲戚监视。

解析

整体思路是先对所有亲戚所在位置进行三角剖分,然后求出泰森多边形。

这样可以得到每个点所管辖区域邻近区域的关系。

那么从一个区域走到另一个区域是增加1个人头。

最后利用广度优先搜索最短路即可求解。

那么核心问题就是如何求得每个点与附近点的关系。

在这里插入图片描述

最直观的做法是,先进行三角剖分,由于voronoi区域是通过所有边的垂直平分线包围形成的。所以三角形的边就代表点之间的连通关系。

但是会有一些特殊情况,

在这里插入图片描述
如上图,P1, P2, P3, P4是共圆的,蓝色线是其三角剖分。

本来红色线是一个分割线,由于红色线与P3P2的交点与4点距离一样,所以如果想直接从P3走到P2那么会被4个人同时监视到,所以该分割失效。

改进算法:
通过计算垂直平分线,得到区域边界。

利用半平面求交,依次加入边(同时建立对应点到点的关系),如果之前算出来的交点在加入的边上,需要删除之前的边,同时之前建立点的关系也要删除。

注意: 半平面求交,对于非封闭区域需要从第1条边开始往队列里加入。

特殊情况:没有人看管,少于3人看管,看管人员共线

代码


#include<stdio.h>
#include<cmath>
#include <algorithm>
#include <vector>
#include <list>
#include <cstring>
#include <utility>
#include <queue>
#include <map>


using namespace std;
const double EPS = 1e-8;

const int N = 1e6 + 10;
const int M = 1e6 + 10;


int cmp(double d) {
	if (abs(d) < EPS)return 0;
	if (d > 0)return 1;
	return -1;
}

class Point {
public:
	double x, y;
	int id;

	Point() {}
	Point(double a, double b) :x(a), y(b) {}
	Point(const Point& p) :x(p.x), y(p.y), id(p.id) {}

	void in() {
		scanf("%lf %lf", &x, &y);
	}
	void out() {
		printf("%f %f\n", x, y);
	}

	double dis() {
		return sqrt(x * x + y * y);
	}

	double dis2() {
		return x * x + y * y;
	}

	Point operator -() const {
		return Point(-x, -y);
	}

	Point operator -(const Point& p) const {
		return Point(x - p.x, y - p.y);
	}

	Point operator +(const Point& p) const {
		return Point(x + p.x, y + p.y);
	}
	Point operator *(double d)const {
		return Point(x * d, y * d);
	}

	Point operator /(double d)const {
		return Point(x / d, y / d);
	}


	void operator -=(Point& p) {
		x -= p.x;
		y -= p.y;
	}

	void operator +=(Point& p) {
		x += p.x;
		y += p.y;
	}
	void operator *=(double d) {
		x *= d;
		y *= d;
	}

	void operator /=(double d) {
		this ->operator*= (1 / d);
	}

	bool operator<(const Point& a) const {
		return x < a.x || (abs(x - a.x) < EPS && y < a.y);
	}

	bool operator==(const Point& a) const {
		return abs(x - a.x) < EPS && abs(y - a.y) < EPS;
	}
};

// 向量操作

double cross(const Point& a, const Point& b) {
	return a.x * b.y - a.y * b.x;
}

double dot(const Point& a, const Point& b) {
	return a.x * b.x + a.y * b.y;
}


class Point3D {
public:
	double x, y, z;

	Point3D() {}
	Point3D(double a, double b, double c) :x(a), y(b), z(c) {}
	Point3D(const Point3D& p) :x(p.x), y(p.y), z(p.z) {}

	double dis() {
		return sqrt(x * x + y * y + z * z);
	}

	double dis2() {
		return x * x + y * y + z * z;
	}

	Point3D operator -(const Point3D& p) const {
		return Point3D(x - p.x, y - p.y, z - p.z);
	}

	Point3D operator +(const Point3D& p) const {
		return Point3D(x + p.x, y + p.y, z + p.z);
	}
	Point3D operator *(double d)const {
		return Point3D(x * d, y * d, z * d);
	}

	Point3D operator /(double d)const {
		return Point3D(x / d, y / d, z / d);
	}


	void operator -=(Point3D& p) {
		x -= p.x;
		y -= p.y;
		z -= p.z;
	}

	void operator +=(Point3D& p) {
		x += p.x;
		y += p.y;
		z += p.z;
	}
	void operator *=(double d) {
		x *= d;
		y *= d;
		z *= d;
	}

	void operator /=(double d) {
		this ->operator*= (1 / d);
	}
};

// 向量操作
Point3D cross(const Point3D& a, const Point3D& b) {
	return Point3D(a.y * b.z - a.z * b.y, -a.x * b.z + a.z * b.x,
		a.x * b.y - a.y * b.x);
}

double dot(const Point3D& a, const Point3D& b) {
	return a.x * b.x + a.y * b.y + a.z * b.z;
}


class Line {
public:
	Point front, tail;
	int ind;
	double ang;
	Line() {}
	Line(const Point& a, const Point& b) :front(a), tail(b) {
		ang = atan2(front.y - tail.y, front.x - tail.x);
	}
	Line(const Point& a, const Point& b, int i) :front(a), tail(b), ind(i) {
		ang = atan2(front.y - tail.y, front.x - tail.x);
	}

};

/*
0 不相交
1 相交
0 平行/重合
*/
int cross(const Line& a, const Line& b) {
	Point dir1 = a.front - a.tail;
	Point dir2 = b.front - b.tail;
	if (cmp(cross(dir1, dir2)) == 0) {
		return 0;
	}

	if (cmp(cross(a.front - b.tail, dir2)) * cmp(cross(a.tail - b.tail, dir2)) >= 0)return 0;
	if (cmp(cross(b.front - a.tail, dir1)) * cmp(cross(b.tail - a.tail, dir1)) >= 0)return 0;
	return 1;
}


int inCircle(Point p0, Point p1, Point p2, Point p3) {
	Point d1 = p1 - p0;
	Point d2 = p2 - p0;
	if (cross(d1, d2) < 0)return inCircle(p0, p2, p1, p3); // 保证平面法向向上

	// 构建映射点
	Point3D lift0(p0.x, p0.y, p0.dis2());
	Point3D lift1(p1.x, p1.y, p1.dis2());
	Point3D lift2(p2.x, p2.y, p2.dis2());
	Point3D lift3(p3.x, p3.y, p3.dis2());

	Point3D z1(lift1 - lift0), z2(lift2 - lift0);
	Point3D normal = cross(z1, z2); // 计算平面法向
	double project = dot(normal, lift3 - lift0); // 计算点到平面距离

	return cmp(project);
}



class EdgeDelaunay {
public:
	int id;
	std::list<EdgeDelaunay>::iterator c;
	EdgeDelaunay(int id = 0) { this->id = id; }
};

class Delaunay {
public:
	std::list<EdgeDelaunay> head[N];  // graph
	Point p[N];
	int n = 0;

	void init(int psize, Point ps[]) {
		this->n = psize;
		for (int i = 0; i < psize; ++i) head[i].clear();
		memcpy(this->p, ps, sizeof(Point) * n);
		std::sort(this->p, this->p + n);
		divide(0, n - 1);
	}

	void addEdge(int u, int v) {
		head[u].push_front(EdgeDelaunay(v));
		head[v].push_front(EdgeDelaunay(u));
		head[u].begin()->c = head[v].begin();
		head[v].begin()->c = head[u].begin();
	}

	void divide(int l, int r) {
		if (r - l <= 1) {  // #point <= 2
			for (int i = l; i <= r; i++)
				for (int j = i + 1; j <= r; j++) addEdge(i, j);
			return;
		}
		int mid = (l + r) / 2;
		divide(l, mid);
		divide(mid + 1, r);

		std::list<EdgeDelaunay>::iterator it;
		int nowl = l, nowr = r;

		for (int update = 1; update;) {
			// 查找左边最低线位置
			update = 0;
			Point ptL = p[nowl], ptR = p[nowr];
			for (it = head[nowl].begin(); it != head[nowl].end(); it++) {
				Point t = p[it->id];
				double v = cross(ptL - ptR, t - ptR);
				if (cmp(v) > 0 || (cmp(v) == 0 && (t - ptR).dis() < (ptL - ptR).dis())) {
					nowl = it->id, update = 1;
					break;
				}
			}
			if (update) continue;
			// 查找右边最低线位置
			for (it = head[nowr].begin(); it != head[nowr].end(); it++) {
				Point t = p[it->id];
				double v = cross(ptR - ptL, t - ptL);
				if (cmp(v) < 0 || (cmp(v) == 0 && (t - ptL).dis() < (ptL - ptR).dis())) {
					nowr = it->id, update = 1;
					break;
				}
			}
		}

		addEdge(nowl, nowr);  // 添加基线

		for (; true;) {
			Point ptL = p[nowl], ptR = p[nowr];
			int ch = -1, side = 0;
			for (it = head[nowl].begin(); it != head[nowl].end(); it++) {
				if (cmp(cross(ptR - ptL, p[it->id] - ptL)) <= 0)continue; // 判断夹角是否小于180
				if (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0) {
					ch = it->id, side = -1;
				}
			}
			for (it = head[nowr].begin(); it != head[nowr].end(); it++) {
				if (cmp(cross(p[it->id] - ptR, ptL - ptR)) <= 0) continue;// 判断夹角是否小于180
				if (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0) {
					ch = it->id, side = 1;
				}
			}
			if (ch == -1) break;  // 所有线已经加完
			if (side == -1) {
				for (it = head[nowl].begin(); it != head[nowl].end();) {
					// 判断是否相交,边缘不算相交
					if (cross(Line(ptL, p[it->id]), Line(ptR, p[ch]))) {
						head[it->id].erase(it->c);
						head[nowl].erase(it++);
					}
					else {
						it++;
					}
				}
				nowl = ch;
				addEdge(nowl, nowr);
			}
			else {
				for (it = head[nowr].begin(); it != head[nowr].end();) {
					// 判断是否相交,边缘不算相交
					if (cross(Line(ptR, p[it->id]), Line(ptL, p[ch]))) {
						head[it->id].erase(it->c);
						head[nowr].erase(it++);
					}
					else {
						it++;
					}
				}
				nowr = ch;
				addEdge(nowl, nowr);
			}
		}
	}

	std::vector<std::pair<int, int> > getEdge() {
		std::vector<std::pair<int, int> > ret;
		ret.reserve(n);
		std::list<EdgeDelaunay>::iterator it;
		for (int i = 0; i < n; i++) {
			for (it = head[i].begin(); it != head[i].end(); it++) {
				ret.push_back(std::make_pair(p[i].id, p[it->id].id));
			}
		}
		return ret;
	}
};


int cmp(const Line& a, const Line& b) {
	//auto ta = atan2(a.front.y - a.tail.y, a.front.x - a.tail.x);
	//auto tb = atan2(b.front.y - b.tail.y, b.front.x - b.tail.x);

	return cmp(a.ang - b.ang);
}


// 点在直线哪一边>0 左边,<0边
int SideJudge(const Line& a, const Point& b) {
	return cmp(cross(a.front - a.tail, b - a.tail));
}


int LineSort(const Line& a, const Line& b) {
	int c = cmp(a, b);
	if (c)return c < 0;
	return SideJudge(b, a.front) > 0;
}

/*
点p 到 p+r 表示线段1
点q 到 q+s 表示线段2
线段1 上1点用 p' = p+t*r (0<=t<=1)
线段2 上1点用 q' = q+u*s (0<=u<=1)
让两式相等求交点 p+t*r = q+u*s
两边都叉乘s
(p+t*r)Xs = (q+u*s)Xs
pXs + t*rXs = qXs
t = (q-p)Xs/(rXs)
同理,
u = (p-q)Xr/(sXr) -> u = (q-p)Xr/(rXs)

以下分4种情况:
1. 共线,sXr==0 && (q-p)Xr==0, 计算 (q-p)在r上的投影在r长度上的占比t0,
计算(q+s-p)在r上的投影在r长度上的占比t1,查看[t0, t1]是否与范围[0,1]有交集。
如果t0>t1, 则比较[t1, t0]是否与范围[0,1]有交集。
t0 = (q-p)*r/(r*r)
t1 = (q+s-p)*r/(r*r) = t0 + s · r / (r · r)
2. 平行sXr==0 && (q-p)Xr!=0
3. 0<=u<=1 && 0<=t<=1 有交点
4. 其他u, t不在0到范围内,没有交点。
*/
pair<double, double> intersection(const Point& q, const Point& s, const Point& p, const Point& r) {
	// 计算 (q-p)Xr
	auto qpr = cross(q - p, r);
	auto qps = cross(q - p, s);

	auto rXs = cross(r, s);
	if (cmp(rXs) == 0)return { -1, -1 }; // 平行或共线
	// 求解t, u
	// t = (q-p)Xs/(rXs)
	auto t = qps / rXs;

	// u = (q-p)Xr/(rXs)
	auto u = qpr / rXs;

	return { u, t };
}

Point LineCross(const Line& a, const Line& b) {
	Point dira = a.front - a.tail;
	Point dirb = b.front - b.tail;

	auto p = intersection(a.tail, dira, b.tail, dirb);
	return a.tail + dira * p.first;
}


class HalfPlane {
public:
	vector<Line> lines;

	void addLine(const Line& a) {
		lines.push_back(a);
	}

	vector<int> run(vector<Point>& ps) {
		sort(lines.begin(), lines.end(), LineSort);
		vector<int> q(lines.size() + 10);
		vector<Point> t(lines.size() + 10);

		// 查找到第1条线
		int k = lines.size() - 1;
		bool isBoundary = false;
		for (; k >= 0; --k) {
			int nk = (k + 1)%lines.size();
			if (cmp(cross(lines[k].front - lines[k].tail, lines[nk].front - lines[nk].tail)) <= 0) {
				isBoundary = true;
				k = nk;
				break;
			}
		}

		if (!isBoundary)k = 0;

		int l = -1, r = 0;
		q[0] = k;
		for (int i = (k+1)%lines.size(); i !=k; i=(i+1)%lines.size()) {
			while (r - l > 1 && SideJudge(lines[i], t[r]) <= 0)r--;
			while (r - l > 1 && SideJudge(lines[i], t[l + 2]) <= 0)l++;
			q[++r] = i;
			t[r] = LineCross(lines[q[r]], lines[q[r - 1]]);
		}

		while (r - l > 2 && SideJudge(lines[q[l + 1]], t[r]) <= 0)r--;
		vector<int> ans;
		for (int i = l + 1; i <= r; ++i) {
			ans.push_back(lines[q[i]].ind);
			if(i>l+1)ps.push_back(t[i]);
		}
		
		if (!isBoundary) {
			t[r + 1] = LineCross(lines[q[l + 1]], lines[q[r]]);
			ps.push_back(t[r + 1]);
		}

		return ans;
	}
};


class Edge {
public:
	Edge() {}
	Edge(int t, int n, double w) :to(t), next(n), weight(w) {}
	int index;
	int from;
	int to;
	int next;
	double weight;
	bool isValid() const { return to >= 0; }
};

class Graph {
public:
	int size;
	Graph() {}
	void init(int n) {
		size = n;
		head.assign(n, -1);
		edge.resize(M * 2);
		len = 0;
		emptyEdge = Edge(-1, -1, 0);
	}

	Edge headEdge(int a) {
		if (head[a] < 0)return emptyEdge;
		return edge[head[a]];
	}

	Edge nextEdge(const Edge& ed) {
		if (ed.next < 0)return emptyEdge;
		return edge[ed.next];
	}

	void add(int a, int b, double w) {
		//printf("add : %d %d %.6lf\n", a, b, w);
		edge[len] = Edge(b, head[a], w);
		edge[len].index = len;
		head[a] = len;
		len++;
	}

	void del(int ind) {
		edge[ind].to = -1;
	}

private:
	vector<int> head;
	int len;
	Edge emptyEdge;
	vector<Edge> edge;
};

Graph g;

Point oiPs[N];
Delaunay de;
bool isBoundary[N];

int bfs(int n, int start) {
	map<int, bool> vis;
	int step = 1;

	queue<int> qu;
	qu.push(start);
	vis[start] = true;

	while (!qu.empty()) {
		int len = qu.size();
		while (len--) {
			auto front = qu.front();
			qu.pop();
			if (isBoundary[front]) return step;

			for (Edge ed = g.headEdge(front); ed.isValid(); ed = g.nextEdge(ed)) {
				if (vis[ed.to])continue;
				vis[ed.to] = true;
				qu.push(ed.to);
			}
		}

		step++;
	}

	return step;
}

void  solve() {
	int n, t;
	scanf("%d", &t);
	int x1, y1, x0, y0;
	while (t--) {
		scanf("%d", &n);
		scanf("%d%d%d%d", &x1, &y1, &x0, &y0);
		// 输入亲戚坐标
		for (int i = 0; i < n; ++i) {
			int a, b;
			scanf("%d%d", &a, &b);
			oiPs[i].x = a;
			oiPs[i].y = b;
			oiPs[i].id = i;
		}
		if (n == 0) {
			puts("0");
			continue;
		}
		if (n < 4) {
			puts("1");
			continue;
		}

		// 所有点共线
		bool oneLine = true;
		for (int i = 2; i < n; ++i) {
			if (cmp(cross(oiPs[0] - oiPs[1], oiPs[0] - oiPs[i])))oneLine = false;
		}
		if (oneLine) {
			puts("1");
			continue;
		}

		// if (n > 600) exit(1);
		de.init(n, oiPs);

		auto oiedges = de.getEdge();
		vector<vector<int>> link(n, vector<int>());
		for (auto oie : oiedges) {
			link[oie.first].push_back(oie.second);
		}

		g.init(n);

		int start = 0;
		for (int i = 0; i < n; ++i) {
			// 遍历每个边界点,收集邻边,并按照逆时针排序。
			int len = link[i].size();
			auto &ind = link[i];
			HalfPlane hp;
			// 求voronoi 边界之间交点
			for (int j = 0; j < len; ++j) {
				Point mid = (oiPs[i] + oiPs[ind[j]]) / 2;
				Point dir = oiPs[ind[j]] - oiPs[i];
				dir = { -dir.y, dir.x }; // 旋转90度

				hp.addLine(Line(mid+dir, mid, j));
			}
			vector<Point> ps;
			auto ids = hp.run(ps);
			sort(ids.begin(), ids.end());
			int k = 0;
			for (int j = 0; j < len && k<ids.size(); ++j) {
				if (ids[k] == j) {
					g.add(i, ind[j],1);

					k++;
				}
			}

			isBoundary[i] = ps.size()!=ids.size();
			for (auto& p : ps) {
				if (isBoundary[i])break;
				//if (p.x > 0 && p.x < x1 && p.y>0 && p.y < y1)continue;
				if(p.x<0 || p.x>x1 || p.y<0 ||p.y>y1) isBoundary[i] = true;
			}
			if ((oiPs[i] - Point(x0, y0)).dis() < (oiPs[start] - Point(x0, y0)).dis())start = i;
		}
		
		printf("%d\n", bfs(n, start));

	}
}



int main() {
	solve();
	return 0;

}


/*
2
4
10 10 5 5
5 6
3 5
7 5
5 3
17
14 12 7 6
7 11
6 9
7 7
1 10
2 20
1 6
2 6
1 1
2 2
5 1
5 2
13 1
12 2
12 7
13 7
12 11
13 11

5
10 10 5 5

3 3
5 5
3 7
7 3
7 7


5
10 10 5 5

0 0
5 5
0 10
10 0
10 10

3 
10 10 5 5

0 0
5 5
0 10

4
10 10 5 5

0 0
5 5
10 10
6 6



10
10 10 5 5

5 5
4 4
6 4
4 6
6 6
7 7
7 3
3 7
7 3
1 1


*/

本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。创作不易,帮忙点击公众号的链接。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/244921.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Monkey工具之fastbot-iOS实践

背景 目前移动端App上线后 crash 率比较高&#xff0c; 尤其在iOS端。我们需要一款Monkey工具测试App的稳定性&#xff0c;更早的发现crash问题并修复。 去年移动开发者大会上有参加 fastbot 的分享&#xff0c;所以很自然的就想到Fastbot工具。 Fastbot-iOS安装配置 准备工…

变电站蓄电池在线监测系统(论文+源码)

1. 系统设计 本次课题为变电站蓄电池在线监测系统的设计&#xff0c;其系统架构如图3.1所示&#xff0c;包括了主控制器STC89C52单片机&#xff0c;液晶显示器LCD1602,模数转换器ADC0832&#xff0c;电流传感器ACS712&#xff0c;分压电阻&#xff0c;蜂鸣器以及温度传感器。在…

实用篇 | 3D建模中Blender软件的下载及使用[图文详情]

本文基于数字人系列的3D建模工具Blender软件的安装及使用&#xff0c;还介绍了图片生成3D模型的AI工具~ 目录 1.Blender的下载 2.Blender的使用 3.安装插件(通过压缩包安装) 4.实例 4.1.Blender使用MB-Lab插件快速人体模型建构 4.1.1.点击官网&#xff0c;进行下载 4.1.…

消息可靠性保证

回顾RabbitMQ的消息传递过程 如图所示&#xff0c;发生消息丢失的可能阶段也就是生产者发送消息&#xff0c;时rabbitmq存储消息时&#xff0c;消费者消费消息时。项目源码&#xff1a;gitee 生产者发送消息阶段 生产者发送消息时把交换机名写错生产者发送消息时把routingK…

推荐一款好用的包含表格识别的OCR网站

在当今数字化的时代&#xff0c;文字和表格识别已经成为了许多行业的关键技术。无论是处理大量的纸质文档&#xff0c;还是从网络上收集数据&#xff0c;OCR&#xff08;光学字符识别&#xff09;技术都扮演着重要的角色。然而&#xff0c;对于许多用户来说&#xff0c;OCR软件…

Infobright列存数据库原理介绍

简介 Infobright 是一个面向 OLAP 场景的开源列存数据库。比较容易找到代码的版本是 Infobright Community Edition 4.0.7&#xff0c;大概是 2006 年前后的代码。2016 年6 月&#xff0c;Infobright 决定停止开源1。由于它同时提供企业版和社区版&#xff0c;开源版本的功能相…

斑马zebra目标检测数据集VOC+YOLO格式2300张

斑马是由四百万年前的原马进化出来的&#xff0c;最早出现的斑马可能是细纹斑马。有关史前马科动物的化石现存于美国爱达荷州克文的克文化石床国家博物馆。斑马的史前马为“克文马”&#xff08;美洲斑马或者克文斑马&#xff09;&#xff0c;学名为“Equussimplicidens”&…

WordPress VIP收费下载插件Erphpdown v17.0.1 开心版

会员推广下载专业版 WordPress插件&#xff08;erphpdown&#xff09;是模板兔开发的一款针对虚拟资源收费下载/付费下载/付费视频/收费查看/付费阅读/付费查看/VIP下载查看的插件&#xff0c;经过完美测试运行于wordpress 3.x-5.x版本。后续模板兔会增加更多实用的功能。 模板…

docker部署go gin框架 Linux环境

目录 文章目的是什么 环境介绍 Linux 环境下 docker 部署 go gin 详细步骤 部署 gin 文章目的是什么 假设我们学习了 go 语言&#xff0c;在 Linux 上安装了 go 相关的程序&#xff0c;也能直接运行&#xff0c;使用以下命令&#xff1a; go run main.go 假如代码是这样的…

跟着我学Python基础篇:08.集合和字典

往期文章 跟着我学Python基础篇&#xff1a;01.初露端倪 跟着我学Python基础篇&#xff1a;02.数字与字符串编程 跟着我学Python基础篇&#xff1a;03.选择结构 跟着我学Python基础篇&#xff1a;04.循环 跟着我学Python基础篇&#xff1a;05.函数 跟着我学Python基础篇&#…

java设计模式学习之【代理模式】

文章目录 引言代理模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用图片加载示例代码地址 引言 在现实生活中&#xff0c;我们经常使用代理来处理我们不想直接参与或无法直接参与的事务&#xff0c;例如&#xff0c;使用律师来代表法庭上的案件。在软件开发…

计算机网络——网络层——OSPF协议的介绍

什么是 OSPF &#xff1f; OSPF 是一个基于链路状态的自治系统内部路由协议&#xff0c;在 TCP/IP 的网络层中进行路由选择&#xff0c;常用于构建大型企业网络或者服务上的骨干网络。在互联网核心路由器之间也可以使用。 OSPF 概述 OSPF 使用的是 Dijkstra&#xff08;最短…

Vue 实现一个弹出框,允许用户输入信息,并在确认时将输入的信息进行输出到控制台

父组件用来点击按钮弹出弹出框 <!--ParentComponent.vue--> <template><div><button click"showPopupV">点我会有个弹出框&#xff01;&#xff01;&#xff01;</button><PopupComponent v-if"showPopup" :data"p…

【退订】阿里云产品

之前因为学习需要使用了阿里云上的产品服务&#xff0c;项目结束后给忘记了&#xff0c;直到最近阿里云发短信我才知道&#xff1a; 我使用的是datawork的服务&#xff0c;现在先登录阿里云官网&#xff1a; 阿里云-计算&#xff0c;为了无法计算的价值 (aliyun.com) 之后点…

【毕业设计】基于STM32的智能衣柜设计

1、功能说明 功能如下: 1、用stm32控制ds18b20采集温度 2、然后按键可以设置上下限温度&#xff0c; 3、采集的温度低于下限温度时候 打开加热片开始加热&#xff0c; 4、加热到上限温度关闭加热片停止加热&#xff0c; 5、采集的温度可以在oled显示&#xff0c; 6、然后弄个按…

MySQL增量备份与恢复

实验环境 某学校近期在进行期中考试&#xff0c;要求数据库管理员负责一班&#xff0c;二班学生的考试成绩录入&#xff0c;为保证数据的可靠性&#xff0c;数据库管理员在录入学生成绩后均要做数据库备份&#xff0c;并且为了测试备份数据是否可 用&#xff0c;模拟数据丢失故…

MySQL数据库,视图、存储过程与存储函数

数据库对象&#xff1a; 常见的数据库对象&#xff1a; 视图&#xff1a; 视图是一种虚拟表&#xff0c;本身是不具有数据的占用很少的内存空间。 视图建立在已有表的基础上&#xff0c;视图赖以建立的这些表称为基表。 视图的创建和删除只影响视图本身&#xff0c;不影响对…

多云网络互通问题怎么解决——SD-WAN

随着业务的扩张&#xff0c;企业对云资源的用量也越来越大&#xff0c;逐渐形成了混合云架构。要解决多云网络互通的问题&#xff0c;其中一种常见的组网方案是云专线。然而&#xff0c;这种方式也带来了一系列问题&#xff0c;包括&#xff1a; 1、受服务商约束&#xff0c;需…

Docker真的好难用啊,为什么说它移植性好啊?

看起来你对Docker有点困惑和挑战呀。Docker刚开始确实有点难以入门&#xff0c;但是一旦掌握了它的核心概念和操作&#xff0c;你会发现它其实非常强大和便利。 接下来我会根据你提出的问题和场景&#xff0c;详细地解答。 关于你的实际问题&#xff1a; 刚接触时的困难是正。…

【机器学习 | 假设检验系列】假设检验系列—卡方检验(详细案例,数学公式原理推导),最常被忽视得假设检验确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…