Here is a complexity map under the assumption P ≠ NP: a band of P (easy) at the bottom, NP-complete (hardest) at the top, and the NP-intermediate strip between them. Click each problem to drop it onto the layer where complexity theorists believe it belongs.
<p class="hint">Under the assumption <b>P ≠ NP</b>, the world of NP splits into three layers. Click each problem to drop it onto the layer where complexity theorists place it. Watch the <b>middle strip</b> fill up — Ladner proved it can never be empty.</p>
<div id="map" class="map">
<div class="layer top" data-layer="2"><span class="lbl">NP-complete — the hardest</span><div class="bin" id="bin2"></div></div>
<div class="layer middle" data-layer="1"><span class="lbl">NP-intermediate — the limbo</span><div class="bin" id="bin1"></div></div>
<div class="layer bot" data-layer="0"><span class="lbl">P — the easy</span><div class="bin" id="bin0"></div></div>
</div>
<div class="tray" id="tray"></div>
<div class="status" id="status">Click a problem to place it.</div>
<div class="btns">
<button id="auto" type="button">Place all correctly</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.map { display: flex; flex-direction: column; gap: 4px; }
.layer { border-radius: 8px; padding: .35rem .55rem; min-height: 58px; border: 1px solid; }
.layer .lbl { font-size: .72rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase; opacity: .8; }
.layer.top { background: #fdecec; border-color: #e9b6b6; color: #7d1f1f; }
.layer.middle { background: #fff6e0; border-color: #e9d29a; color: #7a5a12; }
.layer.bot { background: #e8f6ec; border-color: #a9d9ba; color: #1d5e36; }
.bin { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .3rem; min-height: 24px; }
.tray { display: flex; flex-wrap: wrap; gap: .35rem; margin: .6rem 0; }
.chip { font: 600 13px system-ui, sans-serif; padding: .35rem .6rem; border-radius: 999px;
border: 1px solid #1d3557; background: #fff; color: #1d3557; cursor: pointer; transition: all .12s; }
.chip:hover { background: #eef2f7; }
.chip.placed { background: #1d3557; color: #fff; cursor: default; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ladner's theorem (1975): if P != NP, NP splits into three layers and the
// MIDDLE one (NP-intermediate) is provably non-empty. This map places famous
// problems on the layer where complexity theory puts them. Layers:
// 0 = P (easy) 1 = NP-intermediate (limbo) 2 = NP-complete (hardest)
const PROBLEMS = [
{ id: 'sort', name: 'Sorting', layer: 0 },
{ id: 'shortest', name: 'Shortest path', layer: 0 },
{ id: 'matching', name: 'Bipartite matching', layer: 0 },
{ id: 'factor', name: 'Integer factoring', layer: 1 },
{ id: 'iso', name: 'Graph isomorphism', layer: 1 },
{ id: 'dlog', name: 'Discrete log', layer: 1 },
{ id: 'sat', name: 'SAT', layer: 2 },
{ id: 'tsp', name: 'Traveling salesman', layer: 2 },
{ id: 'clique', name: 'Max clique', layer: 2 }
];
let cursor = 0; // next problem in the tray to place
function chip(p, placed){
const c = document.createElement('button');
c.type = 'button';
c.className = 'chip' + (placed ? ' placed' : '');
c.textContent = p.name;
c.dataset.id = p.id;
return c;
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function renderTray(){
const tray = document.getElementById('tray');
tray.innerHTML = '';
[0,1,2].forEach(L => document.getElementById('bin'+L).innerHTML = '');
PROBLEMS.forEach((p, i) => {
if (i < cursor){
document.getElementById('bin'+p.layer).appendChild(chip(p, true));
} else {
const c = chip(p, false);
c.addEventListener('click', () => place(i));
tray.appendChild(c);
}
});
}
// Place the *next* tray problem onto its theoretically-correct layer.
function place(i){
if (i !== cursor) return; // must place left-to-right
cursor++;
renderTray();
const mid = PROBLEMS.filter((p,j) => j < cursor && p.layer === 1).length;
if (cursor >= PROBLEMS.length)
setStatus(fmt('All placed. The middle strip holds %mid% problems — Ladner guarantees it is never empty if P ≠ NP.', { mid: mid }), 'ok');
else
setStatus(fmt('Placed "%name%". Click the next one.', { name: PROBLEMS[i].name }), '');
}
function auto(){ while (cursor < PROBLEMS.length) place(cursor); }
function reset(){ cursor = 0; renderTray(); setStatus('Click a problem to place it.', ''); }
document.getElementById('auto').addEventListener('click', auto);
document.getElementById('reset').addEventListener('click', reset);
renderTray();
<p class="hint">Under the assumption <b>P ≠ NP</b>, the world of NP splits into three layers. Click each problem to drop it onto the layer where complexity theorists place it. Watch the <b>middle strip</b> fill up — Ladner proved it can never be empty.</p>
<div id="map" class="map">
<div class="layer top" data-layer="2"><span class="lbl">NP-complete — the hardest</span><div class="bin" id="bin2"></div></div>
<div class="layer middle" data-layer="1"><span class="lbl">NP-intermediate — the limbo</span><div class="bin" id="bin1"></div></div>
<div class="layer bot" data-layer="0"><span class="lbl">P — the easy</span><div class="bin" id="bin0"></div></div>
</div>
<div class="tray" id="tray"></div>
<div class="status" id="status">Click a problem to place it.</div>
<div class="btns">
<button id="auto" type="button">Place all correctly</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.map { display: flex; flex-direction: column; gap: 4px; }
.layer { border-radius: 8px; padding: .35rem .55rem; min-height: 58px; border: 1px solid; }
.layer .lbl { font-size: .72rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase; opacity: .8; }
.layer.top { background: #fdecec; border-color: #e9b6b6; color: #7d1f1f; }
.layer.middle { background: #fff6e0; border-color: #e9d29a; color: #7a5a12; }
.layer.bot { background: #e8f6ec; border-color: #a9d9ba; color: #1d5e36; }
.bin { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .3rem; min-height: 24px; }
.tray { display: flex; flex-wrap: wrap; gap: .35rem; margin: .6rem 0; }
.chip { font: 600 13px system-ui, sans-serif; padding: .35rem .6rem; border-radius: 999px;
border: 1px solid #1d3557; background: #fff; color: #1d3557; cursor: pointer; transition: all .12s; }
.chip:hover { background: #eef2f7; }
.chip.placed { background: #1d3557; color: #fff; cursor: default; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ladner's theorem (1975): if P != NP, NP splits into three layers and the
// MIDDLE one (NP-intermediate) is provably non-empty. This map places famous
// problems on the layer where complexity theory puts them. Layers:
// 0 = P (easy) 1 = NP-intermediate (limbo) 2 = NP-complete (hardest)
const PROBLEMS = [
{ id: 'sort', name: 'Sorting', layer: 0 },
{ id: 'shortest', name: 'Shortest path', layer: 0 },
{ id: 'matching', name: 'Bipartite matching', layer: 0 },
{ id: 'factor', name: 'Integer factoring', layer: 1 },
{ id: 'iso', name: 'Graph isomorphism', layer: 1 },
{ id: 'dlog', name: 'Discrete log', layer: 1 },
{ id: 'sat', name: 'SAT', layer: 2 },
{ id: 'tsp', name: 'Traveling salesman', layer: 2 },
{ id: 'clique', name: 'Max clique', layer: 2 }
];
let cursor = 0; // next problem in the tray to place
function chip(p, placed){
const c = document.createElement('button');
c.type = 'button';
c.className = 'chip' + (placed ? ' placed' : '');
c.textContent = p.name;
c.dataset.id = p.id;
return c;
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function renderTray(){
const tray = document.getElementById('tray');
tray.innerHTML = '';
[0,1,2].forEach(L => document.getElementById('bin'+L).innerHTML = '');
PROBLEMS.forEach((p, i) => {
if (i < cursor){
document.getElementById('bin'+p.layer).appendChild(chip(p, true));
} else {
const c = chip(p, false);
c.addEventListener('click', () => place(i));
tray.appendChild(c);
}
});
}
// Place the *next* tray problem onto its theoretically-correct layer.
function place(i){
if (i !== cursor) return; // must place left-to-right
cursor++;
renderTray();
const mid = PROBLEMS.filter((p,j) => j < cursor && p.layer === 1).length;
if (cursor >= PROBLEMS.length)
setStatus(fmt('All placed. The middle strip holds %mid% problems — Ladner guarantees it is never empty if P ≠ NP.', { mid: mid }), 'ok');
else
setStatus(fmt('Placed "%name%". Click the next one.', { name: PROBLEMS[i].name }), '');
}
function auto(){ while (cursor < PROBLEMS.length) place(cursor); }
function reset(){ cursor = 0; renderTray(); setStatus('Click a problem to place it.', ''); }
document.getElementById('auto').addEventListener('click', auto);
document.getElementById('reset').addEventListener('click', reset);
renderTray();
Notice that the middle strip is not optional. The classic suspects — factoring (the security of RSA) and graph isomorphism — keep landing there: easy to check a solution, but no known polynomial algorithm and no known NP-completeness proof. Ladner guarantees that if P ≠ NP, this strip is never empty; in fact it contains an infinite, layered hierarchy of distinct difficulties.
Comments
Loading comments...