双河造桥问题
上传时间:2026-04-09 10:20 · 作者:ytry1983
最短距离:双河造桥问题 (几何证明版)
通过平行四边形证明:平移法如何缩短路径
📏 几何证明:平行四边形法
根据平行四边形性质,对边相等:
- $AM_1 = A'N_1$
- $A'M_2 = A''N_2$
所以总路程 = $AM_1 + M_1N_1 + N_1M_2 + M_2N_2 + N_2B$
等量代换后 = $(A'N_1 + N_1M_2 + A''N_2) + a + b$
当 $A', N_1, M_2, A''$ 与 $B$ 在一条直线上时,陆地段之和最短。
💡 总结
作图的核心就是通过平移,把分散的陆地路径段“捏”成一条直线。平行四边形不仅是作图的依据,也是证明路径等价性的关键图形。
查看解析页源码
<!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>
<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-indigo-600 p-6 text-white text-center">
<h1 class="text-2xl font-bold">最短距离:双河造桥问题 (几何证明版)</h1>
<p class="mt-2 opacity-90">通过平行四边形证明:平移法如何缩短路径</p>
</div>
<!-- Animation Area -->
<div class="relative h-[500px] w-full bg-slate-50 border-y border-slate-200 overflow-hidden">
<svg id="animation-svg" class="w-full h-full" viewBox="0 0 600 500" preserveAspectRatio="xMidYMid meet">
<!-- 河流背景 1 (宽度 a) -->
<rect x="0" y="80" width="600" height="50" fill="#e0f2fe" opacity="0.8" />
<line x1="0" y1="80" x2="600" y2="80" stroke="#0ea5e9" stroke-width="1.5" stroke-dasharray="4" />
<line x1="0" y1="130" x2="600" y2="130" stroke="#0ea5e9" stroke-width="1.5" stroke-dasharray="4" />
<text x="540" y="75" fill="#0ea5e9" class="text-xs font-bold italic">l1</text>
<text x="540" y="145" fill="#0ea5e9" class="text-xs font-bold italic">l2</text>
<text x="20" y="110" fill="#0369a1" class="text-xs font-bold">河流 a</text>
<!-- 河流背景 2 (宽度 b) -->
<rect x="0" y="200" width="600" height="70" fill="#e0f2fe" opacity="0.8" />
<line x1="0" y1="200" x2="600" y2="200" stroke="#0ea5e9" stroke-width="1.5" stroke-dasharray="4" />
<line x1="0" y1="270" x2="600" y2="270" stroke="#0ea5e9" stroke-width="1.5" stroke-dasharray="4" />
<text x="540" y="195" fill="#0ea5e9" class="text-xs font-bold italic">l3</text>
<text x="540" y="285" fill="#0ea5e9" class="text-xs font-bold italic">l4</text>
<text x="20" y="240" fill="#0369a1" class="text-xs font-bold">河流 b</text>
<!-- 关键点 A 和 B -->
<g id="points-static">
<circle cx="280" cy="30" r="6" fill="#ef4444" />
<text x="265" y="22" class="font-bold fill-slate-800">村庄 A</text>
<circle cx="450" cy="380" r="6" fill="#ef4444" />
<text x="440" y="405" class="font-bold fill-slate-800">村庄 B</text>
</g>
<!-- 动态图层 -->
<g id="dynamic-layers"></g>
</svg>
<!-- Overlay Info -->
<div id="info-box" class="absolute bottom-6 left-6 right-6 bg-white/90 backdrop-blur-md p-4 rounded-xl shadow-lg border border-indigo-100 transition-opacity duration-300">
<h3 id="step-title" class="text-lg font-bold text-indigo-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-indigo-600 text-white hover:bg-indigo-700 transition-all shadow-lg shadow-indigo-200 active:scale-95">
下一步
</button>
</div>
</div>
<!-- Explanation -->
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full">
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h4 class="font-bold text-indigo-800 flex items-center gap-2">
<span>📏</span> 几何证明:平行四边形法
</h4>
<div class="text-sm text-slate-600 mt-2 space-y-2 leading-relaxed">
<p>根据平行四边形性质,对边相等:</p>
<ul class="list-disc list-inside bg-indigo-50 p-2 rounded text-indigo-900">
<li>$AM_1 = A'N_1$</li>
<li>$A'M_2 = A''N_2$</li>
</ul>
<p>所以总路程 = $AM_1 + M_1N_1 + N_1M_2 + M_2N_2 + N_2B$</p>
<p>等量代换后 = $(A'N_1 + N_1M_2 + A''N_2) + a + b$</p>
<p>当 $A', N_1, M_2, A''$ 与 $B$ 在一条直线上时,陆地段之和最短。</p>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h4 class="font-bold text-indigo-800 flex items-center gap-2">
<span>💡</span> 总结
</h4>
<p class="text-sm text-slate-600 mt-2">
作图的核心就是通过平移,把分散的陆地路径段“捏”成一条直线。平行四边形不仅是作图的依据,也是证明路径等价性的关键图形。
</p>
</div>
</div>
<script>
const steps = [
{ title: "1. 初始状态", desc: "村庄 A 和 B 隔着两条河。每条河上的桥必须垂直于河岸。" },
{ title: "2. 第一次平移", desc: "将 A 向下平移河流 a 的宽度,得到点 A'。AA' 等于第一座桥的长。" },
{ title: "3. 第二次平移", desc: "将 A' 向下平移河流 b 的宽度,得到点 A''。此时 AA'' 是两座桥的总长。" },
{ title: "4. 连结 A''B 找落点", desc: "连结 A''B。它与 l4 的交点 N2 是第二座桥的出口。这样我们把陆地段凑成了直线。" },
{ title: "5. 显示平行四边形证明", desc: "通过平行四边形可以看出:AM₁=A'N₁,A'M₂=A''N₂。因此折线总长等于直线 A''B 加上两座桥宽。" }
];
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-indigo-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-indigo-600 w-12' : 'bg-slate-300 w-8'}`;
});
dynamicLayer.innerHTML = '';
const pA = { x: 280, y: 30 };
const pB = { x: 450, y: 380 };
const riverA_h = 50;
const riverB_h = 70;
const pAPrime = { x: 280, y: 30 + riverA_h };
const pADoublePrime = { x: 280, y: 30 + riverA_h + riverB_h };
// 几何计算 (交点)
const k = (pB.y - pADoublePrime.y) / (pB.x - pADoublePrime.x);
// N2 在 l4 (y=270)
const pN2 = { x: (270 - pADoublePrime.y) / k + pADoublePrime.x, y: 270 };
const pM2 = { x: pN2.x, y: 200 };
// N1 在 l2 (y=130)
const pN1 = { x: (130 - pAPrime.y) / k + pAPrime.x, y: 130 };
const pM1 = { x: pN1.x, y: 80 };
if (currentStep >= 1) {
createSVGElement('line', { x1: pA.x, y1: pA.y, x2: pAPrime.x, y2: pAPrime.y, stroke: '#f59e0b', 'stroke-width': 1.5, 'stroke-dasharray': '3' });
createSVGElement('circle', { cx: pAPrime.x, cy: pAPrime.y, r: 4, fill: '#f59e0b' });
createSVGElement('text', { x: pAPrime.x - 45, y: pAPrime.y + 5, class: 'fill-amber-600 text-[10px] font-bold', textContent: "A' (平移 a)" });
}
if (currentStep >= 2) {
createSVGElement('line', { x1: pAPrime.x, y1: pAPrime.y, x2: pADoublePrime.x, y2: pADoublePrime.y, stroke: '#f59e0b', 'stroke-width': 1.5, 'stroke-dasharray': '3' });
createSVGElement('circle', { cx: pADoublePrime.x, cy: pADoublePrime.y, r: 5, fill: '#f59e0b', class: 'animate-pulse-custom' });
createSVGElement('text', { x: pADoublePrime.x - 50, y: pADoublePrime.y + 5, class: 'fill-amber-600 text-[10px] font-bold', textContent: "A'' (平移 a+b)" });
}
if (currentStep >= 3) {
createSVGElement('line', { x1: pADoublePrime.x, y1: pADoublePrime.y, x2: pB.x, y2: pB.y, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '6' });
createSVGElement('line', { x1: pM2.x, y1: pM2.y, x2: pN2.x, y2: pN2.y, stroke: '#10b981', 'stroke-width': 4 });
createSVGElement('text', { x: pN2.x + 8, y: pN2.y + 10, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'N2' });
createSVGElement('text', { x: pM2.x + 8, y: pM2.y - 2, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'M2' });
}
if (currentStep >= 4) {
// 绘制平行四边形 1: A-A'-N1-M1
const poly1 = `${pA.x},${pA.y} ${pAPrime.x},${pAPrime.y} ${pN1.x},${pN1.y} ${pM1.x},${pM1.y}`;
createSVGElement('polygon', { points: poly1, fill: '#fbbf24', opacity: 0.2, stroke: '#d97706', 'stroke-width': 1, 'stroke-dasharray': '2' });
// 绘制平行四边形 2: A'-A''-N2-M2
const poly2 = `${pAPrime.x},${pAPrime.y} ${pADoublePrime.x},${pADoublePrime.y} ${pN2.x},${pN2.y} ${pM2.x},${pM2.y}`;
createSVGElement('polygon', { points: poly2, fill: '#fbbf24', opacity: 0.2, stroke: '#d97706', 'stroke-width': 1, 'stroke-dasharray': '2' });
// 桥 M1N1
createSVGElement('line', { x1: pM1.x, y1: pM1.y, x2: pN1.x, y2: pN1.y, stroke: '#10b981', 'stroke-width': 4 });
createSVGElement('text', { x: pN1.x + 8, y: pN1.y + 10, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'N1' });
createSVGElement('text', { x: pM1.x + 8, y: pM1.y - 2, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'M1' });
// 最终折线路径
const pathData = `M ${pA.x} ${pA.y} L ${pM1.x} ${pM1.y} M ${pN1.x} ${pN1.y} L ${pM2.x} ${pM2.y} M ${pN2.x} ${pN2.y} L ${pB.x} ${pB.y}`;
createSVGElement('path', { d: pathData, stroke: '#ef4444', 'stroke-width': 3, fill: 'none' });
// 强调 AM1 = A'N1 和 A'M2 = A''N2
createSVGElement('text', { x: (pA.x + pM1.x)/2 - 30, y: (pA.y + pM1.y)/2, class: 'fill-red-600 text-[10px] italic', textContent: 'AM₁' });
createSVGElement('text', { x: (pAPrime.x + pN1.x)/2 + 5, y: (pAPrime.y + pN1.y)/2, class: 'fill-blue-600 text-[10px] italic', textContent: "A'N₁" });
}
}
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>