返回列表
数学

最短距离造桥问题演示动画

上传时间:2026-04-09 10:19 · 作者:ytry1983

解题过程与思路预览 请仅渲染可信 HTML
最短距离:造桥选址问题动画演示

最短距离:造桥选址问题动画演示

利用“平移”思想解决线段和最小问题

河 流 (宽度为 d) 村庄 A 村庄 B

💡 几何原理

设桥宽为 $d$。路径全长 = $AM + MN + NB$。
因为桥 $MN$ 垂直于河岸且长度固定,所以 $MN = d$。
通过平移,构造平行四边形 $AMNA'$,使得 $AM = A'N$。
则路径长度 = $A'N + d + NB$。要使和最小,只需 $A'N + NB$ 最小。
根据两点之间线段最短,$A', N, 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-orange {
            0%, 100% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.7; transform: scale(1.2); }
        }
        .animate-pulse-custom {
            animation: pulse-orange 2s infinite;
        }
    </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">
        <!-- Header -->
        <div class="bg-blue-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-[400px] md:h-[500px] w-full bg-blue-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">
                <!-- 河流背景 -->
                <rect x="0" y="150" width="600" height="100" fill="#e0f2fe" />
                <line x1="0" y1="150" x2="600" y2="150" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="5,5" />
                <line x1="0" y1="250" x2="600" y2="250" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="5,5" />
                <text x="10" y="210" fill="#0ea5e9" class="text-sm italic font-bold">河 流 (宽度为 d)</text>

                <!-- 点 A 和 B -->
                <g id="points-static">
                    <circle cx="100" cy="50" r="6" fill="#ef4444" />
                    <text x="90" y="35" class="font-bold">村庄 A</text>
                    
                    <circle cx="500" cy="350" r="6" fill="#ef4444" />
                    <text x="510" y="370" class="font-bold">村庄 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/95 backdrop-blur-sm p-4 rounded-xl shadow-md border border-slate-100 transition-opacity duration-300">
                <h3 id="step-title" class="text-lg font-bold text-slate-800"></h3>
                <p id="step-desc" class="text-slate-600 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>

    <!-- Info Grid -->
    <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-blue-800 flex items-center gap-2">
                <span>💡</span> 几何原理
            </h4>
            <p class="text-sm text-slate-600 mt-2 leading-relaxed">
                设桥宽为 $d$。路径全长 = $AM + MN + NB$。<br/>
                因为桥 $MN$ 垂直于河岸且长度固定,所以 $MN = d$。<br/>
                通过平移,构造平行四边形 $AMNA'$,使得 $AM = A'N$。<br/>
                则路径长度 = $A'N + d + NB$。要使和最小,只需 $A'N + NB$ 最小。<br/>
                根据两点之间线段最短,$A', N, B$ 三点共线时,路径最短。
            </p>
        </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> 解题口诀
            </h4>
            <ul class="text-sm text-slate-600 mt-2 space-y-2 list-disc list-inside">
                <li>岸边两点隔河望</li>
                <li>垂直平移一桥长</li>
                <li>连接平移点与点</li>
                <li>交点之处定桥梁</li>
            </ul>
        </div>
    </div>

    <script>
        const steps = [
            { title: "1. 初始场景", desc: "村庄 A 和 B 隔河相望,河宽为定值,我们需要建一座垂直于河岸的桥 MN。" },
            { title: "2. 平移点 A", desc: "关键:将 A 沿垂直河岸方向向下平移一个河宽的距离,得到点 A'。此时 AA' = MN。" },
            { title: "3. 连接 A'B", desc: "连接 A' 和 B 得到一条直线。根据'两点之间线段最短',A'B 就是除去桥宽后的最短路径。" },
            { title: "4. 确定桥的位置", desc: "线段 A'B 与下岸的交点即为 N,向上作垂线交上岸于 M。MN 即为桥的位置。" },
            { title: "5. 完成最短路径", desc: "最终路径为 A-M-N-B。因为 AM 始终等于 A'N,所以 AM+NB = A'N+NB = 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-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 pointA = { x: 100, y: 50 };
            const pointB = { x: 500, y: 350 };
            const riverY1 = 150;
            const riverY2 = 250;
            const riverWidth = 100;
            const pointAPrime = { x: 100, y: 150 };
            const pointN = { x: 300, y: 250 };
            const pointM = { x: 300, y: 150 };

            if (currentStep >= 1) {
                // A' 点和 AA' 辅助线
                createSVGElement('line', { x1: pointA.x, y1: pointA.y, x2: pointAPrime.x, y2: pointAPrime.y, stroke: '#f97316', 'stroke-width': 2, 'stroke-dasharray': '4' });
                createSVGElement('circle', { cx: pointAPrime.x, cy: pointAPrime.y, r: 6, fill: '#f97316', class: 'animate-pulse-custom' });
                createSVGElement('text', { x: pointAPrime.x - 15, y: pointAPrime.y + 25, class: 'font-bold fill-orange-500', textContent: "A' (平移点)" });
                // 箭头
                createSVGElement('path', { d: `M${pointA.x-5} ${pointAPrime.y-10} L${pointA.x} ${pointAPrime.y} L${pointA.x+5} ${pointAPrime.y-10}`, fill: 'none', stroke: '#f97316', 'stroke-width': 2 });
            }

            if (currentStep >= 2) {
                // A'B 辅助线
                createSVGElement('line', { x1: pointAPrime.x, y1: pointAPrime.y, x2: pointB.x, y2: pointB.y, stroke: '#94a3b8', 'stroke-width': 2, 'stroke-dasharray': '8' });
            }

            if (currentStep >= 3) {
                // 桥 MN
                createSVGElement('line', { x1: pointM.x, y1: pointM.y, x2: pointN.x, y2: pointN.y, stroke: '#10b981', 'stroke-width': 4 });
                createSVGElement('circle', { cx: pointM.x, cy: pointM.y, r: 4, fill: '#10b981' });
                createSVGElement('circle', { cx: pointN.x, cy: pointN.y, r: 4, fill: '#10b981' });
                createSVGElement('text', { x: pointM.x - 20, y: pointM.y - 10, class: 'text-xs font-bold fill-emerald-600', textContent: 'M' });
                createSVGElement('text', { x: pointN.x - 20, y: pointN.y + 20, class: 'text-xs font-bold fill-emerald-600', textContent: 'N' });
            }

            if (currentStep >= 4) {
                // 最终红线路径
                createSVGElement('line', { x1: pointA.x, y1: pointA.y, x2: pointM.x, y2: pointM.y, stroke: '#ef4444', 'stroke-width': 3 });
                createSVGElement('line', { x1: pointN.x, y1: pointN.y, x2: pointB.x, y2: pointB.y, stroke: '#ef4444', 'stroke-width': 3 });
                
                // 强调等量关系
                createSVGElement('line', { x1: pointA.x, y1: pointA.y, x2: pointAPrime.x, y2: pointAPrime.y, stroke: '#f97316', 'stroke-width': 2, opacity: 0.4 });
                createSVGElement('line', { x1: pointM.x, y1: pointM.y, x2: pointN.x, y2: pointN.y, stroke: '#f97316', 'stroke-width': 2, opacity: 0.4 });
            }
        }

        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>