将军饮马
上传时间:2026-04-09 10:59 · 作者:ytry1983
最短距离:将军饮马 (第10题)
利用“轴对称”原理求解线段之和最小值
📝 数学计算过程
1. **对称性**:作 $A$ 关于河岸的对称点 $A'$,则 $AC = A'C = 400\text{m}$。
2. **最短路径**:连接 $A'B$,根据“两点之间线段最短”,线段 $A'B$ 的长度即为最短路程。
3. **勾股定理**:构造直角三角形,直角边长分别为:
- 水平方向距离 = $CD = 800\text{m}$
- 垂直方向距离 = $AC + BD = 400 + 200 = 600\text{m}$
4. **结果**:最短路程 = $\sqrt{800^2 + 600^2} = 1000\text{m}$。
💡 饮水点 $P$ 的位置
通过相似三角形推导,饮水点 $P$ 距离 $C$ 点的距离为:
$800 \times \frac{400}{400+200} = 800 \times \frac{2}{3} \approx 533.3\text{米}$。
查看解析页源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>最短距离:将军饮马问题演示</title>
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 MathJax 配置 -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']]
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
},
startup: {
ready: () => {
MathJax.startup.defaultReady();
console.log('MathJax is ready');
// 初始渲染
window.renderMath();
}
}
};
</script>
<!-- 引入 MathJax 渲染库 (v3) -->
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<style>
.step-transition {
transition: all 0.5s ease-in-out;
}
@keyframes pulse-dot {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.3); opacity: 0.7; }
}
.animate-pulse-custom {
animation: pulse-dot 2s infinite;
}
.math-font {
font-family: 'Times New Roman', serif;
font-style: italic;
}
</style>
</head>
<body class="bg-slate-50 min-h-screen font-sans p-4 md:p-8 flex flex-col items-center">
<div class="max-w-4xl w-full bg-white rounded-2xl shadow-xl overflow-hidden border border-slate-200">
<!-- Header -->
<div class="bg-blue-600 p-6 text-white text-center">
<h1 class="text-2xl font-bold text-white">最短距离:将军饮马 (第10题)</h1>
<p class="mt-2 opacity-90 text-white">利用“轴对称”原理求解线段之和最小值</p>
</div>
<!-- Animation Area -->
<div class="relative h-[600px] w-full bg-slate-50 border-y border-slate-200 overflow-hidden">
<!-- 视图优化:y轴从-150开始,确保对称点可见;底部预留空间防止遮挡 -->
<svg id="animation-svg" class="w-full h-full" viewBox="0 -150 800 750" preserveAspectRatio="xMidYMid meet">
<!-- 河流背景 -->
<rect x="0" y="150" width="800" height="40" fill="#e0f2fe" opacity="0.8" />
<line x1="0" y1="150" x2="800" y2="150" stroke="#0ea5e9" stroke-width="2" />
<text x="740" y="140" fill="#0ea5e9" class="text-sm font-bold italic">河岸 CD</text>
<text x="100" y="140" class="font-bold fill-slate-600">C</text>
<text x="700" y="140" class="font-bold fill-slate-600">D</text>
<g id="points-static">
<!-- A点位置优化 -->
<circle cx="100" cy="390" r="6" fill="#ef4444" />
<text x="80" y="415" class="font-bold fill-slate-800">地点 A (牧童)</text>
<text x="110" y="380" class="text-[10px] fill-slate-400 italic">AC = 400m</text>
<!-- B点位置优化 -->
<circle cx="580" cy="270" r="6" fill="#ef4444" />
<text x="590" y="285" class="font-bold fill-slate-800">地点 B (家)</text>
<text x="520" y="260" class="text-[10px] fill-slate-400 italic">BD = 200m</text>
</g>
<g id="dynamic-layers"></g>
</svg>
<!-- Overlay Info - 背景更透明,且设置 pointer-events-none 防止干扰点击 -->
<div id="info-box" class="absolute bottom-4 left-6 right-6 bg-white/80 backdrop-blur-sm p-4 rounded-xl shadow-lg border border-blue-100 transition-opacity duration-300 pointer-events-none">
<h3 id="step-title" class="text-lg font-bold text-blue-900"></h3>
<p id="step-desc" class="text-slate-700 text-sm mt-1"></p>
</div>
</div>
<!-- Controls -->
<div class="p-6 flex justify-between items-center bg-slate-50">
<button id="prev-btn" class="px-6 py-2 rounded-full border border-slate-300 text-slate-600 hover:bg-white transition-all active:scale-95 disabled:opacity-50">
上一步
</button>
<div id="dot-indicators" class="flex gap-2"></div>
<button id="next-btn" class="px-6 py-2 rounded-full bg-blue-600 text-white hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 active:scale-95">
下一步
</button>
</div>
</div>
<!-- Math Calculation Box -->
<div id="math-box" class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full pb-12">
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h4 class="font-bold text-blue-800 flex items-center gap-2">
<span>📝</span> 数学计算过程
</h4>
<div class="text-sm text-slate-600 mt-2 space-y-2 leading-relaxed">
<p>1. **对称性**:作 $A$ 关于河岸的对称点 $A'$,则 $AC = A'C = 400\text{m}$。</p>
<p>2. **最短路径**:连接 $A'B$,根据“两点之间线段最短”,线段 $A'B$ 的长度即为最短路程。</p>
<p>3. **勾股定理**:构造直角三角形,直角边长分别为:</p>
<ul class="list-disc list-inside ml-4">
<li>水平方向距离 = $CD = 800\text{m}$</li>
<li>垂直方向距离 = $AC + BD = 400 + 200 = 600\text{m}$</li>
</ul>
<p>4. **结果**:最短路程 = $\sqrt{800^2 + 600^2} = 1000\text{m}$。</p>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h4 class="font-bold text-blue-800 flex items-center gap-2">
<span>💡</span> 饮水点 $P$ 的位置
</h4>
<p class="text-sm text-slate-600 mt-2">
通过相似三角形推导,饮水点 $P$ 距离 $C$ 点的距离为:<br>
$800 \times \frac{400}{400+200} = 800 \times \frac{2}{3} \approx 533.3\text{米}$。
</p>
</div>
</div>
<script>
// 定义全局渲染函数,包含安全检查
window.renderMath = () => {
if (window.MathJax && typeof MathJax.typesetPromise === 'function') {
MathJax.typesetPromise([document.getElementById('info-box'), document.getElementById('math-box')]).catch((err) => console.log(err));
} else if (window.MathJax && typeof MathJax.typeset === 'function') {
MathJax.typeset([document.getElementById('info-box'), document.getElementById('math-box')]);
}
};
const steps = [
{ title: "1. 初始场景", desc: "地点 $A$(牧童)和 $B$(家)在河岸 $CD$ 的同侧。需要找到岸上的一点 $P$ 使得 $AP+PB$ 最短。" },
{ title: "2. 作对称点 $A'$", desc: "以河岸为对称轴,作 $A$ 的对称点 $A'$。此时 $AC = A'C = 400\text{m}$。" },
{ title: "3. 连接 $A'B$", desc: "由于 $AP = A'P$,所以 $AP+PB = A'P+PB$。根据两点间线段最短,$A'B$ 即为所求路径。" },
{ title: "4. 确定饮水点 $P$", desc: "线段 $A'B$ 与河岸的交点即为最优饮水位置 $P$。" },
{ title: "5. 最终路径与计算", desc: "最短路程为 $A'B$ 的长度。构建直角三角形(底 $800$,高 $600$),斜边为 $\sqrt{800^2 + 600^2} = 1000$ 米。" }
];
let currentStep = 0;
const dynamicLayer = document.getElementById('dynamic-layers');
const stepTitle = document.getElementById('step-title');
const stepDesc = document.getElementById('step-desc');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const dotIndicators = document.getElementById('dot-indicators');
steps.forEach((_, i) => {
const dot = document.createElement('div');
dot.className = `h-2 w-8 rounded-full transition-all ${i === 0 ? 'bg-blue-600 w-12' : 'bg-slate-300'}`;
dotIndicators.appendChild(dot);
});
function updateUI() {
stepTitle.textContent = steps[currentStep].title;
stepDesc.textContent = steps[currentStep].desc;
prevBtn.disabled = currentStep === 0;
nextBtn.textContent = currentStep === steps.length - 1 ? "重新开始" : "下一步";
Array.from(dotIndicators.children).forEach((dot, i) => {
dot.className = `h-2 transition-all rounded-full ${i === currentStep ? 'bg-blue-600 w-12' : 'bg-slate-300 w-8'}`;
});
dynamicLayer.innerHTML = '';
const riverY = 150;
const pA = { x: 100, y: 390 };
const pB = { x: 580, y: 270 };
const pAPrime = { x: 100, y: 150 - 240 };
if (currentStep >= 1) {
createSVGElement('line', { x1: pA.x, y1: pA.y, x2: pAPrime.x, y2: pAPrime.y, stroke: '#94a3b8', 'stroke-width': 1.5, 'stroke-dasharray': '4' });
createSVGElement('circle', { cx: pAPrime.x, cy: pAPrime.y, r: 6, fill: '#f59e0b', class: 'animate-pulse-custom' });
createSVGElement('text', { x: 75, y: pAPrime.y - 15, class: 'fill-amber-600 font-bold', textContent: "A'" });
}
if (currentStep >= 2) {
createSVGElement('line', { x1: pAPrime.x, y1: pAPrime.y, x2: pB.x, y2: pB.y, stroke: '#94a3b8', 'stroke-width': 2, 'stroke-dasharray': '8' });
}
if (currentStep >= 3) {
const k = (pB.y - pAPrime.y) / (pB.x - pAPrime.x);
const pX = (riverY - pAPrime.y) / k + pAPrime.x;
createSVGElement('circle', { cx: pX, cy: riverY, r: 5, fill: '#10b981' });
createSVGElement('text', { x: pX - 5, y: riverY - 15, class: 'fill-emerald-700 font-bold text-xs', textContent: 'P' });
}
if (currentStep >= 4) {
const k = (pB.y - pAPrime.y) / (pB.x - pAPrime.x);
const pX = (riverY - pAPrime.y) / k + pAPrime.x;
createSVGElement('line', { x1: pA.x, y1: pA.y, x2: pX, y2: riverY, stroke: '#ef4444', 'stroke-width': 3 });
createSVGElement('line', { x1: pX, y1: riverY, x2: pB.x, y2: pB.y, stroke: '#ef4444', 'stroke-width': 3 });
createSVGElement('path', { d: `M ${pAPrime.x} ${pAPrime.y} L ${pB.x} ${pAPrime.y} L ${pB.x} ${pB.y}`, fill: 'none', stroke: '#3b82f6', 'stroke-width': 1, 'stroke-dasharray': '2' });
}
// 安全渲染数学公式
window.renderMath();
}
function createSVGElement(type, props) {
const el = document.createElementNS('http://www.w3.org/2000/svg', type);
for (let key in props) {
if (key === 'textContent') {
el.textContent = props[key];
} else {
el.setAttribute(key, props[key]);
}
}
dynamicLayer.appendChild(el);
return el;
}
nextBtn.addEventListener('click', () => {
currentStep = (currentStep + 1) % steps.length;
updateUI();
});
prevBtn.addEventListener('click', () => {
if (currentStep > 0) {
currentStep--;
updateUI();
}
});
// 初始执行一次
updateUI();
</script>
</body>
</html>