[{"data":1,"prerenderedAt":19332},["ShallowReactive",2],{"$fcXLI91EzFg6rt-yDcJ_0xMn27IN8U_0XZ2NyVrwFwGI":3},[4,86,157,229,300,367,434,501,562,633,702,769,837,935,997,1065,1130,1196,1266,1336,1402,1468,1536,1602,1675,1747,1815,1884,1952,2018,2085,2144,2221,2295,2365,2439,2503,2565,2623,2681,2740,2793,2856,2908,2967,3022,3081,3155,3224,3296,3368,3437,3507,3583,3648,3713,3779,3841,3904,3970,4032,4096,4162,4228,4290,4351,4410,4467,4522,4584,4642,4702,4764,4820,4882,4950,5006,5063,5125,5189,5241,5292,5349,5415,5476,5533,5584,5643,5706,5768,5829,5884,5944,6002,6056,6117,6176,6231,6287,6347,6407,6466,6520,6582,6642,6706,6771,6835,6899,6959,7005,7051,7108,7163,7220,7281,7335,7387,7451,7505,7558,7618,7681,7741,7802,7857,7920,7982,8025,8080,8138,8200,8255,8309,8364,8426,8483,8533,8590,8643,8703,8763,8820,8876,8927,8986,9038,9095,9152,9210,9263,9316,9372,9432,9486,9542,9598,9663,9714,9771,9829,9891,9952,10016,10073,10130,10188,10251,10301,10360,10414,10473,10533,10587,10642,10698,10749,10804,10859,10905,10954,11007,11058,11108,11165,11219,11275,11335,11395,11448,11504,11553,11605,11663,11715,11770,11824,11877,11932,11988,12040,12094,12149,12206,12256,12313,12374,12430,12477,12538,12588,12650,12707,12763,12818,12877,12934,12989,13050,13102,13167,13231,13291,13349,13409,13472,13532,13599,13664,13727,13783,13841,13899,13961,14022,14085,14147,14196,14251,14304,14360,14417,14477,14538,14595,14651,14708,14761,14825,14887,14953,15006,15059,15107,15158,15224,15286,15349,15410,15469,15529,15588,15646,15709,15762,15825,15879,15936,15994,16050,16110,16169,16231,16286,16341,16401,16454,16511,16568,16622,16678,16740,16794,16849,16908,16958,17010,17071,17128,17186,17250,17310,17363,17418,17472,17525,17579,17635,17695,17750,17808,17866,17922,17981,18036,18091,18146,18210,18267,18321,18384,18442,18504,18557,18615,18675,18729,18791,18849,18902,18954,19006,19062,19114,19164,19219,19277],{"id":5,"title":6,"level":7,"module":8,"seq":9,"goal":10,"keywords":11,"next":14,"explanation":15,"examples":27,"exercises":35,"problems":80,"summary":84,"nextTitle":85},"01-A1-1","顺序执行","Lv.1","程序设计基础",1,"能按“先做 A、再做 B、最后 C”的顺序写出步骤，并理解顺序改变可能改变结果。",[12,13],"顺序结构","执行顺序","01-A1-2",[16,21],{"title":17,"content":18,"codePy":19,"codeCpp":20},"1. 程序从上往下依次执行","程序从第一行到最后一行一条一条执行，前一步的结果会直接带到下一步——就像做菜要按顺序放料，先放油再放菜。","print(\"加载地图\")\nprint(\"生成角色\")\nprint(\"开始游戏\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C \"加载地图\" \u003C\u003C endl;\n    cout \u003C\u003C \"生成角色\" \u003C\u003C endl;\n    cout \u003C\u003C \"开始游戏\" \u003C\u003C endl;\n    return 0;\n}",{"title":22,"content":23,"codePy":24,"codeCpp":25,"tip":26},"2. 顺序改变，结果也会变","把步骤调换，输出顺序就会跟着变。顺序结构里没有分支和循环，最简单也最基础。","print(\"刷牙\")\nprint(\"起床\")   # 顺序错了，输出就乱了","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C \"刷牙\" \u003C\u003C endl;\n    cout \u003C\u003C \"起床\" \u003C\u003C endl;   \u002F\u002F 顺序错了\n    return 0;\n}","写程序前先想清楚“正确的顺序”，再动笔。",[28],{"title":29,"prompt":30,"analysis":31,"codePy":32,"codeCpp":33,"run":34},"例 1：用三行输出写出生活流程","用 3 行输出写出“起床 → 刷牙 → 上学”的流程。","按时间先后，把每件事写成一行输出即可。","print(\"起床\")\nprint(\"刷牙\")\nprint(\"上学\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C \"起床\" \u003C\u003C endl;\n    cout \u003C\u003C \"刷牙\" \u003C\u003C endl;\n    cout \u003C\u003C \"上学\" \u003C\u003C endl;\n    return 0;\n}","起床\n刷牙\n上学",{"choice":36,"coding":65},[37,46,55],{"id":38,"stem":39,"options":40,"answer":9,"explanation":45},"q1","程序一般从哪一行开始执行？",[41,42,43,44],"最后一行","第一行","随机一行","中间一行","程序默认从上（第一行）往下顺序执行。",{"id":47,"stem":48,"options":49,"answer":9,"explanation":54},"q2","把两行 print 的顺序调换，输出会怎样？",[50,51,52,53],"完全不变","输出顺序也跟着变","程序报错","运行变慢","顺序结构严格按照书写顺序输出。",{"id":56,"stem":57,"options":58,"answer":63,"explanation":64},"q3","下面哪个不是顺序结构的特点？",[59,60,61,62],"依次执行","前一步结果带到下一步","可以随意跳过某一步","最简单最基础",2,"顺序结构不会跳过步骤，必须一步一步走。",[66],{"id":67,"title":68,"prompt":69,"inputFormat":70,"outputFormat":71,"samples":72,"starterPy":75,"starterCpp":76,"hint":77,"referencePy":78,"referenceCpp":79},"c1","按顺序输出三个数","输入三个整数 a、b、c，按 a b c 的顺序用空格输出。","一行三个整数 a b c。","一行三个整数，空格分隔。",[73],{"input":74,"output":74},"1 2 3","a, b, c = map(int, input().split())\n# 按顺序输出 a b c","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b, c;\n    cin >> a >> b >> c;\n    \u002F\u002F 按顺序输出 a b c\n    return 0;\n}","按输入顺序原样输出即可，不用计算。","a, b, c = map(int, input().split())\nprint(a, b, c)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b, c;\n    cin >> a >> b >> c;\n    cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b \u003C\u003C \" \" \u003C\u003C c \u003C\u003C endl;\n    return 0;\n}",[81],{"pid":82,"title":83},"P1001","A+B Problem","顺序结构是程序的“基本功”：代码从上往下依次执行，调换顺序输出就变。先把流程想清楚，再写代码。","变量与赋值",{"id":87,"title":88,"level":7,"module":8,"seq":89,"goal":90,"keywords":91,"explanation":96,"examples":108,"exercises":116,"summary":154,"next":155,"nextTitle":156},"01-A1-10","if \u002F else 语句",10,"能写出 if \u002F else 让程序做选择。",[92,93,94,95],"if","else","分支","缩进",[97,103],{"title":98,"content":99,"codePy":100,"codeCpp":101,"tip":102},"1. if \u002F else 的基本写法","if 后面跟条件，条件为真就执行它下面缩进的代码块；else 处理“否则”的情况。注意 Python 靠缩进区分属于 if 的代码。","score = 85\nif score >= 60:\n    print(\"及格\")\nelse:\n    print(\"不及格\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score = 85;\n    if (score >= 60) {\n        cout \u003C\u003C \"及格\" \u003C\u003C endl;\n    } else {\n        cout \u003C\u003C \"不及格\" \u003C\u003C endl;\n    }\n    return 0;\n}","Python 用缩进（通常 4 个空格）表示“属于 if 的代码块”；C++ 用大括号 {} 包裹。",{"title":104,"content":105,"codePy":106,"codeCpp":107},"2. 只有 if 也可以","如果“不成立”时什么都不用做，可以只写 if，不加 else。","hp = 10\nif hp \u003C 20:\n    print(\"危险！快加血\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int hp = 10;\n    if (hp \u003C 20) cout \u003C\u003C \"危险！快加血\" \u003C\u003C endl;\n    return 0;\n}",[109],{"title":110,"prompt":111,"analysis":112,"codePy":113,"codeCpp":114,"run":115},"例 1：奇偶数","输入一个整数，奇数输出 \"odd\"，偶数输出 \"even\"。","用 n % 2 判断，配合 if \u002F else 两分支。","n = int(input())\nif n % 2 == 1:\n    print(\"odd\")\nelse:\n    print(\"even\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n;\n    cin >> n;\n    if (n % 2 == 1) cout \u003C\u003C \"odd\" \u003C\u003C endl;\n    else cout \u003C\u003C \"even\" \u003C\u003C endl;\n    return 0;\n}","输入 7 → odd",{"choice":117,"coding":139},[118,125,133],{"id":38,"stem":119,"options":120,"answer":9,"explanation":124},"在 Python 中，if 下面的代码靠什么表示“属于 if”？",[121,95,122,123],"大括号 {}","分号","括号 ()","Python 用缩进来划分代码块。",{"id":47,"stem":126,"options":127,"answer":9,"explanation":132},"else 处理的是哪种情况？",[128,129,130,131],"if 条件为真时","if 条件为假时","循环结束时","程序开始时","else 是“否则”，即条件不成立时执行。",{"id":56,"stem":134,"options":135,"answer":9,"explanation":138},"C++ 中包裹 if 代码块的是？",[95,121,136,137],"方括号 []","圆括号 ()","C++ 用大括号 {} 包裹代码块。",[140],{"id":67,"title":141,"prompt":142,"inputFormat":143,"outputFormat":144,"samples":145,"starterPy":149,"starterCpp":150,"hint":151,"referencePy":152,"referenceCpp":153},"最大数","输入两个整数 a、b，输出较大的那个。","一行两个整数 a b。","一行一个整数（较大值）。",[146],{"input":147,"output":148},"3 8","8","a, b = map(int, input().split())\n# 输出较大的数","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    \u002F\u002F 输出较大的数\n    return 0;\n}","用 if 比较 a 和 b。","a, b = map(int, input().split())\nprint(a if a > b else b)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    if (a > b) cout \u003C\u003C a \u003C\u003C endl;\n    else cout \u003C\u003C b \u003C\u003C endl;\n    return 0;\n}","if \u002F else 是程序做选择的工具：条件真执行 if 块，否则执行 else 块。Python 看缩进，C++ 看大括号。","01-A1-11","多分支选择",{"id":155,"title":156,"level":7,"module":8,"seq":158,"goal":159,"keywords":160,"explanation":165,"examples":177,"exercises":185,"summary":226,"next":227,"nextTitle":228},11,"能理解“多分支选择”——根据一个值选多种情况之一。",[161,162,163,164],"多分支","match","switch","elif",[166,172],{"title":167,"content":168,"codePy":169,"codeCpp":170,"tip":171},"1. 一个变量的多种取值","当要按一个变量的不同取值做不同事时，用多分支比一堆 if 更清晰。Python 用 if\u002Felif\u002Felse 或 match，C++ 用 switch。","level = 2\nif level == 1:\n    print(\"新手\")\nelif level == 2:\n    print(\"进阶\")\nelse:\n    print(\"高手\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int level = 2;\n    if (level == 1) cout \u003C\u003C \"新手\" \u003C\u003C endl;\n    else if (level == 2) cout \u003C\u003C \"进阶\" \u003C\u003C endl;\n    else cout \u003C\u003C \"高手\" \u003C\u003C endl;\n    return 0;\n}","C++ 的 switch 只能判断“相等且为整数\u002F字符”；多条件组合时更常用 if\u002Felse if。",{"title":173,"content":174,"codePy":175,"codeCpp":176},"2. 用 match \u002F switch 更直观","如果只是在“一个变量的多个固定值”之间选，结构会更整齐。","level = 2\nmatch level:\n    case 1:\n        print(\"铜\")\n    case 2:\n        print(\"银\")\n    case _:\n        print(\"其他\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int level = 2;\n    switch (level) {\n        case 1: cout \u003C\u003C \"铜\" \u003C\u003C endl; break;\n        case 2: cout \u003C\u003C \"银\" \u003C\u003C endl; break;\n        default: cout \u003C\u003C \"其他\" \u003C\u003C endl;\n    }\n    return 0;\n}",[178],{"title":179,"prompt":180,"analysis":181,"codePy":182,"codeCpp":183,"run":184},"例 1：关卡对应场景","根据关卡号 1\u002F2\u002F3 输出“森林\u002F沙漠\u002F雪原”，其他输出“未知”。","一个变量 level 的多种取值，用多分支清晰表达。","level = int(input())\nif level == 1:\n    print(\"森林\")\nelif level == 2:\n    print(\"沙漠\")\nelif level == 3:\n    print(\"雪原\")\nelse:\n    print(\"未知\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int level;\n    cin >> level;\n    if (level == 1) cout \u003C\u003C \"森林\" \u003C\u003C endl;\n    else if (level == 2) cout \u003C\u003C \"沙漠\" \u003C\u003C endl;\n    else if (level == 3) cout \u003C\u003C \"雪原\" \u003C\u003C endl;\n    else cout \u003C\u003C \"未知\" \u003C\u003C endl;\n    return 0;\n}","输入 2 → 沙漠",{"choice":186,"coding":208},[187,195,201],{"id":38,"stem":188,"options":189,"answer":9,"explanation":194},"当要“按一个变量的多个固定值分别处理”时，用什么更清晰？",[190,191,192,193],"只用 print","多分支（if\u002Felif 或 switch）","循环","变量","一个变量的多取值用多分支最清晰。",{"id":47,"stem":196,"options":197,"answer":9,"explanation":200},"C++ 中多分支除了 if\u002Felse if，还可以用？",[162,163,198,199],"loop","case","C++ 用 switch 做多分支（配合 case\u002Fdefault）。",{"id":56,"stem":202,"options":203,"answer":9,"explanation":207},"Python 中“否则如果”的关键字是？",[204,164,205,206],"else if","elseif","eif","Python 用 elif 表示“否则如果”。",[209],{"id":67,"title":210,"prompt":211,"inputFormat":212,"outputFormat":213,"samples":214,"starterPy":221,"starterCpp":222,"hint":223,"referencePy":224,"referenceCpp":225},"成绩等级","输入分数：>=90 输出 A，>=80 输出 B，>=60 输出 C，否则输出 D。","一行一个整数 score。","一行一个字母 A\u002FB\u002FC\u002FD。",[215,218],{"input":216,"output":217},"85","B",{"input":219,"output":220},"55","D","score = int(input())\n# 按区间输出等级","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score;\n    cin >> score;\n    \u002F\u002F 按区间输出等级\n    return 0;\n}","从大到小依次用 if\u002Felif 判断区间。","score = int(input())\nif score >= 90: print(\"A\")\nelif score >= 80: print(\"B\")\nelif score >= 60: print(\"C\")\nelse: print(\"D\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score;\n    cin >> score;\n    if (score >= 90) cout \u003C\u003C \"A\" \u003C\u003C endl;\n    else if (score >= 80) cout \u003C\u003C \"B\" \u003C\u003C endl;\n    else if (score >= 60) cout \u003C\u003C \"C\" \u003C\u003C endl;\n    else cout \u003C\u003C \"D\" \u003C\u003C endl;\n    return 0;\n}","多分支按“一个变量的多种取值”选不同处理：Python 用 if\u002Felif\u002Felse 或 match，C++ 用 if\u002Felse if 或 switch。从大到小判断区间最稳妥。","01-A1-12","for 循环",{"id":227,"title":228,"level":7,"module":8,"seq":230,"goal":231,"keywords":232,"explanation":236,"examples":248,"exercises":256,"summary":297,"next":298,"nextTitle":299},12,"能使用 for 循环把一件事重复做 N 次。",[233,192,234,235],"for","重复","range",[237,243],{"title":238,"content":239,"codePy":240,"codeCpp":241,"tip":242},"1. 让代码自动重复","for 让一段代码按序列（如 1 到 5）自动重复执行，不用把同一行抄很多遍。","for i in range(5):\n    print(\"第\", i + 1, \"次\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int i = 0; i \u003C 5; i++) {\n        cout \u003C\u003C \"第 \" \u003C\u003C i + 1 \u003C\u003C \" 次\" \u003C\u003C endl;\n    }\n    return 0;\n}","Python 的 range(5) 是 0~4 共 5 次；C++ 的 i 从 0 到 \u003C5 也是 5 次。变量 i 叫“循环变量”。",{"title":244,"content":245,"codePy":246,"codeCpp":247},"2. 循环变量能参与计算","每一次循环，循环变量 i 会取不同的值，可以用它做累加、编号等。","total = 0\nfor i in range(1, 6):   # 1,2,3,4,5\n    total = total + i\nprint(\"1到5的和：\", total)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int total = 0;\n    for (int i = 1; i \u003C= 5; i++) {\n        total = total + i;\n    }\n    cout \u003C\u003C \"1到5的和：\" \u003C\u003C total \u003C\u003C endl;\n    return 0;\n}",[249],{"title":250,"prompt":251,"analysis":252,"codePy":253,"codeCpp":254,"run":255},"例 1：打印 1 到 10","用 for 循环输出 1 到 10。","range(1, 11) 产生 1~10，逐次打印。","for i in range(1, 11):\n    print(i)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int i = 1; i \u003C= 10; i++) {\n        cout \u003C\u003C i \u003C\u003C endl;\n    }\n    return 0;\n}","1\n2\n...\n10",{"choice":257,"coding":282},[258,266,274],{"id":38,"stem":259,"options":260,"answer":9,"explanation":265},"range(5) 在 Python 中表示循环几次？",[261,262,263,264],"4 次","5 次","6 次","0 次","range(5) 产生 0~4，共 5 个数，循环 5 次。",{"id":47,"stem":267,"options":268,"answer":9,"explanation":273},"for 循环适合哪种任务？",[269,270,271,272],"只执行一次","把一件事重复做 N 次","做条件判断","定义变量","for 用于已知次数的重复。",{"id":56,"stem":275,"options":276,"answer":9,"explanation":281},"C++ for 循环里负责“每次自增”的是哪部分？",[277,278,279,280],"for 的第一段","第三段 i++","条件段","循环体","for(初始化; 条件; 自增) 中第三段 i++ 控制每次变化。",[283],{"id":67,"title":284,"prompt":285,"inputFormat":286,"outputFormat":287,"samples":288,"starterPy":292,"starterCpp":293,"hint":294,"referencePy":295,"referenceCpp":296},"1 到 N 的和","输入整数 n，用 for 循环求 1 到 n 的和并输出。","一行一个整数 n。","一行一个整数（和）。",[289],{"input":290,"output":291},"5","15","n = int(input())\ns = 0\n# 用 for 累加 1..n\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, s = 0;\n    cin >> n;\n    \u002F\u002F 用 for 累加 1..n\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","让循环变量从 1 到 n，每次 s = s + i。","n = int(input())\ns = 0\nfor i in range(1, n + 1):\n    s = s + i\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, s = 0;\n    cin >> n;\n    for (int i = 1; i \u003C= n; i++) s = s + i;\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","for 循环把一件事重复做 N 次：Python 用 range，C++ 用 for(初始化;条件;自增)。循环变量 i 每轮取不同值，可参与计算。","01-A1-13","while 循环",{"id":298,"title":299,"level":7,"module":8,"seq":301,"goal":302,"keywords":303,"explanation":306,"examples":318,"exercises":326,"summary":364,"next":365,"nextTitle":366},13,"能使用 while 在条件满足时不断重复。",[304,192,305],"while","条件循环",[307,313],{"title":308,"content":309,"codePy":310,"codeCpp":311,"tip":312},"1. 条件为真就一直循环","while 只要条件为真就一直重复执行循环体，常用于“不知道要循环几次，只知道何时停”的情况。","hp = 100\nwhile hp > 0:\n    hp = hp - 10\n    print(\"剩余血量\", hp)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int hp = 100;\n    while (hp > 0) {\n        hp = hp - 10;\n        cout \u003C\u003C \"剩余血量 \" \u003C\u003C hp \u003C\u003C endl;\n    }\n    return 0;\n}","务必让循环体里“能改变条件”，否则条件永远为真会变成死循环！",{"title":314,"content":315,"codePy":316,"codeCpp":317},"2. for 与 while 的区别","for 适合“已知次数”，while 适合“看条件”。两者能互相改写，但各有所长。","# 用 while 从 5 数到 1\ni = 5\nwhile i >= 1:\n    print(i)\n    i = i - 1","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int i = 5;\n    while (i >= 1) {\n        cout \u003C\u003C i \u003C\u003C endl;\n        i = i - 1;\n    }\n    return 0;\n}",[319],{"title":320,"prompt":321,"analysis":322,"codePy":323,"codeCpp":324,"run":325},"例 1：从 5 数到 1","用 while 循环从 5 倒数到 1。","初始 i=5，条件 i>=1，每轮 i 减 1。","i = 5\nwhile i >= 1:\n    print(i)\n    i = i - 1","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int i = 5;\n    while (i >= 1) {\n        cout \u003C\u003C i \u003C\u003C endl;\n        i--;\n    }\n    return 0;\n}","5\n4\n3\n2\n1",{"choice":327,"coding":349},[328,336,344],{"id":38,"stem":329,"options":330,"answer":9,"explanation":335},"while 循环在什么情况下停止？",[331,332,333,334],"执行一次后","条件变为假时","永远不停止","遇到 print 时","while 在条件为假时退出循环。",{"id":47,"stem":337,"options":338,"answer":9,"explanation":343},"如果 while 的条件永远为真，会发生？",[339,340,341,342],"正常结束","死循环","自动跳过","报错退出","条件永远真会陷入死循环，要确保在循环体内改变条件。",{"id":56,"stem":345,"options":346,"answer":9,"explanation":348},"“不知道循环几次、只知道何时停”更适合用？",[233,304,92,347],"print","while 适合按条件控制、次数不确定的循环。",[350],{"id":67,"title":351,"prompt":352,"inputFormat":353,"outputFormat":354,"samples":355,"starterPy":359,"starterCpp":360,"hint":361,"referencePy":362,"referenceCpp":363},"累加到超过 100","从 1 开始不断累加，直到和大于 100 为止，输出最后的那个和。","无输入。","一行一个整数（>100 的最小累加和）。",[356],{"input":357,"output":358},"","105","s = 0\ni = 1\n# 用 while 累加直到 s > 100\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int s = 0, i = 1;\n    \u002F\u002F 用 while 累加直到 s > 100\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","while s \u003C= 100 时继续 s = s + i; i = i + 1。","s = 0\ni = 1\nwhile s \u003C= 100:\n    s = s + i\n    i = i + 1\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int s = 0, i = 1;\n    while (s \u003C= 100) {\n        s = s + i;\n        i++;\n    }\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","while 在条件为真时不断重复，适合“次数不确定、看条件停”的场景。务必在循环体内改变条件，否则会死循环。","01-A1-14","do-while 循环",{"id":365,"title":366,"level":7,"module":8,"seq":368,"goal":369,"keywords":370,"explanation":373,"examples":385,"exercises":393,"summary":431,"next":432,"nextTitle":433},14,"能理解“先做一次，再判断要不要继续”的循环。",[371,192,372],"do-while","至少一次",[374,380],{"title":375,"content":376,"codePy":377,"codeCpp":378,"tip":379},"1. 至少执行一次","do-while 先执行一次循环体，再检查条件——所以“至少跑一次”。C++ 直接有 do-while；Python 常用 while True + break 实现。","while True:\n    answer = input(\"再玩一次？(y\u002Fn) \")\n    if answer == \"n\":\n        break","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    string answer;\n    do {\n        cout \u003C\u003C \"再玩一次？(y\u002Fn) \" \u003C\u003C endl;\n        cin >> answer;\n    } while (answer != \"n\");\n    return 0;\n}","do-while 和普通 while 的区别：前者先做事后判断（至少一次），后者先判断后做事（可能一次都不做）。",{"title":381,"content":382,"codePy":383,"codeCpp":384},"2. 和 while 的差别","如果条件一开始就不满足，while 会一次都不执行，do-while 仍会执行一次。","# 对比：条件一开始为假\nwhile False:\n    print(\"不会执行\")\n\n# do-while 风格：先做一次\nwhile True:\n    print(\"至少一次\")\n    break","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int x = 0;\n    while (x > 0) { cout \u003C\u003C \"不执行\"; }   \u002F\u002F 一次都不做\n    do { cout \u003C\u003C \"至少一次\"; } while (x > 0); \u002F\u002F 做一次\n    return 0;\n}",[386],{"title":387,"prompt":388,"analysis":389,"codePy":390,"codeCpp":391,"run":392},"例 1：菜单至少显示一次","实现一个“再来一局”菜单：先显示一次，用户输入 n 才退出。","至少显示一次，适合 do-while 思路。","while True:\n    print(\"游戏结束\")\n    ans = input(\"再来一局？(y\u002Fn) \")\n    if ans == \"n\":\n        break","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    string ans;\n    do {\n        cout \u003C\u003C \"游戏结束\" \u003C\u003C endl;\n        cout \u003C\u003C \"再来一局？(y\u002Fn) \" \u003C\u003C endl;\n        cin >> ans;\n    } while (ans != \"n\");\n    return 0;\n}","游戏结束\n再来一局？(y\u002Fn) y\n游戏结束\n再来一局？(y\u002Fn) n",{"choice":394,"coding":417},[395,402,410],{"id":38,"stem":396,"options":397,"answer":9,"explanation":401},"do-while 循环最少会执行几次？",[264,398,399,400],"1 次","2 次","无限次","do-while 先执行后判断，至少执行 1 次。",{"id":47,"stem":403,"options":404,"answer":9,"explanation":409},"Python 没有原生 do-while，常用什么模拟？",[405,406,407,408],"for + continue","while True + break","if + else","def","用 while True 执行一次后，满足条件 break。",{"id":56,"stem":411,"options":412,"answer":9,"explanation":416},"“可能一次都不执行”的是哪种循环？",[371,413,414,415],"普通 while","两者都一次都不做","两者都至少一次","普通 while 先判断，条件一开始假就一次都不做。",[418],{"id":67,"title":419,"prompt":420,"inputFormat":421,"outputFormat":422,"samples":423,"starterPy":427,"starterCpp":428,"hint":429,"referencePy":430,"referenceCpp":428},"密码重试","让用户输入密码，正确（假设正确密码是 1234）就输出 \"ok\" 并结束，错误就继续让输入（至少输入一次）。","多行，每行一个整数密码。","正确时输出一行 \"ok\"。",[424],{"input":425,"output":426},"1111\n1234","ok","while True:\n    pwd = int(input())\n    # 判断并决定是否继续","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int pwd;\n    do {\n        cin >> pwd;\n    } while (pwd != 1234);\n    cout \u003C\u003C \"ok\" \u003C\u003C endl;\n    return 0;\n}","用 while True，相等就 break。","while True:\n    pwd = int(input())\n    if pwd == 1234:\n        print(\"ok\")\n        break","do-while “先做后判断”，至少执行一次（游戏结束菜单、密码重试很合适）。Python 用 while True + break 模拟；普通 while 可能一次都不做。","01-A1-15","break 语句",{"id":432,"title":433,"level":7,"module":8,"seq":435,"goal":436,"keywords":437,"explanation":441,"examples":453,"exercises":461,"summary":498,"next":499,"nextTitle":500},15,"能使用 break 立即跳出循环。",[438,439,440],"break","跳出循环","终止",[442,448],{"title":443,"content":444,"codePy":445,"codeCpp":446,"tip":447},"1. break 是“紧急出口”","break 一旦执行，就立刻结束整个循环，无论条件是否还满足。常用来“找到目标就停”。","for i in range(10):\n    if i == 3:\n        break\n    print(i)   # 只打印 0 1 2","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int i = 0; i \u003C 10; i++) {\n        if (i == 3) break;\n        cout \u003C\u003C i \u003C\u003C endl;   \u002F\u002F 只打印 0 1 2\n    }\n    return 0;\n}","break 只跳出“最内层”的那一层循环，不会退出多层。",{"title":449,"content":450,"codePy":451,"codeCpp":452},"2. 找到就停","在“找第一个符合条件”的场景里，break 能避免无谓的继续搜索。","for n in range(2, 100):\n    if n % 7 == 0:\n        print(\"第一个 7 的倍数是\", n)\n        break","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 2; n \u003C 100; n++) {\n        if (n % 7 == 0) {\n            cout \u003C\u003C \"第一个 7 的倍数是 \" \u003C\u003C n \u003C\u003C endl;\n            break;\n        }\n    }\n    return 0;\n}",[454],{"title":455,"prompt":456,"analysis":457,"codePy":458,"codeCpp":459,"run":460},"例 1：输入 q 退出","循环读入字符串，输入 \"q\" 就结束循环。","读到 q 时用 break 跳出。","while True:\n    s = input()\n    if s == \"q\":\n        break\n    print(\"你输入了\", s)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string s;\n    while (true) {\n        cin >> s;\n        if (s == \"q\") break;\n        cout \u003C\u003C \"你输入了 \" \u003C\u003C s \u003C\u003C endl;\n    }\n    return 0;\n}","hello\n你输入了 hello\nq",{"choice":462,"coding":486},[463,471,479],{"id":38,"stem":464,"options":465,"answer":9,"explanation":470},"break 的作用是？",[466,467,468,469],"跳过本次循环","立刻结束整个循环","暂停程序","什么都不做","break 立即终止（跳出）所在循环。",{"id":47,"stem":472,"options":473,"answer":9,"explanation":478},"break 会跳出几层循环？",[474,475,476,477],"所有循环","最内层那一层","最外层","下一层","break 只跳出当前最内层的循环。",{"id":56,"stem":480,"options":481,"answer":9,"explanation":485},"想“找到目标就停止搜索”，应该用？",[482,438,483,484],"continue","pass","return","找到即用 break 结束循环。",[487],{"id":67,"title":488,"prompt":489,"inputFormat":353,"outputFormat":490,"samples":491,"starterPy":493,"starterCpp":494,"hint":495,"referencePy":496,"referenceCpp":497},"第一个平方大于 50 的数","从 1 开始找，输出第一个“平方大于 50”的整数。","一行一个整数。",[492],{"input":357,"output":148},"i = 1\nwhile True:\n    # 找到平方>50 就输出并 break\n    i = i + 1","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int i = 1;\n    while (true) {\n        \u002F\u002F 找到平方>50 就输出并 break\n        i++;\n    }\n    return 0;\n}","if i*i > 50 就 print 并 break。","i = 1\nwhile True:\n    if i * i > 50:\n        print(i)\n        break\n    i = i + 1","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int i = 1;\n    while (true) {\n        if (i * i > 50) {\n            cout \u003C\u003C i \u003C\u003C endl;\n            break;\n        }\n        i++;\n    }\n    return 0;\n}","break 是循环的“紧急出口”，一旦执行立刻结束整个（最内层）循环。适合“找到目标\u002F满足条件就停”的场景。","01-A1-16","continue 语句",{"id":499,"title":500,"level":7,"module":8,"seq":502,"goal":503,"keywords":504,"explanation":506,"examples":518,"exercises":526,"summary":559,"next":560,"nextTitle":561},16,"能使用 continue 跳过本次、进入下一次循环。",[482,505,192],"跳过",[507,513],{"title":508,"content":509,"codePy":510,"codeCpp":511,"tip":512},"1. continue 只跳过这一轮","continue 只跳过当前这一轮循环体剩下的部分，然后进入下一轮——循环本身还在继续。","for i in range(5):\n    if i == 2:\n        continue\n    print(i)   # 打印 0 1 3 4（跳过 2）","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int i = 0; i \u003C 5; i++) {\n        if (i == 2) continue;\n        cout \u003C\u003C i \u003C\u003C endl;   \u002F\u002F 打印 0 1 3 4\n    }\n    return 0;\n}","break 是“停”（结束循环），continue 是“跳”（进下一轮），别搞混。",{"title":514,"content":515,"codePy":516,"codeCpp":517},"2. 跳过不要的元素","遍历列表时，常用来跳过“不符合条件”的元素，只处理想要的。","for n in range(1, 6):\n    if n % 2 == 0:\n        continue   # 跳过偶数\n    print(n, \"是奇数\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 1; n \u003C= 5; n++) {\n        if (n % 2 == 0) continue;   \u002F\u002F 跳过偶数\n        cout \u003C\u003C n \u003C\u003C \" 是奇数\" \u003C\u003C endl;\n    }\n    return 0;\n}",[519],{"title":520,"prompt":521,"analysis":522,"codePy":523,"codeCpp":524,"run":525},"例 1：只打印奇数","用 continue 跳过偶数，只打印 1 到 10 里的奇数。","偶数时 continue 跳过，不打印。","for n in range(1, 11):\n    if n % 2 == 0:\n        continue\n    print(n)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 1; n \u003C= 10; n++) {\n        if (n % 2 == 0) continue;\n        cout \u003C\u003C n \u003C\u003C endl;\n    }\n    return 0;\n}","1\n3\n5\n7\n9",{"choice":527,"coding":546},[528,534,542],{"id":38,"stem":529,"options":530,"answer":9,"explanation":533},"continue 的作用是？",[531,532,468,469],"结束整个循环","跳过本次、进入下一轮","continue 只跳过本轮，循环继续。",{"id":47,"stem":535,"options":536,"answer":9,"explanation":541},"break 和 continue 的区别是？",[537,538,539,540],"完全一样","break 结束循环，continue 进下一轮","都结束循环","都进下一轮","break 停、continue 跳。",{"id":56,"stem":543,"options":544,"answer":9,"explanation":545},"想“跳过偶数只处理奇数”，应该在偶数时？",[438,482,484,483],"偶数时 continue 跳过本轮。",[547],{"id":67,"title":548,"prompt":549,"inputFormat":353,"outputFormat":550,"samples":551,"starterPy":554,"starterCpp":555,"hint":556,"referencePy":557,"referenceCpp":558},"跳过 3 的倍数","输出 1 到 20 中“不是 3 的倍数”的数，每行一个。","多行，每行一个符合条件的整数。",[552],{"input":357,"output":553},"1\n2\n4\n5\n...（略）","for n in range(1, 21):\n    # 跳过 3 的倍数后打印","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 1; n \u003C= 20; n++) {\n        \u002F\u002F 跳过 3 的倍数后打印\n    }\n    return 0;\n}","if n % 3 == 0: continue。","for n in range(1, 21):\n    if n % 3 == 0:\n        continue\n    print(n)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 1; n \u003C= 20; n++) {\n        if (n % 3 == 0) continue;\n        cout \u003C\u003C n \u003C\u003C endl;\n    }\n    return 0;\n}","continue 只跳过“当前这一轮”，循环继续往下走。break 是停、continue 是跳，两者用途不同。","01-A1-17","调试 Debug",{"id":560,"title":561,"level":7,"module":8,"seq":563,"goal":564,"keywords":565,"explanation":570,"examples":582,"exercises":590,"summary":630,"next":631,"nextTitle":632},17,"能看懂报错信息，定位并修正常见错误。",[566,567,568,569],"调试","报错","Debug","排查",[571,577],{"title":572,"content":573,"codePy":574,"codeCpp":575,"tip":576},"1. 程序“生病”了，你来当医生","调试就是看错误提示、缩小范围、逐行检查。报错信息通常会告诉你“哪一行、什么类型的问题”，先读它再动手。","print(\"Hello)   # 报错：括号\u002F引号不匹配\n# 修正：print(\"Hello\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C \"Hello\"   \u002F\u002F 报错：少了分号\n    return 0;\n}\n\u002F\u002F 修正：cout \u003C\u003C \"Hello\" \u003C\u003C endl;","常见错误：拼写错、少了引号\u002F分号、缩进不对、类型不匹配。报错先别慌，从最后一行往回看。",{"title":578,"content":579,"codePy":580,"codeCpp":581},"2. 用打印中间值排查","程序没报错但结果不对时，在关键位置打印中间变量，看“哪一步开始不对”。","total = 0\nfor i in range(1, 4):\n    total = total + i\n    print(\"i=\", i, \"total=\", total)   # 观察每一步","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int total = 0;\n    for (int i = 1; i \u003C= 3; i++) {\n        total = total + i;\n        cout \u003C\u003C \"i=\" \u003C\u003C i \u003C\u003C \" total=\" \u003C\u003C total \u003C\u003C endl;\n    }\n    return 0;\n}",[583],{"title":584,"prompt":585,"analysis":586,"codePy":587,"codeCpp":588,"run":589},"例 1：读出报错","下面代码报错，请说出大概错在哪：print(\"Hello)。","字符串只写了左引号没写右引号，会报语法\u002F括号不匹配错误。","# 错误写法\nprint(\"Hello)\n# 正确写法\nprint(\"Hello\")","\u002F\u002F 错误：少了右引号对应的配对\ncout \u003C\u003C \"Hello;\n\u002F\u002F 正确\ncout \u003C\u003C \"Hello\" \u003C\u003C endl;","修正后输出：Hello",{"choice":591,"coding":617},[592,600,609],{"id":38,"stem":593,"options":594,"answer":9,"explanation":599},"程序报错时，第一步应该？",[595,596,597,598],"立刻重写全部","先读错误提示（哪行、什么错）","关掉电脑","随便改改","报错信息会指出行号和错误类型，先看它最高效。",{"id":47,"stem":601,"options":602,"answer":607,"explanation":608},"程序没有报错但结果不对，常用什么排查？",[603,604,605,606],"print 输出中间值","多写循环","删掉代码","换电脑",0,"在关键处打印中间变量，定位从哪一步开始不对。",{"id":56,"stem":610,"options":611,"answer":9,"explanation":616},"C++ 每行语句结尾通常需要？",[612,613,614,615],"逗号","分号 ;","句号","什么都不用","C++ 语句以分号结束，少了常报编译错误。",[618],{"id":67,"title":619,"prompt":620,"inputFormat":353,"outputFormat":621,"samples":622,"starterPy":625,"starterCpp":626,"hint":627,"referencePy":628,"referenceCpp":629},"找出“消失的加号’","下面程序想求 1+2+3，但结果不对。请修正后输出正确结果（应为 6）。","一行一个整数 6。",[623],{"input":357,"output":624},"6","a = 1\nb = 2\nc = 3\n# 下面这行结果不对，请修正\ns = a b c\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a = 1, b = 2, c = 3;\n    \u002F\u002F 下面这行结果不对，请修正\n    int s = a b c;\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","a b c 之间少了运算符，应该改成 a + b + c。","a = 1\nb = 2\nc = 3\ns = a + b + c\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a = 1, b = 2, c = 3;\n    int s = a + b + c;\n    cout \u003C\u003C s \u003C\u003C endl;\n    return 0;\n}","调试 = 当程序医生：先读报错（行号+类型），再用 print 打中间值定位“哪步开始错”。常见坑：拼写、缺引号\u002F分号、缩进、类型不匹配。","01-A2-1","嵌套循环",{"id":14,"title":85,"level":7,"module":8,"seq":63,"goal":634,"keywords":635,"explanation":638,"examples":650,"exercises":658,"summary":699,"next":700,"nextTitle":701},"能声明变量、赋值并在程序中修改它的值。",[193,636,637],"赋值","=",[639,645],{"title":640,"content":641,"codePy":642,"codeCpp":643,"tip":644},"1. 变量是带名字的“盒子”","变量就像一个贴了名字的盒子，可以存一个数或一段文字，之后随时取出或修改。`=` 是“赋值”，表示把右边的值放进左边的盒子。","score = 0\nscore = score + 10   # 现在 score 是 10\nprint(score)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score = 0;\n    score = score + 10;   \u002F\u002F 现在 score 是 10\n    cout \u003C\u003C score \u003C\u003C endl;\n    return 0;\n}","C++ 里变量要先写“类型”（如 int），Python 不用。",{"title":646,"content":647,"codePy":648,"codeCpp":649},"2. 变量的值可以变","同一个盒子可以反复存入新值，后面取到的是最新的值。这正是“变量”和“固定不变的数”的区别。","hp = 100\nhp = hp - 20   # 被攻击，扣 20\nprint(\"剩余血量\", hp)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int hp = 100;\n    hp = hp - 20;   \u002F\u002F 被攻击，扣 20\n    cout \u003C\u003C \"剩余血量 \" \u003C\u003C hp \u003C\u003C endl;\n    return 0;\n}",[651],{"title":652,"prompt":653,"analysis":654,"codePy":655,"codeCpp":656,"run":657},"例 1：记录连击数","用变量 combo 表示连击数，每次命中就 +1，最后输出。","先初始化为 0，每命中一次执行 combo = combo + 1。","combo = 0\ncombo = combo + 1\ncombo = combo + 1\nprint(\"连击\", combo)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int combo = 0;\n    combo = combo + 1;\n    combo = combo + 1;\n    cout \u003C\u003C \"连击 \" \u003C\u003C combo \u003C\u003C endl;\n    return 0;\n}","连击 2",{"choice":659,"coding":684},[660,668,676],{"id":38,"stem":661,"options":662,"answer":9,"explanation":667},"语句 score = score + 1 的含义是？",[663,664,665,666],"判断 score 是否等于 1","把 score 加 1 后存回 score","让 score 变成 1","删除 score","先算右边 score+1，再把结果赋给左边 score。",{"id":47,"stem":669,"options":670,"answer":9,"explanation":675},"下面哪个最适合用“变量”表示？",[671,672,673,674],"圆周率 3.14","玩家的当前血量","一天有 24 小时","一周有 7 天","血量会变化，应该用可修改的变量；其余是固定值。",{"id":56,"stem":677,"options":678,"answer":9,"explanation":683},"在 C++ 中声明一个整数变量 age，正确的是？",[679,680,681,682],"age = 12","int age = 12","integer age = 12","var age = 12","C++ 声明变量要写类型 int。",[685],{"id":67,"title":686,"prompt":687,"inputFormat":688,"outputFormat":689,"samples":690,"starterPy":694,"starterCpp":695,"hint":696,"referencePy":697,"referenceCpp":698},"血量变化","输入玩家初始血量 hp 和受到的伤害 dmg，输出剩余血量。","一行两个整数 hp dmg。","一行一个整数，剩余血量。",[691],{"input":692,"output":693},"100 20","80","hp, dmg = map(int, input().split())\n# 计算并输出剩余血量","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int hp, dmg;\n    cin >> hp >> dmg;\n    \u002F\u002F 计算并输出剩余血量\n    return 0;\n}","剩余血量 = hp - dmg。","hp, dmg = map(int, input().split())\nprint(hp - dmg)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int hp, dmg;\n    cin >> hp >> dmg;\n    cout \u003C\u003C hp - dmg \u003C\u003C endl;\n    return 0;\n}","变量是带名字、可修改的“盒子”，用 `=` 赋值。C++ 要先写类型（int），Python 不用。值能随时更新，这是程序“会算账”的基础。","01-A1-3","常量",{"id":700,"title":701,"level":7,"module":8,"seq":703,"goal":704,"keywords":705,"explanation":708,"examples":720,"exercises":728,"summary":766,"next":767,"nextTitle":768},3,"能区分“会变的值”和“永远不变的值”，并用常量表达固定数字。",[701,706,707],"const","固定值",[709,715],{"title":710,"content":711,"codePy":712,"codeCpp":713,"tip":714},"1. 常量是起好名字、不能改的值","常量是运行期间固定不变的值，比如圆周率、每关时间。给它起个名字，程序读起来更清楚，也防止被误改。","PI = 3.14\nLEVEL_TIME = 60\nprint(\"本关时间：\", LEVEL_TIME)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    const double PI = 3.14;\n    const int LEVEL_TIME = 60;\n    cout \u003C\u003C \"本关时间：\" \u003C\u003C LEVEL_TIME \u003C\u003C endl;\n    return 0;\n}","Python 约定常量用全大写字母命名；C++ 用 const 关键字。",{"title":716,"content":717,"codePy":718,"codeCpp":719},"2. 常量被修改会报错","常量一旦确定就不能再赋值，强行修改程序会报错——这正是它“保护固定规则”的作用。","LEVEL_TIME = 60\nLEVEL_TIME = 90   # 可能会被提醒：常量不该改","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    const int LEVEL_TIME = 60;\n    \u002F\u002F LEVEL_TIME = 90;   \u002F\u002F 错误：常量不能修改\n    return 0;\n}",[721],{"title":722,"prompt":723,"analysis":724,"codePy":725,"codeCpp":726,"run":727},"例 1：用常量表示固定规则","把“重力加速度 9.8”定义为常量，并输出它。","数值固定且不应被改，用常量最合适。","G = 9.8\nprint(\"重力加速度：\", G)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    const double G = 9.8;\n    cout \u003C\u003C \"重力加速度：\" \u003C\u003C G \u003C\u003C endl;\n    return 0;\n}","重力加速度： 9.8",{"choice":729,"coding":751},[730,737,744],{"id":38,"stem":731,"options":732,"answer":63,"explanation":736},"常量最适合用来表示下列哪类值？",[733,734,671,735],"玩家当前分数","一天的工作进度","鼠标位置","圆周率是固定不变的值，适合用常量。",{"id":47,"stem":738,"options":739,"answer":9,"explanation":743},"在 C++ 中声明常量，需要用到哪个关键字？",[740,706,741,742],"let","fixed","static","C++ 用 const 声明常量。",{"id":56,"stem":745,"options":746,"answer":9,"explanation":750},"试图修改一个常量，会发生什么？",[747,52,748,749],"自动变成变量","值悄悄改变","没有任何影响","常量不可修改，强行修改会编译\u002F运行报错。",[752],{"id":67,"title":753,"prompt":754,"inputFormat":755,"outputFormat":756,"samples":757,"starterPy":761,"starterCpp":762,"hint":763,"referencePy":764,"referenceCpp":765},"周长与面积","用常量 PI=3.14，输入半径 r，输出圆的周长（2*PI*r）和面积（PI*r*r）。","一行一个小数 r。","一行两个数：周长和面积，空格分隔（保留默认精度即可）。",[758],{"input":759,"output":760},"2","12.56 12.56","PI = 3.14\nr = float(input())\n# 用 PI 计算周长和面积并输出","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    const double PI = 3.14;\n    double r;\n    cin >> r;\n    \u002F\u002F 用 PI 计算并输出\n    return 0;\n}","周长 = 2*PI*r，面积 = PI*r*r。","PI = 3.14\nr = float(input())\nprint(2 * PI * r, PI * r * r)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    const double PI = 3.14;\n    double r;\n    cin >> r;\n    cout \u003C\u003C 2 * PI * r \u003C\u003C \" \" \u003C\u003C PI * r * r \u003C\u003C endl;\n    return 0;\n}","常量是固定不变、起了名字的值（圆周率、关卡时间等）。Python 用全大写，C++ 用 const。它防止重要数字被误改。","01-A1-4","数据类型",{"id":767,"title":768,"level":7,"module":8,"seq":770,"goal":771,"keywords":772,"explanation":777,"examples":789,"exercises":797,"summary":834,"next":835,"nextTitle":836},4,"能区分整数、小数、文字三种最基本的数据类型。",[773,774,775,776],"整数","小数","字符串","类型",[778,784],{"title":779,"content":780,"codePy":781,"codeCpp":782,"tip":783},"1. 三种最基本类型","整数(int)是 1、2、3 这样没有小数点的数；小数(float\u002Fdouble)带小数点；字符串(str\u002Fstring)是用引号包起来的文字。不同类型能做的事不一样。","age = 12          # 整数\nheight = 1.55     # 小数\nname = \"Alex\"     # 字符串","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    int age = 12;          \u002F\u002F 整数\n    double height = 1.55;  \u002F\u002F 小数\n    string name = \"Alex\";  \u002F\u002F 字符串\n    return 0;\n}","\"12\"（带引号）是字符串，12（不带）是整数，两者不一样！",{"title":785,"content":786,"codePy":787,"codeCpp":788},"2. 不同类型用在不同地方","角色名字用字符串，坐标用小数，生命值用整数。把“文字”当“数字”去加减会出错。","print(\"分数\" + 100)     # 错误：不能把文字和数字直接相加\nprint(\"分数\" + str(100))  # 正确：先转成文字","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    \u002F\u002F C++ 不同类型用不同变量，混合运算要转换\n    int score = 100;\n    cout \u003C\u003C \"分数 \" \u003C\u003C score \u003C\u003C endl;\n    return 0;\n}",[790],{"title":791,"prompt":792,"analysis":793,"codePy":794,"codeCpp":795,"run":796},"例 1：给角色建三个属性","用整数存年龄、小数存身高、字符串存名字，并输出。","按类型选择合适写法：整数不加引号，小数带点，文字加引号。","age = 12\nheight = 1.55\nname = \"Alex\"\nprint(name, age, height)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    int age = 12;\n    double height = 1.55;\n    string name = \"Alex\";\n    cout \u003C\u003C name \u003C\u003C \" \" \u003C\u003C age \u003C\u003C \" \" \u003C\u003C height \u003C\u003C endl;\n    return 0;\n}","Alex 12 1.55",{"choice":798,"coding":821},[799,806,813],{"id":38,"stem":800,"options":801,"answer":9,"explanation":805},"\"12\" 和 12 的区别是？",[537,802,803,804],"前者是字符串，后者是整数","前者是整数，后者是字符串","都是小数","带引号的是字符串（文字），不带的是整数（数字）。",{"id":47,"stem":807,"options":808,"answer":9,"explanation":812},"身高 1.55 应该用哪种类型？",[809,810,775,811],"整数 int","小数 double\u002Ffloat","布尔","带小数点的数用小数类型。",{"id":56,"stem":814,"options":815,"answer":63,"explanation":820},"下列哪个是字符串？",[816,817,818,819],"100","3.14","\"你好\"","True","用引号包起来的文字才是字符串。",[822],{"id":67,"title":823,"prompt":824,"inputFormat":825,"outputFormat":826,"samples":827,"starterPy":829,"starterCpp":830,"hint":831,"referencePy":832,"referenceCpp":833},"自我介绍","输入名字（字符串）、年龄（整数）、身高（小数），按“名字 年龄 身高”输出。","一行：名字 年龄 身高（空格分隔）。","一行：名字 年龄 身高。",[828],{"input":796,"output":796},"parts = input().split()\nname = parts[0]\nage = int(parts[1])\nheight = float(parts[2])\n# 输出","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string name;\n    int age;\n    double height;\n    cin >> name >> age >> height;\n    \u002F\u002F 输出\n    return 0;\n}","按输入顺序原样输出三个值即可。","parts = input().split()\nname = parts[0]\nage = int(parts[1])\nheight = float(parts[2])\nprint(name, age, height)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string name;\n    int age;\n    double height;\n    cin >> name >> age >> height;\n    cout \u003C\u003C name \u003C\u003C \" \" \u003C\u003C age \u003C\u003C \" \" \u003C\u003C height \u003C\u003C endl;\n    return 0;\n}","三种基础类型：整数（不带小数点）、小数（带小数点）、字符串（引号包起来的文字）。\"12\"≠12，类型用错程序会报错。","01-A1-5","算术运算",{"id":835,"title":836,"level":7,"module":8,"seq":838,"goal":839,"keywords":840,"explanation":845,"examples":863,"exercises":878,"summary":932,"next":933,"nextTitle":934},5,"能正确使用 + - * \u002F 和取模 % 进行计算，并理解“先乘除后加减”的优先级。",[841,842,843,844],"运算符","算术","取模","优先级",[846,852,857],{"title":847,"content":848,"codePy":849,"codeCpp":850,"tip":851},"1. 四则运算","程序里最常用的计算和数学一样：`+` 加、`-` 减、`*` 乘、`\u002F` 除。它们可以直接对数字或变量运算，结果还是一个数字。","a = 10\nb = 3\nprint(a + b)   # 13\nprint(a - b)   # 7\nprint(a * b)   # 30\nprint(a \u002F b)   # 3.333...（带小数）","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a = 10, b = 3;\n    cout \u003C\u003C a + b \u003C\u003C endl;  \u002F\u002F 13\n    cout \u003C\u003C a - b \u003C\u003C endl;  \u002F\u002F 7\n    cout \u003C\u003C a * b \u003C\u003C endl;  \u002F\u002F 30\n    cout \u003C\u003C a \u002F b \u003C\u003C endl;  \u002F\u002F 3（整数除法只留整数部分）\n    return 0;\n}","Python 的 \u002F 结果带小数；C++ 里两个整数相除只保留整数部分（10\u002F3=3）。想要小数要先转成小数。",{"title":853,"content":854,"codePy":855,"codeCpp":856},"2. 运算优先级：先乘除后加减","一个算式里同时有加减乘除时，先算乘除、后算加减；想改变顺序就用括号 `()`，括号最优先。","print(2 + 3 * 2)     # 先算 3*2=6，再 +2 → 8\nprint((2 + 3) * 2)   # 先算括号 5，再 *2 → 10","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C 2 + 3 * 2 \u003C\u003C endl;      \u002F\u002F 8\n    cout \u003C\u003C (2 + 3) * 2 \u003C\u003C endl;    \u002F\u002F 10\n    return 0;\n}",{"title":858,"content":859,"codePy":860,"codeCpp":861,"tip":862},"3. 取模 %：求余数","`%` 叫“取模 \u002F 求余”，结果是“整除之后剩下的部分”。比如 `10 % 3`：10 除以 3 商 3 余 1，所以结果是 1。它常用来判断奇偶、分组。","print(10 % 3)   # 1（10 除以 3 的余数）\nprint(9 % 3)    # 0（整除，没有余数）\nprint(7 % 2)    # 1 → 7 是奇数","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C 10 % 3 \u003C\u003C endl;  \u002F\u002F 1\n    cout \u003C\u003C 9 % 3 \u003C\u003C endl;   \u002F\u002F 0\n    cout \u003C\u003C 7 % 2 \u003C\u003C endl;   \u002F\u002F 1\n    return 0;\n}","判断一个数 n 是奇数还是偶数：看 n % 2 是否等于 1（奇）或等于 0（偶）。",[864,871],{"title":865,"prompt":866,"analysis":867,"codePy":868,"codeCpp":869,"run":870},"例 1：计算三科总分","小明的语文 88、数学 95、英语 76，写程序算出他的总分。","把三个分数相加即可，用变量存起来更清晰。","chinese = 88\nmath = 95\nenglish = 76\ntotal = chinese + math + english\nprint(\"总分：\", total)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int chinese = 88, math = 95, english = 76;\n    int total = chinese + math + english;\n    cout \u003C\u003C \"总分：\" \u003C\u003C total \u003C\u003C endl;\n    return 0;\n}","总分： 259",{"title":872,"prompt":873,"analysis":874,"codePy":875,"codeCpp":876,"run":877},"例 2：判断奇偶","输入一个整数 n，判断它是奇数还是偶数。","用 n % 2：等于 0 是偶数，等于 1 是奇数。","n = 7\nif n % 2 == 0:\n    print(\"偶数\")\nelse:\n    print(\"奇数\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n = 7;\n    if (n % 2 == 0) cout \u003C\u003C \"偶数\" \u003C\u003C endl;\n    else cout \u003C\u003C \"奇数\" \u003C\u003C endl;\n    return 0;\n}","奇数",{"choice":879,"coding":903},[880,887,895],{"id":38,"stem":881,"options":882,"answer":9,"explanation":886},"表达式 2 + 3 * 2 的结果是？",[883,148,884,885],"10","12","7","先算乘法 3*2=6，再算加法 2+6=8。",{"id":47,"stem":888,"options":889,"answer":63,"explanation":894},"10 % 3 的结果是？",[890,891,892,893],"3","0","1","3.33","10 除以 3 商 3 余 1，取模得到余数 1。",{"id":56,"stem":896,"options":897,"answer":9,"explanation":902},"想让 (2 + 3) 先算、再乘以 2，应该？",[898,899,900,901],"从左到右依次写","加括号写成 (2 + 3) * 2","用方括号","写 2 + 3 * 2 就行","括号优先级最高，能强制改变运算顺序。",[904,917],{"id":67,"title":905,"prompt":906,"inputFormat":143,"outputFormat":907,"samples":908,"starterPy":912,"starterCpp":913,"hint":914,"referencePy":915,"referenceCpp":916},"四则运算器","输入两个整数 a、b，在一行里依次输出 a+b、a-b、a*b 的结果，用空格隔开。","一行三个整数：a+b、a-b、a*b，空格分隔。",[909],{"input":910,"output":911},"10 3","13 7 30","a, b = map(int, input().split())\n# 在这里补全计算并输出","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    \u002F\u002F 补全计算并输出\n    return 0;\n}","分别算出三个值，一次输出，用空格隔开。","a, b = map(int, input().split())\nprint(a + b, a - b, a * b)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    cout \u003C\u003C a + b \u003C\u003C \" \" \u003C\u003C a - b \u003C\u003C \" \" \u003C\u003C a * b \u003C\u003C endl;\n    return 0;\n}",{"id":918,"title":919,"prompt":920,"inputFormat":286,"outputFormat":921,"samples":922,"starterPy":927,"starterCpp":928,"hint":929,"referencePy":930,"referenceCpp":931},"c2","奇偶判断","输入一个整数 n，如果是偶数输出 \"even\"，否则输出 \"odd\"。","一行字符串 \"even\" 或 \"odd\"。",[923,925],{"input":885,"output":924},"odd",{"input":148,"output":926},"even","n = int(input())\n# 用 n % 2 判断奇偶并输出","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n;\n    cin >> n;\n    \u002F\u002F 用 n % 2 判断奇偶并输出\n    return 0;\n}","n % 2 == 0 为偶数，否则为奇数。","n = int(input())\nprint(\"even\" if n % 2 == 0 else \"odd\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n;\n    cin >> n;\n    if (n % 2 == 0) cout \u003C\u003C \"even\" \u003C\u003C endl;\n    else cout \u003C\u003C \"odd\" \u003C\u003C endl;\n    return 0;\n}","算术运算是程序处理数字的基石：`+ - * \u002F` 做四则运算，注意“先乘除后加减”、括号最优先；`%` 取余数常用来判断奇偶和分组。多写几道题就能熟练。","01-A1-6","关系运算",{"id":933,"title":934,"level":7,"module":8,"seq":936,"goal":937,"keywords":938,"explanation":941,"examples":953,"exercises":961,"summary":994,"next":995,"nextTitle":996},6,"能使用 > \u003C >= \u003C= == != 比较大小，得到“真 \u002F 假”。",[934,939,940,811],"比较","真假",[942,948],{"title":943,"content":944,"codePy":945,"codeCpp":946,"tip":947},"1. 比较得到“真”或“假”","关系运算用来比较两个值：大于 `>`、小于 `\u003C`、大于等于 `>=`、小于等于 `\u003C=`、等于 `==`、不等于 `!=`。结果只有两种：成立（真）或不成立（假）。","print(3 > 2)    # True（成立）\nprint(5 == 5)   # True\nprint(4 != 4)   # False（不成立）","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C (3 > 2) \u003C\u003C endl;    \u002F\u002F 1（真）\n    cout \u003C\u003C (5 == 5) \u003C\u003C endl;   \u002F\u002F 1（真）\n    cout \u003C\u003C (4 != 4) \u003C\u003C endl;   \u002F\u002F 0（假）\n    return 0;\n}","注意 `==` 是比较“是否相等”，`=` 是赋值，千万别写混！C++ 里真用 1、假用 0 表示。",{"title":949,"content":950,"codePy":951,"codeCpp":952},"2. 关系运算常用于做判断","把比较结果交给 if 等语句，就能让程序“看情况行事”。","score = 85\nprint(score >= 60)   # True，说明及格了","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score = 85;\n    cout \u003C\u003C (score >= 60) \u003C\u003C endl;  \u002F\u002F 1\n    return 0;\n}",[954],{"title":955,"prompt":956,"analysis":957,"codePy":958,"codeCpp":959,"run":960},"例 1：判断是否及格","输入分数 score，判断是否达到 60 分及以上。","用 score >= 60，成立就是及格。","score = int(input())\nprint(score >= 60)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score;\n    cin >> score;\n    cout \u003C\u003C (score >= 60) \u003C\u003C endl;\n    return 0;\n}","输入 85 → 1（表示 True\u002F及格）",{"choice":962,"coding":979},[963,968,975],{"id":38,"stem":964,"options":965,"answer":607,"explanation":967},"表达式 3 > 2 的结果是？",[819,966,890,759],"False","3 大于 2 成立，结果为真 True。",{"id":47,"stem":969,"options":970,"answer":9,"explanation":974},"在程序里，表示“是否相等”应该用？",[637,971,972,973],"==","=>","\u003C>","== 是比较相等，= 是赋值，不能混用。",{"id":56,"stem":976,"options":977,"answer":607,"explanation":978},"10 >= 8 的结果是？",[819,966,883,148],"10 大于等于 8 成立，结果为真。",[980],{"id":67,"title":981,"prompt":982,"inputFormat":212,"outputFormat":983,"samples":984,"starterPy":989,"starterCpp":990,"hint":991,"referencePy":992,"referenceCpp":993},"及格判断","输入分数，若 >=60 输出 \"pass\"，否则输出 \"fail\"。","一行 \"pass\" 或 \"fail\"。",[985,986],{"input":216,"output":483},{"input":987,"output":988},"50","fail","score = int(input())\n# 判断是否及格并输出","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score;\n    cin >> score;\n    \u002F\u002F 判断是否及格并输出\n    return 0;\n}","用 if 判断 score >= 60。","score = int(input())\nprint(\"pass\" if score >= 60 else \"fail\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score;\n    cin >> score;\n    if (score >= 60) cout \u003C\u003C \"pass\" \u003C\u003C endl;\n    else cout \u003C\u003C \"fail\" \u003C\u003C endl;\n    return 0;\n}","关系运算（> \u003C >= \u003C= == !=）比较两个值，结果只有真\u002F假。牢记 `==` 是比较、`=` 是赋值，这是新手最常踩的坑。","01-A1-7","逻辑运算",{"id":995,"title":996,"level":7,"module":8,"seq":998,"goal":999,"keywords":1000,"explanation":1005,"examples":1017,"exercises":1025,"summary":1062,"next":1063,"nextTitle":1064},7,"能使用 and \u002F or \u002F not 把多个条件组合起来。",[996,1001,1002,1003,1004],"and","or","not","与或非",[1006,1012],{"title":1007,"content":1008,"codePy":1009,"codeCpp":1010,"tip":1011},"1. 与(and)、或(or)、非(not)","`and` 表示“并且”（两边都真才真），`or` 表示“或者”（有一个真就真），`not` 表示“取反”（真变假、假变真）。","print(True and False)   # False\nprint(True or False)    # True\nprint(not True)         # False","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    cout \u003C\u003C (true && false) \u003C\u003C endl;  \u002F\u002F 0\n    cout \u003C\u003C (true || false) \u003C\u003C endl;  \u002F\u002F 1\n    cout \u003C\u003C (!true) \u003C\u003C endl;          \u002F\u002F 0\n    return 0;\n}","C++ 里“与”是 &&、“或”是 ||、“非”是 !，和 Python 的 and\u002For\u002Fnot 对应。",{"title":1013,"content":1014,"codePy":1015,"codeCpp":1016},"2. 把多个条件合起来","判断“分数够高 并且 出勤够高”这类复合条件时，逻辑运算就派上用场了。","score = 85\nattend = 90\nprint(score >= 60 and attend >= 80)   # True","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score = 85, attend = 90;\n    cout \u003C\u003C (score >= 60 && attend >= 80) \u003C\u003C endl;  \u002F\u002F 1\n    return 0;\n}",[1018],{"title":1019,"prompt":1020,"analysis":1021,"codePy":1022,"codeCpp":1023,"run":1024},"例 1：能否毕业","分数 >=60 且出勤 >=80% 才能毕业，输入两者判断是否达标。","两个条件同时成立用 and（&&）。","score = int(input())\nattend = int(input())\nprint(score >= 60 and attend >= 80)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int score, attend;\n    cin >> score >> attend;\n    cout \u003C\u003C (score >= 60 && attend >= 80) \u003C\u003C endl;\n    return 0;\n}","输入 85 90 → 1",{"choice":1026,"coding":1044},[1027,1032,1040],{"id":38,"stem":1028,"options":1029,"answer":9,"explanation":1031},"True and False 的结果是？",[819,966,1030,567],"None","and 要两边都真才真，一边为假则整体为假。",{"id":47,"stem":1033,"options":1034,"answer":9,"explanation":1039},"C++ 中表示“或者”的运算符是？",[1035,1036,1037,1038],"&&","||","!","&","C++ 用 || 表示“或”，&& 表示“与”。",{"id":56,"stem":1041,"options":1042,"answer":9,"explanation":1043},"not True 的结果是？",[819,966,891,892],"not 取反，True 变 False。",[1045],{"id":67,"title":1046,"prompt":1047,"inputFormat":1048,"outputFormat":1049,"samples":1050,"starterPy":1057,"starterCpp":1058,"hint":1059,"referencePy":1060,"referenceCpp":1061},"VIP 资格","输入是否 VIP（1 表示是，0 表示否）和是否会员日（1\u002F0），两者任一为 1 就输出 \"yes\"，否则 \"no\"。","一行两个整数：is_vip is_member_day。","一行 \"yes\" 或 \"no\"。",[1051,1054],{"input":1052,"output":1053},"0 1","yes",{"input":1055,"output":1056},"0 0","no","a, b = map(int, input().split())\n# 任一为 1 输出 yes","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    \u002F\u002F 任一为 1 输出 yes\n    return 0;\n}","用 or（||）连接两个条件。","a, b = map(int, input().split())\nprint(\"yes\" if a == 1 or b == 1 else \"no\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    if (a == 1 || b == 1) cout \u003C\u003C \"yes\" \u003C\u003C endl;\n    else cout \u003C\u003C \"no\" \u003C\u003C endl;\n    return 0;\n}","逻辑运算把条件组合起来：and(&&) 都真才真，or(||) 一真即真，not(!) 取反。复杂判断就是多个简单条件拼接。","01-A1-8","输入与输出",{"id":1063,"title":1064,"level":7,"module":8,"seq":1066,"goal":1067,"keywords":1068,"explanation":1073,"examples":1085,"exercises":1093,"summary":1127,"next":1128,"nextTitle":1129},8,"能使用 input 接收用户输入，并用 print\u002Fcout 输出结果。",[1069,1070,1071,1072],"输入","输出","input","cin\u002Fcout",[1074,1080],{"title":1075,"content":1076,"codePy":1077,"codeCpp":1078,"tip":1079},"1. 输入：程序“听”用户说话","input（Python）\u002F cin（C++）用来接收用户从键盘输入的内容。注意输入默认是文字，要当数字用要先转换。","name = input(\"你叫什么？\")\nprint(\"你好，\", name)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string name;\n    cout \u003C\u003C \"你叫什么？\" \u003C\u003C endl;\n    cin >> name;\n    cout \u003C\u003C \"你好，\" \u003C\u003C name \u003C\u003C endl;\n    return 0;\n}","Python 的 input() 得到的是字符串，要算数记得用 int()\u002Ffloat() 转换。C++ 用 cin 直接按类型读取。",{"title":1081,"content":1082,"codePy":1083,"codeCpp":1084},"2. 输出：程序“回答”用户","print（Python）\u002F cout（C++）把结果显示出来给用户看。多个内容可以用空格或换行隔开。","a = int(input(\"输入 a：\"))\nb = int(input(\"输入 b：\"))\nprint(\"和是\", a + b)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    cout \u003C\u003C \"和是 \" \u003C\u003C a + b \u003C\u003C endl;\n    return 0;\n}",[1086],{"title":1087,"prompt":1088,"analysis":1089,"codePy":1090,"codeCpp":1091,"run":1092},"例 1：输入两个数求和","输入两个数，输出它们的和。","先输入、转换类型，再相加输出。","a = int(input())\nb = int(input())\nprint(a + b)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b;\n    cin >> a >> b;\n    cout \u003C\u003C a + b \u003C\u003C endl;\n    return 0;\n}","输入 3 5 → 8",{"choice":1094,"coding":1112},[1095,1101,1105],{"id":38,"stem":1096,"options":1097,"answer":9,"explanation":1100},"下面哪个用于“接收用户输入”？",[347,1071,1098,1099],"cout","end","input() 接收用户输入；print\u002Fcout 用于输出。",{"id":47,"stem":1102,"options":1103,"answer":63,"explanation":1104},"Python 中 input() 得到的默认类型是？",[773,774,775,811],"input() 返回字符串，要当数字算需用 int() 转换。",{"id":56,"stem":1106,"options":1107,"answer":9,"explanation":1111},"C++ 中从键盘读入用哪个？",[1098,1108,1109,1110],"cin","scanf","read","C++ 常用 cin 读入（需配合 >>）。",[1113],{"id":67,"title":1114,"prompt":1115,"inputFormat":1116,"outputFormat":1117,"samples":1118,"starterPy":1122,"starterCpp":1123,"hint":1124,"referencePy":1125,"referenceCpp":1126},"问候程序","输入名字，输出 \"Hello, \u003C名字>\"。","一行一个字符串（名字）。","一行 \"Hello, 名字\"。",[1119],{"input":1120,"output":1121},"Alex","Hello, Alex","name = input()\n# 输出问候","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string name;\n    cin >> name;\n    \u002F\u002F 输出问候\n    return 0;\n}","用拼接或格式化把名字放进输出里。","name = input()\nprint(\"Hello,\", name)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string name;\n    cin >> name;\n    cout \u003C\u003C \"Hello, \" \u003C\u003C name \u003C\u003C endl;\n    return 0;\n}","输入让程序“听”用户（input\u002Fcin），输出让程序“答”用户（print\u002Fcout）。Python 的 input 是字符串，算数前记得转换类型。","01-A1-9","条件判断（概念）",{"id":1128,"title":1129,"level":7,"module":8,"seq":1131,"goal":1132,"keywords":1133,"explanation":1136,"examples":1148,"exercises":1156,"summary":1195,"next":87,"nextTitle":88},9,"能理解“如果…就…否则…”的决策逻辑。",[1134,94,1135,92],"条件","决策",[1137,1143],{"title":1138,"content":1139,"codePy":1140,"codeCpp":1141,"tip":1142},"1. 路口根据红绿灯选路","程序根据条件真假走不同分支——就像路口根据红绿灯选走哪条路。条件为真走一条，为假走另一条。这种“看情况行事”的能力叫分支。","# 如果 分数 够高，就 过关，否则 重来\nif score >= 60:\n    过关\nelse:\n    重来","\u002F\u002F 如果分数够高，就过关，否则重来\nif (score >= 60) {\n    过关();\n} else {\n    重来();\n}","先想清楚“判断什么条件、成立做什么、不成立做什么”，再写代码。",{"title":1144,"content":1145,"codePy":1146,"codeCpp":1147},"2. 生活里的条件","“如果下雨就带伞，否则不用”——任何带“如果”的句子都能翻译成条件判断。","rain = True\nif rain:\n    print(\"带伞\")\nelse:\n    print(\"不用带\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    bool rain = true;\n    if (rain) cout \u003C\u003C \"带伞\" \u003C\u003C endl;\n    else cout \u003C\u003C \"不用带\" \u003C\u003C endl;\n    return 0;\n}",[1149],{"title":1150,"prompt":1151,"analysis":1152,"codePy":1153,"codeCpp":1154,"run":1155},"例 1：用生活例子描述条件","请把“如果电量低于 20% 就充电，否则继续玩”写成条件判断思路。","条件是“电量 \u003C 20%”，成立则充电，否则继续。","if battery \u003C 20:\n    print(\"充电\")\nelse:\n    print(\"继续玩\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int battery = 15;\n    if (battery \u003C 20) cout \u003C\u003C \"充电\" \u003C\u003C endl;\n    else cout \u003C\u003C \"继续玩\" \u003C\u003C endl;\n    return 0;\n}","充电",{"choice":1157,"coding":1178},[1158,1166,1170],{"id":38,"stem":1159,"options":1160,"answer":9,"explanation":1165},"条件判断的作用是？",[1161,1162,1163,1164],"让程序重复做事","让程序“看情况”走不同分支","让程序变快","让程序不报错","条件判断让程序根据不同情况执行不同代码。",{"id":47,"stem":1167,"options":1168,"answer":63,"explanation":1169},"“如果条件为真”对应的关键字通常是？",[233,304,92,408],"if 表示“如果”。",{"id":56,"stem":1171,"options":1172,"answer":9,"explanation":1177},"下面哪句话最适合写成条件判断？",[1173,1174,1175,1176],"把 1 到 10 加起来","如果下雨就带伞","输出三行文字","定义变量 x","带“如果”的句子天然就是条件判断。",[1179],{"id":67,"title":1180,"prompt":1181,"inputFormat":1182,"outputFormat":1183,"samples":1184,"starterPy":1190,"starterCpp":1191,"hint":1192,"referencePy":1193,"referenceCpp":1194},"年龄分组","输入年龄，>=18 输出 \"成人\"，否则输出 \"未成年\"。","一行一个整数 age。","一行 \"成人\" 或 \"未成年\"。",[1185,1188],{"input":1186,"output":1187},"20","成人",{"input":884,"output":1189},"未成年","age = int(input())\n# 判断年龄并输出","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int age;\n    cin >> age;\n    \u002F\u002F 判断年龄并输出\n    return 0;\n}","用 if \u002F else 两分支。","age = int(input())\nprint(\"成人\" if age >= 18 else \"未成年\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int age;\n    cin >> age;\n    if (age >= 18) cout \u003C\u003C \"成人\" \u003C\u003C endl;\n    else cout \u003C\u003C \"未成年\" \u003C\u003C endl;\n    return 0;\n}","条件判断让程序“看情况行事”：条件为真走一条路，为假走另一条。先想清“判断什么、成立做什么、不成立做什么”。",{"id":631,"title":632,"level":1197,"module":8,"seq":9,"goal":1198,"keywords":1199,"explanation":1203,"examples":1215,"exercises":1223,"summary":1263,"next":1264,"nextTitle":1265},"Lv.3","掌握\"嵌套循环\"：在循环中再套循环，用来扫描二维结构（矩阵、棋盘）或枚举组合。",[632,1200,1201,1202],"二维遍历","乘法表","组合枚举",[1204,1209],{"title":1205,"content":1206,"codePy":1207,"codeCpp":1208},"1. 外层每走一步，内层完整跑一圈","嵌套循环就是\"循环里面再套循环\"。外层循环每执行一次，内层循环会从头到尾完整执行一遍。最典型用途是处理二维结构（矩阵、棋盘）以及枚举所有 (i,j) 组合（如乘法表）。","for i in range(3):\n    for j in range(4):\n        print(i, j)","for(int i=0;i\u003C3;i++)\n    for(int j=0;j\u003C4;j++)\n        cout\u003C\u003Ci\u003C\u003C\" \"\u003C\u003Cj;",{"title":1210,"content":1211,"codePy":1212,"codeCpp":1213,"tip":1214},"2. 总次数是\"乘积\"而不是\"和\"","外层 n 次、内层 m 次时，内层体一共执行 n×m 次，这正是打印 n 行 m 列、或枚举所有组合的工作量。注意：把两个 for 写成并列才是 n+m 次，写成嵌套才是 n×m 次。","for i in range(n):\n    for j in range(m):\n        do(i, j)   # 共 n*m 次","for(int i=0;i\u003Cn;i++)\n    for(int j=0;j\u003Cm;j++)\n        do(i, j); \u002F\u002F n*m 次","先确定\"要枚举哪些下标\"，再决定谁在外层、谁在内层。",[1216],{"title":1217,"prompt":1218,"analysis":1219,"codePy":1220,"codeCpp":1221,"run":1222},"例 1：九九乘法表前 3 行","打印乘法表的前 3 行（第 i 行输出 i×1 到 i×i）。","外层 i 控制行，内层 j 从 1 到 i，输出 i*j。","for i in range(1, 4):\n    for j in range(1, i+1):\n        print(f\"{i}*{j}={i*j}\", end=\" \")\n    print()","for(int i=1;i\u003C=3;i++){\n    for(int j=1;j\u003C=i;j++)\n        cout\u003C\u003Ci\u003C\u003C\"*\"\u003C\u003Cj\u003C\u003C\"=\"\u003C\u003Ci*j\u003C\u003C\" \";\n    cout\u003C\u003Cendl;\n}","1*1=1 \n2*1=2 2*2=4 \n3*1=3 3*2=6 3*3=9",{"choice":1224,"coding":1248},[1225,1232,1240],{"id":38,"stem":1226,"options":1227,"answer":9,"explanation":1231},"外层 3 次、内层 4 次的嵌套循环，内层体共执行？",[1228,1229,1230,261],"7 次","12 次","3 次","n×m = 3×4 = 12 次。",{"id":47,"stem":1233,"options":1234,"answer":63,"explanation":1239},"想打印 n 行 m 列的星号矩阵，正确的结构是？",[1235,1236,1237,1238],"两个并列 for","一个 for","嵌套 for","只用 while","行列二维结构用嵌套循环。",{"id":56,"stem":1241,"options":1242,"answer":9,"explanation":1247},"两层嵌套循环的时间复杂度一般是？",[1243,1244,1245,1246],"O(n+m)","O(n×m)","O(1)","O(log n)","内层体执行 n×m 次。",[1249],{"id":67,"title":1250,"prompt":1251,"inputFormat":1252,"outputFormat":1253,"samples":1254,"starterPy":1258,"starterCpp":1259,"hint":1260,"referencePy":1261,"referenceCpp":1262},"星号矩阵","输入 n 和 m（均 ≤ 50），输出一个 n 行 m 列的矩阵，每个位置都是星号 \"*\"，同行空格分隔，行间换行。","一行两个整数 n m。","n 行，每行 m 个 \"*\" 用空格分隔。",[1255],{"input":1256,"output":1257},"3 4","* * * *\n* * * *\n* * * *","n, m = map(int, input().split())\n# 打印 n 行 m 列星号\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, m; cin >> n >> m;\n    \u002F\u002F 嵌套循环打印\n    return 0;\n}","外层 i 从 0 到 n-1，内层 j 从 0 到 m-1，每行最后一个星号后不加多余空格。","n, m = map(int, input().split())\nfor i in range(n):\n    print(\" \".join(\"*\" for _ in range(m)))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, m; cin >> n >> m;\n    for(int i=0;i\u003Cn;i++){\n        for(int j=0;j\u003Cm;j++)\n            cout \u003C\u003C (j ? \" \" : \"\") \u003C\u003C \"*\";\n        cout \u003C\u003C endl;\n    }\n    return 0;\n}","嵌套循环用\"外层每步带动内层完整跑一圈\"来扫描二维或组合空间，总次数是内外层次数的乘积 n×m。先确定枚举的下标，再安排内外层。","01-A2-2","嵌套分支",{"id":1267,"title":1268,"level":1197,"module":8,"seq":89,"goal":1269,"keywords":1270,"explanation":1274,"examples":1286,"exercises":1294,"summary":1333,"next":1334,"nextTitle":1335},"01-A2-10","引用","理解 C++ 引用（变量的别名），用引用参数让函数修改外部变量或避免大对象拷贝。",[1268,1271,1272,1273],"别名","引用参数","swap",[1275,1280],{"title":1276,"content":1277,"codePy":1278,"codeCpp":1279},"1. 引用是\"别名\"","int& r = a; 之后 r 就是 a 的别名，对 r 的任何操作都等价于对 a 操作。引用必须在定义时绑定到一个变量，且不能再改绑。","# Python 没有引用概念，但可变对象（list 等）传\"引用\"\ndef append_one(lst):\n    lst.append(1)","int a = 5;\nint& r = a;\nr = 10;          \u002F\u002F a 也变成 10",{"title":1281,"content":1282,"codePy":1283,"codeCpp":1284,"tip":1285},"2. 引用参数：让函数\"就地修改\"","把函数参数写成 int& x，传进来的就是外部变量的别名，函数里改 x 会直接影响外部。经典例子是 swap；也常用于需要返回多个结果或避免拷贝大结构体。","# 用可变对象达到类似效果\ndef swap(a, b):\n    return b, a","void swap(int& a, int& b){ int t=a; a=b; b=t; }","只读的大对象参数用 const 引用 const T& 既避免拷贝又防止误改。",[1287],{"title":1288,"prompt":1289,"analysis":1290,"codePy":1291,"codeCpp":1292,"run":1293},"例 1：用引用写 swap","写 swap(a,b) 用引用参数交换两个整数；主程序验证。","参数是 int&，交换后外部变量也交换。","def swap(a, b):\n    return b, a\na, b = swap(a, b)","void swap(int& a, int& b){ int t=a; a=b; b=t; }\nint main(){ int a=1,b=2; swap(a,b); cout\u003C\u003Ca\u003C\u003C\" \"\u003C\u003Cb; }","2 1",{"choice":1295,"coding":1319},[1296,1304,1311],{"id":38,"stem":1297,"options":1298,"answer":9,"explanation":1303},"关于 C++ 引用，正确的是？",[1299,1300,1301,1302],"引用可重新绑定","引用是别名","引用可为空","引用用 * 取值","引用是已存在变量的别名。",{"id":47,"stem":1305,"options":1306,"answer":9,"explanation":1310},"void f(int& x){x++;} 调用后，实参会？",[1307,1308,1309,567],"不变","加 1","变 0","引用参数直接改外部变量。",{"id":56,"stem":1312,"options":1313,"answer":9,"explanation":1318},"避免大对象拷贝又防止误改，应使用？",[1314,1315,1316,1317],"int&","const T&","T","指针 T*","const 引用只读且不拷贝。",[1320],{"id":67,"title":1321,"prompt":1322,"inputFormat":1323,"outputFormat":1324,"samples":1325,"starterPy":1328,"starterCpp":1329,"hint":1330,"referencePy":1331,"referenceCpp":1332},"引用交换","写一个函数 swap(int& a, int& b) 交换两个整数；主程序读入 a,b 输出交换后的结果。","一行两个整数 a b（|a|,|b|≤1e9）。","一行：交换后的 a b。",[1326],{"input":1256,"output":1327},"4 3","# Python 用元组返回即可\ndef swap(a, b):\n    return b, a\n","#include \u003Ciostream>\nusing namespace std;\nvoid swap(int& a, int& b){\n    \u002F\u002F 交换\n}\nint main(){ int a,b; cin>>a>>b; swap(a,b); cout\u003C\u003Ca\u003C\u003C\" \"\u003C\u003Cb; }","用临时变量 t 完成交换。","a, b = map(int, input().split())\na, b = b, a\nprint(a, b)","#include \u003Ciostream>\nusing namespace std;\nvoid swap(int& a, int& b){ int t=a; a=b; b=t; }\nint main(){ int a,b; cin>>a>>b; swap(a,b); cout\u003C\u003Ca\u003C\u003C\" \"\u003C\u003Cb; }","引用是变量的别名；引用参数让函数能就地修改外部变量（经典 swap），const 引用用于只读大对象以避免拷贝。","01-A2-11","作用域",{"id":1334,"title":1335,"level":1197,"module":8,"seq":158,"goal":1337,"keywords":1338,"explanation":1342,"examples":1354,"exercises":1362,"summary":1399,"next":1400,"nextTitle":1401},"理解变量作用域（局部\u002F全局）与可见性，避免命名冲突与\"未定义\"错误。",[1335,1339,1340,1341],"局部变量","全局变量","生命周期",[1343,1348],{"title":1344,"content":1345,"codePy":1346,"codeCpp":1347},"1. 局部变量只在\"块\"内可见","在函数或 {} 内定义的变量是局部变量，只在该块内有效；出了块就消失（生命周期结束）。在块外访问它属于\"未定义\"。","def f():\n    x = 1      # 局部\nf()\nprint(x)      # 报错：x 未定义","void f(){ int x = 1; }  \u002F\u002F x 只在 f 内有效\nint main(){ \u002F* x 这里不可见 *\u002F }",{"title":1349,"content":1350,"codePy":1351,"codeCpp":1352,"tip":1353},"2. 全局变量与\"遮蔽\"","在所有函数外定义的是全局变量，整个程序都能访问。若局部又定义了同名变量，局部会\"遮蔽\"全局——此时在该局部里用的是局部变量。尽量少用全局，避免相互干扰。","g = 0\ndef f():\n    g = 1      # 新建局部，不改动全局\n    print(g)   # 1\ndef h():\n    global g; g = 2\nprint(g)      # 0","int g = 0;\nvoid f(){ int g = 1; }  \u002F\u002F 局部遮蔽全局","同名变量\"就近原则\"：先找最近的作用域。",[1355],{"title":1356,"prompt":1357,"analysis":1358,"codePy":1359,"codeCpp":1360,"run":1361},"例 1：局部遮蔽全局","全局变量 g=0，函数内定义同名局部 g=1，观察输出。","函数内用的是局部 g，不影响全局。","g = 0\ndef f():\n    g = 1\n    print(\"in f:\", g)\nf()\nprint(\"global:\", g)","int g = 0;\nvoid f(){ int g = 1; cout \u003C\u003C g; }\nint main(){ f(); cout \u003C\u003C g; }","in f: 1\nglobal: 0",{"choice":1363,"coding":1387},[1364,1372,1379],{"id":38,"stem":1365,"options":1366,"answer":9,"explanation":1371},"在函数内定义的变量，函数外能直接访问吗？",[1367,1368,1369,1370],"能","不能（局部）","有时能","变成全局","局部变量只在函数内可见。",{"id":47,"stem":1373,"options":1374,"answer":9,"explanation":1378},"局部变量与全局变量同名时，局部内使用的是？",[1375,1376,1377,567],"全局","局部（遮蔽）","两者都","就近原则，局部遮蔽全局。",{"id":56,"stem":1380,"options":1381,"answer":9,"explanation":1386},"关于全局变量，最佳实践是？",[1382,1383,1384,1385],"大量使用","尽量少用","只能有一个","不能用","过多全局易互相干扰、难调试。",[1388],{"id":67,"title":1389,"prompt":1390,"inputFormat":353,"outputFormat":1391,"samples":1392,"starterPy":1394,"starterCpp":1395,"hint":1396,"referencePy":1397,"referenceCpp":1398},"全局计数器","定义一个全局变量 cnt=0。写函数 inc() 让其加 1（用 global \u002F 全局引用），主程序调用三次后输出 cnt。","一行：3。",[1393],{"input":357,"output":890},"cnt = 0\ndef inc():\n    global cnt\n    cnt += 1\n","#include \u003Ciostream>\nusing namespace std;\nint cnt = 0;\nvoid inc(){ \u002F* 修改全局 cnt *\u002F }\nint main(){ inc(); inc(); inc(); cout \u003C\u003C cnt; }","C++ 里全局变量函数可直接访问并修改；Python 需 global。","cnt = 0\ndef inc():\n    global cnt\n    cnt += 1\ninc(); inc(); inc()\nprint(cnt)","#include \u003Ciostream>\nusing namespace std;\nint cnt = 0;\nvoid inc(){ cnt += 1; }\nint main(){ inc(); inc(); inc(); cout \u003C\u003C cnt; }","局部变量只在所在块内有效，全局变量全程序可访问；同名时局部遮蔽全局。尽量限制全局变量的使用。","01-A2-12","递归基础",{"id":1400,"title":1401,"level":1197,"module":8,"seq":230,"goal":1403,"keywords":1404,"explanation":1409,"examples":1421,"exercises":1429,"summary":1465,"next":1466,"nextTitle":1467},"理解\"函数调用自身\"的递归思想：把大问题拆成相同的小问题，并写出正确的终止条件。",[1405,1406,1407,1408],"递归","终止条件","自调用","调用栈",[1410,1415],{"title":1411,"content":1412,"codePy":1413,"codeCpp":1414},"1. 递归 = 自己调用自己","递归函数直接或间接调用自身。它把\"规模为 n 的问题\"转化为\"一个或多个规模更小的同类问题\"，直到小到能直接回答（终止条件 \u002F base case）。","def fact(n):\n    if n \u003C= 1: return 1      # 终止\n    return n * fact(n - 1)","long long fact(int n){\n    if (n \u003C= 1) return 1;\n    return (long long)n * fact(n - 1);\n}",{"title":1416,"content":1417,"codePy":1418,"codeCpp":1419,"tip":1420},"2. 必须有终止条件，否则无限递归","没有终止条件的递归会一直调用下去，耗尽调用栈导致栈溢出（崩溃）。写递归先写 base case，再写\"调用自身处理更小规模\"。每层调用都会在栈上压入一帧，递归越深占栈越多。","def fib(n):\n    if n \u003C= 1: return n\n    return fib(n - 1) + fib(n - 2)","int fib(int n){\n    if (n \u003C= 1) return n;\n    return fib(n - 1) + fib(n - 2);\n}","递归三要素：终止条件、递推关系、向终止收敛。",[1422],{"title":1423,"prompt":1424,"analysis":1425,"codePy":1426,"codeCpp":1427,"run":1428},"例 1：阶乘递归","用递归求 n!（n≤12）。","n! = n × (n-1)!，终止于 n\u003C=1 返回 1。","def fact(n):\n    if n \u003C= 1: return 1\n    return n * fact(n - 1)\nprint(fact(int(input())))","long long fact(int n){ if (n\u003C=1) return 1; return (long long)n * fact(n-1); }\nint main(){ int n; cin>>n; cout\u003C\u003Cfact(n); }","输入 5 -> 120",{"choice":1430,"coding":1452},[1431,1437,1445],{"id":38,"stem":1432,"options":1433,"answer":9,"explanation":1436},"递归必不可少的是？",[192,1406,1434,1435],"数组","指针","无终止条件会无限递归、栈溢出。",{"id":47,"stem":1438,"options":1439,"answer":9,"explanation":1444},"n! 的递归关系是？",[1440,1441,1442,1443],"n! = fact(n)+1","n! = n × fact(n-1)","n! = fact(n-1)","n! = n + fact(n-1)","阶乘满足 n! = n×(n-1)!。",{"id":56,"stem":1446,"options":1447,"answer":9,"explanation":1451},"没有终止条件的递归会导致？",[1448,1449,1450,339],"更快","栈溢出","返回 0","调用栈耗尽而崩溃。",[1453],{"id":67,"title":1454,"prompt":1455,"inputFormat":1456,"outputFormat":1457,"samples":1458,"starterPy":1461,"starterCpp":1462,"hint":1463,"referencePy":1426,"referenceCpp":1464},"递归阶乘","用递归函数 fact(n) 求 n!（1≤n≤12）；主程序读入 n 输出结果（用 long long）。","一行整数 n。","一行：n!。",[1459],{"input":290,"output":1460},"120","def fact(n):\n    if n \u003C= 1: return 1\n    # 递归\n","#include \u003Ciostream>\nusing namespace std;\nlong long fact(int n){\n    if (n \u003C= 1) return 1;\n    \u002F\u002F 递归\n}\nint main(){ int n; cin>>n; cout\u003C\u003Cfact(n); }","返回 (long long)n * fact(n-1)。","#include \u003Ciostream>\nusing namespace std;\nlong long fact(int n){ if (n\u003C=1) return 1; return (long long)n * fact(n-1); }\nint main(){ int n; cin>>n; cout\u003C\u003Cfact(n); }","递归用自调用把问题化小；三要素是终止条件、递推关系、向终止收敛。缺少终止条件会导致无限递归与栈溢出。","01-A2-13","结构体",{"id":1466,"title":1467,"level":1197,"module":8,"seq":301,"goal":1469,"keywords":1470,"explanation":1474,"examples":1486,"exercises":1494,"summary":1533,"next":1534,"nextTitle":1535},"能把多个不同类型的数据组合成一个\"结构体\"类型，管理一组相关属性。",[1467,1471,1472,1473],"struct","成员","组合",[1475,1480],{"title":1476,"content":1477,"codePy":1478,"codeCpp":1479},"1. struct 把相关字段打包","结构体让多个不同类型的数据作为一个整体。C++ 用 struct Student { int id; string name; }; 定义；Python 可用类或字典。访问成员用 \".\"。","class Student:\n    def __init__(s, i, n):\n        s.id = i; s.name = n\nst = Student(1, \"Tom\")\nprint(st.name)","struct Student { int id; string name; };\nStudent st;\nst.id = 1; st.name = \"Tom\";\ncout \u003C\u003C st.name;",{"title":1481,"content":1482,"codePy":1483,"codeCpp":1484,"tip":1485},"2. 结构体数组与排序","结构体可以放进数组，每个元素是一组属性，非常适合\"多条记录\"。配合按某字段排序，就能做排行榜、成绩统计等。","students = [{\"id\":1,\"sc\":90}, {\"id\":2,\"sc\":80}]\nstudents.sort(key=lambda x: -x[\"sc\"])","Student a[3];\n\u002F\u002F 按成绩排序可用 sort(a, a+3, cmp)","处理\"同一个对象的多个属性\"时优先考虑结构体。",[1487],{"title":1488,"prompt":1489,"analysis":1490,"codePy":1491,"codeCpp":1492,"run":1493},"例 1：学生结构体排序","定义学生结构体(学号, 成绩)，输入 3 人，按成绩从高到低输出。","用结构体数组存记录，再按成绩排序。","sts = []\nfor _ in range(3):\n    i, s = map(int, input().split())\n    sts.append({\"id\": i, \"sc\": s})\nsts.sort(key=lambda x: -x[\"sc\"])\nfor x in sts: print(x[\"id\"], x[\"sc\"])","struct St{ int id, sc; };\nSt a[3];\nfor(int i=0;i\u003C3;i++) cin>>a[i].id>>a[i].sc;\nsort(a, a+3, [](St x, St y){ return x.sc > y.sc; });","输入 (1,90)(2,80)(3,95) -> 3 95 \u002F 1 90 \u002F 2 80",{"choice":1495,"coding":1518},[1496,1502,1510],{"id":38,"stem":1497,"options":1498,"answer":9,"explanation":1501},"结构体主要解决什么问题？",[192,1499,1405,1500],"把多个字段组合成整体","位运算","struct 把相关属性打包。",{"id":47,"stem":1503,"options":1504,"answer":607,"explanation":1509},"访问结构体成员用？",[1505,1506,1507,1508],".","->","::","[]","普通变量用 .；指针用 ->。",{"id":56,"stem":1511,"options":1512,"answer":9,"explanation":1517},"想把 Student 数组按成绩排序，应？",[1513,1514,1515,1516],"不能排序","按 sc 字段排序","按 id 排序","随便排","用成员字段做排序键。",[1519],{"id":67,"title":1520,"prompt":1521,"inputFormat":1522,"outputFormat":1523,"samples":1524,"starterPy":1528,"starterCpp":1529,"hint":1530,"referencePy":1531,"referenceCpp":1532},"最高分","写结构体 Student(学号 id, 成绩 sc)。输入 3 人(整数 id 与 sc)，输出成绩最高的人的 id 和 sc。","3 行，每行两个整数 id sc。","一行：最高分的 id 与 sc。",[1525],{"input":1526,"output":1527},"1 90\n2 80\n3 95","3 95","sts = []\nfor _ in range(3):\n    i, s = map(int, input().split())\n    sts.append({\"id\": i, \"sc\": s})\n# 找最高\n","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nstruct St{ int id, sc; };\nint main(){\n    St a[3];\n    for(int i=0;i\u003C3;i++) cin>>a[i].id>>a[i].sc;\n    \u002F\u002F 找最高分\n    return 0;\n}","遍历数组记录最大 sc 及其 id。","sts=[]\nfor _ in range(3):\n    i,s=map(int,input().split()); sts.append({\"id\":i,\"sc\":s})\nbest=max(sts,key=lambda x:x[\"sc\"])\nprint(best[\"id\"],best[\"sc\"])","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nstruct St{ int id, sc; };\nint main(){\n    St a[3];\n    for(int i=0;i\u003C3;i++) cin>>a[i].id>>a[i].sc;\n    St best=a[0];\n    for(int i=1;i\u003C3;i++) if(a[i].sc>best.sc) best=a[i];\n    cout\u003C\u003Cbest.id\u003C\u003C\" \"\u003C\u003Cbest.sc; return 0;\n}","结构体把多个相关字段组合成整体，便于管理一组属性；结构体数组配合按字段排序可做记录与排行榜。","01-A2-14","自定义数据类型",{"id":1534,"title":1535,"level":1197,"module":8,"seq":368,"goal":1537,"keywords":1538,"explanation":1543,"examples":1555,"exercises":1562,"summary":1599,"next":1600,"nextTitle":1601},"理解 typedef\u002Fusing 与枚举 enum，给已有类型起别名、定义有限取值集合，提升可读性。",[1539,1540,1541,1542],"typedef","using","enum","枚举",[1544,1549],{"title":1545,"content":1546,"codePy":1547,"codeCpp":1548},"1. 类型别名 typedef \u002F using","typedef int Score; 或 using Score = int; 给类型起个新名字，让代码更语义化，也方便日后统一修改底层类型（如把 int 改成 long long）。","# Python 动态类型，重命名用赋值\nScore = int\ndef f(x: Score): return x","typedef long long ll;\nusing Score = int;",{"title":1550,"content":1551,"codePy":1552,"codeCpp":1553,"tip":1554},"2. 枚举 enum 表示有限取值","enum Color { Red, Green, Blue }; 定义一组命名常量，默认从 0 开始递增，也可手动指定初值。相比用魔法数字 0\u002F1\u002F2，枚举可读性更高、也更安全。","from enum import Enum\nclass Color(Enum):\n    RED=0; GREEN=1; BLUE=2","enum Week { Mon=1, Tue, Wed }; \u002F\u002F Mon=1 之后递增","用枚举表示\"状态、方向、星期\"等有限集合，避免散落的数字。",[1556],{"title":1557,"prompt":1558,"analysis":1559,"codePy":1560,"codeCpp":1561,"run":890},"例 1：枚举表示星期","定义枚举 Week(Mon..Sun)，输出 Wed 对应的数字。","枚举默认从 0 递增，可指定初值。","from enum import Enum\nclass Week(Enum):\n    Mon=1; Tue=2; Wed=3; Sun=7\nprint(Week.Wed.value)","enum Week { Mon=1, Tue, Wed, Sun=7 };\ncout \u003C\u003C Wed;",{"choice":1563,"coding":1585},[1564,1570,1578],{"id":38,"stem":1565,"options":1566,"answer":9,"explanation":1569},"enum 中第一个常量默认值是？",[892,891,1567,1568],"-1","随机","枚举默认从 0 开始。",{"id":47,"stem":1571,"options":1572,"answer":9,"explanation":1577},"typedef long long ll; 的作用是？",[1573,1574,1575,1576],"定义新类型","给 long long 起别名 ll","声明变量","强制转换","typedef\u002Fusing 是类型别名。",{"id":56,"stem":1579,"options":1580,"answer":9,"explanation":1584},"用枚举而非数字表示方向，主要好处是？",[1448,1581,1582,1583],"可读性高、防误用","省内存","能比较大小","命名常量更语义化、更安全。",[1586],{"id":67,"title":1587,"prompt":1588,"inputFormat":1589,"outputFormat":1590,"samples":1591,"starterPy":1594,"starterCpp":1595,"hint":1596,"referencePy":1597,"referenceCpp":1598},"枚举星期","定义枚举 Week(Mon=1,Tue,Wed,Thu,Fri,Sat,Sun)，输入一个 1~7 的整数 d，输出对应英文缩写（Mon\u002FTue\u002F...\u002FSun）。","一行整数 d（1~7）。","一行英文缩写。",[1592],{"input":890,"output":1593},"Wed","from enum import Enum\nclass Week(Enum):\n    Mon=1; Tue=2; Wed=3; Thu=4; Fri=5; Sat=6; Sun=7\nnames = {1:\"Mon\",2:\"Tue\",3:\"Wed\",4:\"Thu\",5:\"Fri\",6:\"Sat\",7:\"Sun\"}\nd = int(input())\n","#include \u003Ciostream>\nusing namespace std;\nenum Week { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };\nint main(){\n    int d; cin >> d;\n    \u002F\u002F 按 d 输出缩写\n    return 0;\n}","用一个字符串数组按 d-1 下标取缩写。","from enum import Enum\nclass Week(Enum):\n    Mon=1; Tue=2; Wed=3; Thu=4; Fri=5; Sat=6; Sun=7\nnames={1:\"Mon\",2:\"Tue\",3:\"Wed\",4:\"Thu\",5:\"Fri\",6:\"Sat\",7:\"Sun\"}\nd=int(input()); print(names[d])","#include \u003Ciostream>\nusing namespace std;\nenum Week { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };\nint main(){\n    const char* names[7]={\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\",\"Sun\"};\n    int d; cin >> d; cout \u003C\u003C names[d-1]; return 0;\n}","typedef\u002Fusing 给类型起别名提升语义化；enum 用命名常量表示有限取值集合，比魔法数字更可读、更安全。","23-A1-1","二进制",{"id":1264,"title":1265,"level":1197,"module":8,"seq":63,"goal":1603,"keywords":1604,"explanation":1608,"examples":1620,"exercises":1628,"summary":1672,"next":1673,"nextTitle":1674},"掌握\"嵌套分支\"：在 if 内部再写 if\u002Felse，处理多维条件，并理解 else 与哪个 if 配对。",[1265,1605,1606,1607],"else 配对","多条件","象限",[1609,1614],{"title":1610,"content":1611,"codePy":1612,"codeCpp":1613},"1. else 与\"最近的\" if 配对","在 if 里面再写 if\u002Felse 就是嵌套分支。C++\u002FPython 里，else 总是和它前面最近的、尚未配对的 if 配对。如果层级变深，最好用 elif（Python）或清晰的括号来避免\"悬挂 else\"的误读。","if x > 0:\n    if y > 0:\n        print(\"第一象限\")\n    else:\n        print(\"第四象限\")","if (x > 0) {\n    if (y > 0) cout \u003C\u003C \"第一象限\";\n    else cout \u003C\u003C \"第四象限\";\n}",{"title":1615,"content":1616,"codePy":1617,"codeCpp":1618,"tip":1619},"2. 用逻辑运算替代深层嵌套","当多个条件要\"同时成立\"时，用 and\u002F&& 把它们并到同一层 if，比层层嵌套更清晰、也不容易漏分支。判断\"落在某个区间\"尤其常用。","if 1 \u003C= x \u003C= 10 and y == 0:\n    print(\"边界\")","if (x >= 1 && x \u003C= 10 && y == 0) cout \u003C\u003C \"边界\";","先画一个\"条件表\"列出所有可能分支，再写代码，避免少写 else。",[1621],{"title":1622,"prompt":1623,"analysis":1624,"codePy":1625,"codeCpp":1626,"run":1627},"例 1：判断坐标所在象限","输入平面坐标 (x,y)，输出它所在的象限（1~4）；若在坐标轴上输出 \"AXIS\"。","先判 x、y 是否为零（轴），否则按正负组合判断四个象限。","if x == 0 or y == 0: print(\"AXIS\")\nelif x > 0 and y > 0: print(1)\nelif x \u003C 0 and y > 0: print(2)\nelif x \u003C 0 and y \u003C 0: print(3)\nelse: print(4)","if (x==0 || y==0) cout\u003C\u003C\"AXIS\";\nelse if (x>0 && y>0) cout\u003C\u003C1;\nelse if (x\u003C0 && y>0) cout\u003C\u003C2;\nelse if (x\u003C0 && y\u003C0) cout\u003C\u003C3;\nelse cout\u003C\u003C4;","输入 (3, -2) -> 4",{"choice":1629,"coding":1654},[1630,1638,1646],{"id":38,"stem":1631,"options":1632,"answer":9,"explanation":1637},"代码 \"if(a) if(b) P; else Q;\" 中的 else 配对的是？",[1633,1634,1635,1636],"外层的 if(a)","内层的 if(b)","两者都配","不配对","else 与最近的未配对 if（即 if(b)）配对。",{"id":47,"stem":1639,"options":1640,"answer":63,"explanation":1645},"判断 x 是否同时在 [1,10] 区间，最清晰写法是？",[1641,1642,1643,1644],"嵌套 if","if(1\u003C=x\u003C=10)","if(x>=1 && x\u003C=10)","if(x>=1) if(x\u003C=10)","用 && 把条件并到同一层最清晰。",{"id":56,"stem":1647,"options":1648,"answer":9,"explanation":1653},"避免\"漏分支\"的最佳习惯是？",[1649,1650,1651,1652],"少写 else","画条件表再写","只写 if","随意写","先列全分支再编码，能覆盖所有情况。",[1655],{"id":67,"title":1656,"prompt":1657,"inputFormat":1658,"outputFormat":1659,"samples":1660,"starterPy":1667,"starterCpp":1668,"hint":1669,"referencePy":1670,"referenceCpp":1671},"坐标象限","输入两个整数 x, y（|x|,|y|≤1000），输出所在象限 1~4；若 x==0 或 y==0 输出 \"AXIS\"。","一行两个整数 x y。","一行：1、2、3、4 或 \"AXIS\"。",[1661,1664],{"input":1662,"output":1663},"3 -2","4",{"input":1665,"output":1666},"0 5","AXIS","x, y = map(int, input().split())\n# 判断象限\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, y; cin >> x >> y;\n    \u002F\u002F 判断\n    return 0;\n}","先判 AXIS（x==0 或 y==0），再用 x、y 的正负组合。","x, y = map(int, input().split())\nif x == 0 or y == 0: print(\"AXIS\")\nelif x > 0 and y > 0: print(1)\nelif x \u003C 0 and y > 0: print(2)\nelif x \u003C 0 and y \u003C 0: print(3)\nelse: print(4)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, y; cin >> x >> y;\n    if (x==0 || y==0) cout \u003C\u003C \"AXIS\";\n    else if (x>0 && y>0) cout \u003C\u003C 1;\n    else if (x\u003C0 && y>0) cout \u003C\u003C 2;\n    else if (x\u003C0 && y\u003C0) cout \u003C\u003C 3;\n    else cout \u003C\u003C 4;\n    return 0;\n}","嵌套分支在 if 内再写 if\u002Felse，else 与最近的 if 配对；用 and\u002F&& 把\"同时成立\"的条件并到同一层能减少层级、避免漏分支。","01-A2-3","多条件逻辑",{"id":1673,"title":1674,"level":1197,"module":8,"seq":703,"goal":1676,"keywords":1677,"explanation":1681,"examples":1693,"exercises":1701,"summary":1744,"next":1745,"nextTitle":1746},"掌握用 and\u002For\u002Fnot（C++ 的 &&\u002F||\u002F!）组合多个布尔条件，正确写出复合判断，并理解短路求值。",[996,1678,1679,1680],"短路","德摩根","复合条件",[1682,1687],{"title":1683,"content":1684,"codePy":1685,"codeCpp":1686},"1. 三种逻辑运算与真值","and(&&)：全真才真；or(||)：有真即真；not(!)：取反。组合条件时，先按真值表推一遍，再写代码。","ok = (age >= 18) and (id_card == True)\nfree = (vip == True) or (score > 90)","bool ok = (age >= 18) && id_card;\nbool free = vip || (score > 90);",{"title":1688,"content":1689,"codePy":1690,"codeCpp":1691,"tip":1692},"2. 短路求值与德摩根定律","短路：当 and 的左为假、或 or 的左为真时，右半边不再计算——这能用来\"先判边界再访问\"。德摩根定律：!(A&&B) = !A || !B，!(A||B) = !A && !B，改写条件时常用来简化否定。","if i \u003C n and a[i] == 0:   # 先判越界再访问\nsafe = not (a == 0 and b == 0)","if (i \u003C n && a[i] == 0) ; \u002F\u002F 先判越界\nbool safe = !(a == 0 && b == 0);","把\"否定\"翻译成肯定、或反过来，优先用德摩根定律，避免写出双重否定。",[1694],{"title":1695,"prompt":1696,"analysis":1697,"codePy":1698,"codeCpp":1699,"run":1700},"例 1：能否构成三角形","输入三边 a,b,c，判断能否构成三角形（任意两边和大于第三边）。","用 and 把三个不等式并起来。","ok = (a+b>c) and (a+c>b) and (b+c>a)\nprint(\"YES\" if ok else \"NO\")","bool ok = (a+b>c) && (a+c>b) && (b+c>a);\ncout \u003C\u003C (ok ? \"YES\" : \"NO\");","输入 3 4 5 -> YES",{"choice":1702,"coding":1726},[1703,1711,1718],{"id":38,"stem":1704,"options":1705,"answer":607,"explanation":1710},"表达式 ! (a || b) 等价于？",[1706,1707,1708,1709],"!a && !b","!a || !b","!(!a && !b)","a && b","德摩根：!(A||B) = !A && !B。",{"id":47,"stem":1712,"options":1713,"answer":9,"explanation":1717},"在 \"if(i \u003C n && a[i]==0)\" 中，若 i>=n，则？",[1714,1715,567,1716],"仍会访问 a[i]","右半边不执行（短路）","结果为真","&& 左为假时短路，a[i] 不会被访问，避免越界。",{"id":56,"stem":1719,"options":1720,"answer":9,"explanation":1725},"\"x 不在 [1,10]\" 应写成？",[1721,1722,1723,1724],"!(1\u003C=x\u003C=10)","x\u003C1 || x>10","x\u003C=1 && x>=10","not(x>=1 and x\u003C=10)","用德摩根展开，x\u003C1 或 x>10。",[1727],{"id":67,"title":1728,"prompt":1729,"inputFormat":1730,"outputFormat":1731,"samples":1732,"starterPy":1739,"starterCpp":1740,"hint":1741,"referencePy":1742,"referenceCpp":1743},"闰年判断","输入年份 y（1≤y≤9999），判断是否为闰年：能被 400 整除，或能被 4 整除但不能被 100 整除。是则输出 \"LEAP\"，否则 \"COMMON\"。","一行一个整数 y。","一行 \"LEAP\" 或 \"COMMON\"。",[1733,1736],{"input":1734,"output":1735},"2000","LEAP",{"input":1737,"output":1738},"1900","COMMON","y = int(input())\n# 闰年条件\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int y; cin >> y;\n    \u002F\u002F 判断\n    return 0;\n}","条件为 (y%400==0) || (y%4==0 && y%100!=0)。","y = int(input())\nleap = (y % 400 == 0) or (y % 4 == 0 and y % 100 != 0)\nprint(\"LEAP\" if leap else \"COMMON\")","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int y; cin >> y;\n    bool leap = (y%400==0) || (y%4==0 && y%100!=0);\n    cout \u003C\u003C (leap ? \"LEAP\" : \"COMMON\");\n    return 0;\n}","多条件逻辑用 &&\u002F||\u002F! 组合布尔值；利用短路可先判边界再访问，德摩根定律用于条件取反的改写。","01-A2-4","类型转换",{"id":1745,"title":1746,"level":1197,"module":8,"seq":770,"goal":1748,"keywords":1749,"explanation":1754,"examples":1766,"exercises":1774,"summary":1812,"next":1813,"nextTitle":1814},"理解隐式\u002F显式类型转换，分清整数除法与浮点除法，避免取整与精度错误。",[1746,1750,1751,1752,1753],"隐式","显式","整数除","取整",[1755,1760],{"title":1756,"content":1757,"codePy":1758,"codeCpp":1759},"1. 整数除以整数 = 整数（截断）","C++ 中两个整数相除结果仍是整数，直接丢弃小数部分（向零截断）。要得到小数，必须先把至少一个操作数变成浮点：乘 1.0 或强制转换 (double)。","print(5 \u002F\u002F 2)        # 2（整除）\nprint(5 \u002F 2)         # 2.5","cout \u003C\u003C 5 \u002F 2;        \u002F\u002F 2（截断）\ncout \u003C\u003C 5.0 \u002F 2;      \u002F\u002F 2.5",{"title":1761,"content":1762,"codePy":1763,"codeCpp":1764,"tip":1765},"2. 字符与整数（ASCII）互转","char 本质是 0~127 的小整数。int(ch) 得到 ASCII 码，char(n)（C++ 里 (char)n）得到对应字符。这种隐式\u002F显式转换在字符处理里天天用。","print(ord(\"A\"))      # 65\nprint(chr(97))       # a","cout \u003C\u003C (int)(\"A\");   \u002F\u002F 65\nchar c = 97; cout \u003C\u003C c; \u002F\u002F a","需要\"四舍五入\"时，对正数可用 (int)(x + 0.5)；更稳妥用 round（Python）。",[1767],{"title":1768,"prompt":1769,"analysis":1770,"codePy":1771,"codeCpp":1772,"run":1773},"例 1：求平均分（保留小数）","输入 n 个整数成绩，输出平均分，保留两位小数。","先求和再除以 n，除法前把和或 n 转成浮点。","s = sum(a); print(f\"{s \u002F n:.2f}\")","double avg = (double)s \u002F n;\ncout \u003C\u003C fixed \u003C\u003C setprecision(2) \u003C\u003C avg;","成绩 90 80 70 -> 80.00",{"choice":1775,"coding":1797},[1776,1782,1790],{"id":38,"stem":1777,"options":1778,"answer":9,"explanation":1781},"C++ 中 5 \u002F 2 的结果是？",[1779,759,890,1780],"2.5","2.0","两整数相除截断得 2。",{"id":47,"stem":1783,"options":1784,"answer":9,"explanation":1789},"想在 C++ 得到 5\u002F2 的小数结果，应写？",[1785,1786,1787,1788],"5 \u002F 2","5.0 \u002F 2","(float)5 \u002F 2 无效","5 \u002F 2.0 仍整数","让一个操作数为浮点即可。",{"id":56,"stem":1791,"options":1792,"answer":9,"explanation":1796},"ord(\"0\") 在 ASCII 中是？",[891,1793,1794,1795],"48","32","65","数字字符 \"0\" 的 ASCII 码是 48。",[1798],{"id":67,"title":1799,"prompt":1800,"inputFormat":1801,"outputFormat":1802,"samples":1803,"starterPy":1807,"starterCpp":1808,"hint":1809,"referencePy":1810,"referenceCpp":1811},"平均值","输入整数 n（1≤n≤1000）和 n 个整数（0~100），输出它们的平均值，保留两位小数。","第一行 n；第二行 n 个整数。","一行浮点数，保留两位小数。",[1804],{"input":1805,"output":1806},"3\n90 80 70","80.00","n = int(input())\na = list(map(int, input().split()))\n# 求平均值\n","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main(){\n    int n, s = 0, x; cin >> n;\n    for(int i=0;i\u003Cn;i++){ cin >> x; s += x; }\n    \u002F\u002F 输出平均值\n    return 0;\n}","用 (double)s \u002F n 转浮点，再 setprecision(2)。","n = int(input())\na = list(map(int, input().split()))\nprint(f\"{sum(a)\u002Fn:.2f}\")","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main(){\n    int n, s = 0, x; cin >> n;\n    for(int i=0;i\u003Cn;i++){ cin >> x; s += x; }\n    double avg = (double)s \u002F n;\n    cout \u003C\u003C fixed \u003C\u003C setprecision(2) \u003C\u003C avg;\n    return 0;\n}","整数相除会截断；要得小数需先转浮点。字符与整数经 ASCII 互转，是字符处理的基石。","01-A2-5","long long",{"id":1813,"title":1814,"level":1197,"module":8,"seq":838,"goal":1816,"keywords":1817,"explanation":1821,"examples":1833,"exercises":1841,"summary":1881,"next":1882,"nextTitle":1883},"知道 int 范围有限，大数用 long long（C++ 64 位整数）避免溢出。",[1814,1818,1819,1820],"溢出","范围","大数",[1822,1827],{"title":1823,"content":1824,"codePy":1825,"codeCpp":1826},"1. int 只有约 ±21 亿","32 位 int 范围约 [-2^31, 2^31-1] ≈ ±21 亿。很多\"累加、乘法、阶乘\"会悄悄超出这个范围导致溢出（结果变成负数或乱码）。","# Python 整数无上限，无需担心\nprint(10**18)","\u002F\u002F int 放不下 10^18\nlong long big = 1000000000000000000LL;",{"title":1828,"content":1829,"codePy":1830,"codeCpp":1831,"tip":1832},"2. 用 long long 并留意\"中间溢出\"","C++ 用 long long（约 ±9×10^18）。注意：两个 int 相乘时，就算结果要赋给 long long，也得先把操作数转成 long long，否则相乘那一步仍是 int 计算、已经溢出。","a = 100000 * 100000      # Python 安全\nb = 100000 * 100000      # 同上","long long r = (long long)a * b;  \u002F\u002F 先转再乘\nlong long sum = 0; for(...) sum += x; \u002F\u002F 累加用 long long","凡是结果可能超过 2e9 的累加\u002F乘法，变量与中间量都用 long long。",[1834],{"title":1835,"prompt":1836,"analysis":1837,"codePy":1838,"codeCpp":1839,"run":1840},"例 1：1 到 n 的和","输入 n（1≤n≤1e6），输出 1+2+...+n（可能很大）。","公式 n*(n+1)\u002F2，n 大时直接用 long long。","n = int(input())\nprint(n * (n + 1) \u002F\u002F 2)","long long n; cin >> n;\nlong long s = n * (n + 1) \u002F 2;\ncout \u003C\u003C s;","输入 1000000 -> 500000500000",{"choice":1842,"coding":1867},[1843,1851,1859],{"id":38,"stem":1844,"options":1845,"answer":607,"explanation":1850},"int 能安全表示的最大绝对值大约是？",[1846,1847,1848,1849],"2^31-1 ≈ 21 亿","2^63-1","10^9","2^16","32 位有符号 int 上限约 21 亿。",{"id":47,"stem":1852,"options":1853,"answer":9,"explanation":1858},"计算 100000 * 100000 并存入 long long，正确写法是？",[1854,1855,1856,1857],"long long r = a * b;","long long r = (long long)a * b;","int r = a * b;","long long r = a * (int)b;","先把操作数转 long long，避免相乘时已用 int 溢出。",{"id":56,"stem":1860,"options":1861,"answer":9,"explanation":1866},"20! 是否超过 int 范围？",[1862,1863,1864,1865],"没有","远超","正好","不确定","20! ≈ 2.4e18，远超 int。",[1868],{"id":67,"title":1869,"prompt":1870,"inputFormat":1801,"outputFormat":1871,"samples":1872,"starterPy":1876,"starterCpp":1877,"hint":1878,"referencePy":1879,"referenceCpp":1880},"累加求和","输入 n（1≤n≤1e6）和 n 个非负整数（每个 ≤1e6），输出它们的总和。","一行：总和（用 long long 范围）。",[1873],{"input":1874,"output":1875},"3\n1000000 1000000 1000000","3000000","n = int(input())\na = list(map(int, input().split()))\n# 求和\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, x; cin >> n;\n    long long s = 0;\n    for(int i=0;i\u003Cn;i++){ cin >> x; s += x; }\n    \u002F\u002F 输出\n    return 0;\n}","累加变量用 long long。","n = int(input())\na = list(map(int, input().split()))\nprint(sum(a))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, x; cin >> n;\n    long long s = 0;\n    for(int i=0;i\u003Cn;i++){ cin >> x; s += x; }\n    cout \u003C\u003C s; return 0;\n}","int 仅约 ±21 亿；累加、乘法、阶乘易溢出时用 long long，且要把操作数先转 long long 防中间溢出。","01-A2-6","浮点数基础",{"id":1882,"title":1883,"level":1197,"module":8,"seq":936,"goal":1885,"keywords":1886,"explanation":1890,"examples":1902,"exercises":1910,"summary":1949,"next":1950,"nextTitle":1951},"理解浮点数的二进制近似表示与精度坑（相等比较、累积误差），会用 EPS 比较。",[1887,1888,1889,939],"浮点数","精度","EPS",[1891,1896],{"title":1892,"content":1893,"codePy":1894,"codeCpp":1895},"1. 浮点数是\"近似\"存储的","float\u002Fdouble 用二进制近似表示小数，很多十进制小数（如 0.1）无法精确表示，于是 0.1 + 0.2 不等于 0.3。直接写 a == b 几乎总会出问题。","print(0.1 + 0.2 == 0.3)   # False\na = round(0.1 + 0.2, 9)","cout \u003C\u003C (0.1 + 0.2 == 0.3); \u002F\u002F 0（false）",{"title":1897,"content":1898,"codePy":1899,"codeCpp":1900,"tip":1901},"2. 用 EPS 做\"近似相等\"比较","比较浮点数要用\"差的绝对值小于一个小量 EPS（如 1e-9）\"。涉及排序\u002F判等都要这么做，避免精度误差导致逻辑错误。","def eq(a, b, eps=1e-9):\n    return abs(a - b) \u003C eps","bool eq(double a, double b){ return fabs(a-b) \u003C 1e-9; }","输出保留小数用 printf(\"%.2f\") \u002F Python f\"{x:.2f}\"；不要用 == 直接比较两个浮点。",[1903],{"title":1904,"prompt":1905,"analysis":1906,"codePy":1907,"codeCpp":1908,"run":1909},"例 1：浮点相等判断","输入两个浮点数 a,b，若它们相差小于 1e-9 视为相等，输出 \"EQ\" 否则 \"NEQ\"。","用 fabs(a-b) \u003C 1e-9 判断。","import math\na, b = map(float, input().split())\nprint(\"EQ\" if abs(a-b) \u003C 1e-9 else \"NEQ\")","double a, b; cin >> a >> b;\ncout \u003C\u003C (fabs(a-b) \u003C 1e-9 ? \"EQ\" : \"NEQ\");","输入 0.1 0.1 -> EQ",{"choice":1911,"coding":1931},[1912,1920,1924],{"id":38,"stem":1913,"options":1914,"answer":9,"explanation":1919},"比较两个浮点数是否相等，正确做法是？",[1915,1916,1917,1918],"a == b","abs(a-b) \u003C 1e-9","a \u003C b","round(a)==round(b)","用 EPS 容忍精度误差。",{"id":47,"stem":1921,"options":1922,"answer":9,"explanation":1923},"0.1 + 0.2 == 0.3 的结果通常是？",[819,966,892,567],"二进制近似导致不相等。",{"id":56,"stem":1925,"options":1926,"answer":9,"explanation":1930},"float 与 double 的主要区别是？",[1819,1927,1928,1929],"精度（有效数字位数）","是否带符号","能否取模","double 有效数字更多、精度更高。",[1932],{"id":67,"title":1933,"prompt":1934,"inputFormat":1935,"outputFormat":1936,"samples":1937,"starterPy":1944,"starterCpp":1945,"hint":1946,"referencePy":1947,"referenceCpp":1948},"近似相等","输入两个整数 p q（1≤p,q≤1000），把它们看作分数 p\u002Fq，判断 p\u002Fq 与 0.5 是否近似相等（差 \u003C 1e-9）。相等输出 \"HALF\" 否则 \"NO\"。","一行两个整数 p q。","一行 \"HALF\" 或 \"NO\"。",[1938,1941],{"input":1939,"output":1940},"1 2","HALF",{"input":1942,"output":1943},"1 3","NO","p, q = map(int, input().split())\n# 比较 p\u002Fq 与 0.5\n","#include \u003Ciostream>\n#include \u003Ccmath>\nusing namespace std;\nint main(){\n    int p, q; cin >> p >> q;\n    \u002F\u002F 比较 p\u002Fq 与 0.5\n    return 0;\n}","用 fabs((double)p\u002Fq - 0.5) \u003C 1e-9。","p, q = map(int, input().split())\nprint(\"HALF\" if abs(p\u002Fq - 0.5) \u003C 1e-9 else \"NO\")","#include \u003Ciostream>\n#include \u003Ccmath>\nusing namespace std;\nint main(){\n    int p, q; cin >> p >> q;\n    bool half = fabs((double)p\u002Fq - 0.5) \u003C 1e-9;\n    cout \u003C\u003C (half ? \"HALF\" : \"NO\");\n    return 0;\n}","浮点数以二进制近似存小数，不能直接 == 比较；用 EPS（如 1e-9）判断近似相等，输出保留位数用格式化。","01-A2-7","字符与 ASCII",{"id":1950,"title":1951,"level":1197,"module":8,"seq":998,"goal":1953,"keywords":1954,"explanation":1959,"examples":1971,"exercises":1979,"summary":2015,"next":2016,"nextTitle":2017},"理解字符在计算机中以 ASCII 码存储，掌握字符与整数的互换及常用字符区间。",[1955,1956,1957,1958],"ASCII","字符","编码","大小写",[1960,1965],{"title":1961,"content":1962,"codePy":1963,"codeCpp":1964},"1. 字符本质是小整数","char 在内存里就是 0~127 的整数（ASCII 码）。'A'=65、'a'=97、'0'=48、空格=32。把字符当整数处理，就能做很多统计与变换。","print(ord(\"A\"))   # 65\nprint(chr(97))    # a","cout \u003C\u003C (int)(\"A\");  \u002F\u002F 65\nchar c = 97; cout \u003C\u003C c; \u002F\u002F a",{"title":1966,"content":1967,"codePy":1968,"codeCpp":1969,"tip":1970},"2. 常用区间与大小写转换","数字字符 \"0\"~\"9\" 是 48~57；大写 \"A\"~\"Z\" 是 65~90；小写 \"a\"~\"z\" 是 97~122。大小写相差 32：小写转大写 ch-32，大写转小写 ch+32（或直接用 isalpha\u002Fislower\u002Ftoupper 等库函数）。","if \"a\" \u003C= ch \u003C= \"z\": up = chr(ord(ch) - 32)","if (ch >= \"a\" && ch \u003C= \"z\") ch = ch - 32;","判断字母\u002F数字优先用 ctype 函数（isalpha\u002Fisdigit），可读性更好。",[1972],{"title":1973,"prompt":1974,"analysis":1975,"codePy":1976,"codeCpp":1977,"run":1978},"例 1：大写转小写","输入一个大写字母，输出对应的小写字母。","大写 + 32 即为小写。","ch = input().strip()\nprint(chr(ord(ch) + 32))","char ch; cin >> ch;\nch = ch + 32;\ncout \u003C\u003C ch;","输入 A -> a",{"choice":1980,"coding":2000},[1981,1987,1992],{"id":38,"stem":1982,"options":1983,"answer":9,"explanation":1986},"\"a\" - \"A\" 在 ASCII 中是？",[1984,1794,1795,1985],"26","97","小写比大写大 32。",{"id":47,"stem":1988,"options":1989,"answer":9,"explanation":1991},"int(\"0\") 的 ASCII 码是？",[891,1793,1794,1990],"57","数字字符 \"0\" 为 48。",{"id":56,"stem":1993,"options":1994,"answer":703,"explanation":1999},"判断 ch 是大写字母，应写？",[1995,1996,1997,1998],"65\u003C=ch\u003C=90","\"A\"\u003C=ch\u003C=\"Z\"","isupper(ch)","以上都行","三种等价，推荐 isupper 最可读。",[2001],{"id":67,"title":2002,"prompt":2003,"inputFormat":2004,"outputFormat":2005,"samples":2006,"starterPy":2010,"starterCpp":2011,"hint":2012,"referencePy":2013,"referenceCpp":2014},"大小写转换","输入一个字符串（仅含英文字母，长度 ≤ 100），把其中所有大写转小写、小写转大写，输出结果。","一行字符串。","一行转换后的字符串。",[2007],{"input":2008,"output":2009},"AbC","aBc","s = input().strip()\n# 大小写互换\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; cin >> s;\n    \u002F\u002F 互换\n    return 0;\n}","遍历每个字符，是上则+32、是下则-32。","s = input().strip()\nout = []\nfor ch in s:\n    if \"a\" \u003C= ch \u003C= \"z\": out.append(chr(ord(ch)-32))\n    elif \"A\" \u003C= ch \u003C= \"Z\": out.append(chr(ord(ch)+32))\n    else: out.append(ch)\nprint(\"\".join(out))","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; cin >> s;\n    for(char &ch : s){\n        if (ch >= \"A\" && ch \u003C= \"Z\") ch += 32;\n        else if (ch >= \"a\" && ch \u003C= \"z\") ch -= 32;\n    }\n    cout \u003C\u003C s; return 0;\n}","字符以 ASCII 码（0~127 整数）存储；掌握 \"0\"=48、\"A\"=65、\"a\"=97 与大小写差 32，配合 ctype 函数做字符处理。","01-A2-8","函数",{"id":2016,"title":2017,"level":1197,"module":8,"seq":1066,"goal":2019,"keywords":2020,"explanation":2024,"examples":2036,"exercises":2044,"summary":2082,"next":2083,"nextTitle":2084},"能把一段功能封装成函数，理解定义、调用、返回值，实现\"一次编写、多处复用\"。",[2017,2021,2022,2023],"封装","返回值","调用",[2025,2030],{"title":2026,"content":2027,"codePy":2028,"codeCpp":2029},"1. 函数 = 带名字的一段可复用代码","函数把一组操作打包，起个名字，需要的时候\"调用\"它。这样同一段逻辑只写一次，主程序更清晰，也便于测试。","def gcd(a, b):\n    while b:\n        a, b = b, a % b\n    return a","int gcd(int a, int b){\n    while (b) { int t = a % b; a = b; b = t; }\n    return a;\n}",{"title":2031,"content":2032,"codePy":2033,"codeCpp":2034,"tip":2035},"2. 返回值与 void","函数可以用 return 把结果交回调用处；不返回时用 void（Python 里默认返回 None）。调用函数要按参数顺序传入\"实参\"。","def is_prime(n):\n    if n \u003C 2: return False\n    for i in range(2, int(n**0.5)+1):\n        if n % i == 0: return False\n    return True","bool is_prime(int n){\n    if (n \u003C 2) return false;\n    for (int i=2;i*i\u003C=n;i++) if (n%i==0) return false;\n    return true;\n}","遇到\"重复出现的逻辑\"或\"一块独立子任务\"，就应当抽成函数。",[2037],{"title":2038,"prompt":2039,"analysis":2040,"codePy":2041,"codeCpp":2042,"run":2043},"例 1：判断素数","写一个函数 is_prime(n)，主程序读入 n 并打印 \"YES\"\u002F\"NO\"。","2 以下不是素数；从 2 试除到 √n。","def is_prime(n):\n    if n \u003C 2: return False\n    for i in range(2, int(n**0.5)+1):\n        if n % i == 0: return False\n    return True\nn = int(input())\nprint(\"YES\" if is_prime(n) else \"NO\")","bool is_prime(int n){\n    if (n \u003C 2) return false;\n    for (int i=2;i*i\u003C=n;i++) if (n%i==0) return false;\n    return true;\n}\nint main(){ int n; cin>>n; cout\u003C\u003C(is_prime(n)?\"YES\":\"NO\"); }","输入 17 -> YES",{"choice":2045,"coding":2068},[2046,2054,2062],{"id":38,"stem":2047,"options":2048,"answer":9,"explanation":2053},"函数不需要返回值时，C++ 应声明为？",[2049,2050,2051,2052],"int","void","bool","auto","void 表示无返回值。",{"id":47,"stem":2055,"options":2056,"answer":9,"explanation":2061},"关于 return，正确的是？",[2057,2058,2059,2060],"可返回多个值用逗号","return 后函数立即结束","必须写在末尾","void 也能 return 值","return 会立即结束函数。",{"id":56,"stem":2063,"options":2064,"answer":9,"explanation":2067},"调用 fun(3, 5) 时，3 和 5 称为？",[2065,2066,1339,1340],"形参","实参","调用时传入的是实参。",[2069],{"id":67,"title":2070,"prompt":2071,"inputFormat":2072,"outputFormat":2073,"samples":2074,"starterPy":2077,"starterCpp":2078,"hint":2079,"referencePy":2080,"referenceCpp":2081},"最大公约数","写一个函数 gcd(a,b) 返回 a,b 的最大公约数；主程序读入 a,b 并输出结果。","一行两个整数 a b（1≤a,b≤1e9）。","一行：最大公约数。",[2075],{"input":2076,"output":624},"12 18","def gcd(a, b):\n    # 实现\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nint gcd(int a, int b){\n    \u002F\u002F 实现\n}\nint main(){ int a,b; cin>>a>>b; cout\u003C\u003Cgcd(a,b); }","辗转相除法：while(b) a,b = b,a%b。","def gcd(a, b):\n    while b:\n        a, b = b, a % b\n    return a\nprint(gcd(*map(int, input().split())))","#include \u003Ciostream>\nusing namespace std;\nint gcd(int a, int b){ while(b){int t=a%b;a=b;b=t;} return a; }\nint main(){ int a,b; cin>>a>>b; cout\u003C\u003Cgcd(a,b); }","函数把可复用逻辑打包；用 return 返回结果，无返回用 void；把重复子任务抽成函数是结构化编程的第一步。","01-A2-9","参数传递",{"id":2083,"title":2084,"level":1197,"module":8,"seq":1131,"goal":2086,"keywords":2087,"explanation":2089,"examples":2101,"exercises":2109,"summary":2143,"next":1267,"nextTitle":1268},"理解 C++ 的\"值传递\"——函数收到的是实参的副本，修改形参不影响外部；并知道如何把结果交回。",[2084,2088,2065,2066],"值传递",[2090,2095],{"title":2091,"content":2092,"codePy":2093,"codeCpp":2094},"1. 值传递：形参是实参的\"拷贝\"","C++ 默认按值传递：调用时把实参的值复制一份给形参。函数里改形参，只是改了这份拷贝，外面的实参不会改变。","# Python 也类似（不可变对象）\ndef add_one(x):\n    x = x + 1\n    return x","void add_one(int x){ x = x + 1; }  \u002F\u002F 外面的变量不变",{"title":2096,"content":2097,"codePy":2098,"codeCpp":2099,"tip":2100},"2. 想\"交回结果\"就用 return","要拿函数算出的新值，最干净的办法是 return 它，再在调用处接收。想让函数\"就地修改\"外部变量，则需要引用或指针（下一节）。","def add(a, b):\n    return a + b\nr = add(3, 4)","int add(int a, int b){ return a + b; }\nint r = add(3, 4);","需要多个结果时，可 return 一个结构体\u002F元组，或用引用参数（见 01-A2-10）。",[2102],{"title":2103,"prompt":2104,"analysis":2105,"codePy":2106,"codeCpp":2107,"run":2108},"例 1：值传递无效 swap","尝试用值传递写一个 swap(a,b)，你会发现外部变量没有被交换。","形参只是拷贝，函数内交换不影响实参。","def swap(a, b):\n    a, b = b, a\na, b = 1, 2\nswap(a, b)\nprint(a, b)   # 仍是 1 2","void swap(int a, int b){ int t=a; a=b; b=t; }\nint main(){ int a=1,b=2; swap(a,b); cout\u003C\u003Ca\u003C\u003C\" \"\u003C\u003Cb; } \u002F\u002F 1 2","1 2（未交换）",{"choice":2110,"coding":2131},[2111,2118,2123],{"id":38,"stem":2112,"options":2113,"answer":63,"explanation":2117},"C++ 默认参数传递方式是？",[2114,2115,2088,2116],"引用传递","指针传递","共享传递","默认按值传递，形参是拷贝。",{"id":47,"stem":2119,"options":2120,"answer":9,"explanation":2122},"在 \"void f(int x){x++;}\" 调用后，实参？",[1308,1307,2121,1309],"变两倍","形参是副本，外部不变。",{"id":56,"stem":2124,"options":2125,"answer":9,"explanation":2130},"函数想把计算结果交给调用处，最通用的方式是？",[2126,2127,2128,2129],"改全局变量","用 return","改形参","打印出来","return 把结果交回。",[2132],{"id":67,"title":2133,"prompt":2134,"inputFormat":1323,"outputFormat":2135,"samples":2136,"starterPy":2138,"starterCpp":2139,"hint":2140,"referencePy":2141,"referenceCpp":2142},"两数之和","写一个函数 add(a,b) 返回 a+b；主程序读入 a,b 并输出和。","一行：a+b。",[2137],{"input":1256,"output":885},"def add(a, b):\n    # 返回 a+b\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nint add(int a, int b){\n    \u002F\u002F 返回 a+b\n}\nint main(){ int a,b; cin>>a>>b; cout\u003C\u003Cadd(a,b); }","直接 return a + b；用 long long 防溢出。","def add(a, b):\n    return a + b\na, b = map(int, input().split())\nprint(add(a, b))","#include \u003Ciostream>\nusing namespace std;\nlong long add(long long a, long long b){ return a + b; }\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Cadd(a,b); }","C++ 默认值传递，形参是实参的拷贝，函数内改形参不影响外部；要交回结果用 return，要就地修改用引用\u002F指针。",{"problems":2145,"id":2146,"title":2147,"goal":2148,"explanation":2149,"examples":2165,"summary":2173,"exercises":2174,"next":2217,"level":7,"module":2218,"seq":9,"keywords":2219,"nextTitle":2220},[],"02-A1-1","什么是算法","理解“算法”的定义与基本特征，能区分“会做”和“有步骤、可重复地做”。",[2150,2155,2160],{"title":2151,"content":2152,"codePy":2153,"codeCpp":2154},"算法的定义","算法是解决某一类问题的“明确、有限、可执行的步骤集合”。做菜有菜谱、出行有路线，程序里的算法就是把问题变成一步步能交给计算机执行的指令。","# 算法示例：求最大值\n# 步骤1: 假设第一个最大\n# 步骤2: 逐个比较更新\n# 步骤3: 输出结果","\u002F\u002F 算法=明确有限的步骤\n\u002F\u002F 1.设max=第一个\n\u002F\u002F 2.逐个比较\n\u002F\u002F 3.输出max",{"title":2156,"content":2157,"codePy":2158,"codeCpp":2159},"好算法的标准","① 正确性：结果对；② 可读性：人能看懂；③ 健壮性：对异常输入不崩；④ 高效性：省时间省空间（复杂度低）。初学先求“正确且清晰”。","# 同一问题可有多解，比较优劣\ndef max1(a):  # 逐位比\n    m = a[0]\n    for x in a[1:]:\n        if x > m: m = x\n    return m","\u002F\u002F 不同写法效率不同\n\u002F\u002F 好的算法更省时\u002F省空间",{"title":2161,"content":2162,"codePy":2163,"codeCpp":2164},"算法 vs 程序","算法是“思想\u002F步骤”，程序是算法的具体实现（用某门语言写出来）。同一个算法可以用 C++ 或 Python 实现。","# 算法(思想) -> 程序(实现)","\u002F\u002F 算法是思路，程序是落地",[2166],{"title":2167,"prompt":2168,"analysis":2169,"codePy":2170,"codeCpp":2171,"run":2172},"用算法思想描述“找最大”","给定一组数，用文字+代码描述一个求最大值的算法步骤。","算法强调“步骤明确有限”：先取第一个为当前最大，再依次比较、遇大则更新，最后输出。","def find_max(a):\n    m = a[0]\n    for x in a:\n        if x > m: m = x\n    return m","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a[] = {3, 9, 2, 7};\n    int m = a[0];\n    for (int i = 1; i \u003C 4; i++)\n        if (a[i] > m) m = a[i];\n    cout \u003C\u003C m;  \u002F\u002F 9\n    return 0;\n}","输出: 9","算法是解决问题的明确、有限、可执行的步骤。评价算法看正确性、可读性、健壮性、高效性。算法是思想，程序是它在某种语言下的实现。",{"choice":2175,"coding":2203},[2176,2185,2194],{"id":2177,"stem":2178,"options":2179,"answer":9,"explanation":2184},"02-A1-1-c1","下列说法最符合“算法”定义的是？",[2180,2181,2182,2183],"一段能运行的代码","解决某类问题的明确、有限、可执行步骤","一个数学公式","一台计算机","算法强调步骤明确、有限、可执行。",{"id":2186,"stem":2187,"options":2188,"answer":9,"explanation":2193},"02-A1-1-c2","评价算法时，“高效性”通常指？",[2189,2190,2191,2192],"代码行数少","省时间与空间（复杂度低）","变量名短","注释多","高效即时间\u002F空间复杂度低。",{"id":2195,"stem":2196,"options":2197,"answer":9,"explanation":2202},"02-A1-1-c3","同一个算法，用 C++ 和 Python 分别实现，这说明了？",[2198,2199,2200,2201],"算法就是程序","算法是思想，程序是其实现","两者不可能一样","Python 没有算法","算法是思路，程序是落地实现，与语言无关。",[2204],{"id":2205,"title":2206,"prompt":2207,"inputFormat":2208,"outputFormat":2209,"samples":2210,"starterPy":357,"starterCpp":2213,"referencePy":2214,"referenceCpp":2215,"hint":2216},"02-A1-1-p1","求和算法","用“算法步骤+代码”实现：求一组数的和。要求代码清晰、有注释说明每一步。","一行若干整数","它们的和",[2211],{"input":2212,"output":883},"1 2 3 4","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int x, s = 0;\n    while (cin >> x) s += x;\n    cout \u003C\u003C s;\n    return 0;\n}","nums = list(map(int, input().split()))\ns = 0\nfor x in nums:   # 逐个累加\n    s += x\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int x, s = 0;\n    while (cin >> x) s += x;  \u002F\u002F 边读边加\n    cout \u003C\u003C s;\n    return 0;\n}","算法步骤：初始化和为 0，逐个读入并累加，最后输出。","02-A1-2","复杂度与算法分析",[],"程序执行过程",{"problems":2222,"id":2217,"title":2220,"goal":2223,"explanation":2224,"examples":2240,"summary":2248,"exercises":2249,"next":2292,"level":7,"module":2218,"seq":63,"keywords":2293,"nextTitle":2294},[],"理解代码从编写到运行的流程，以及“语句逐条执行、函数调用、输入输出”在计算机中的基本过程。",[2225,2230,2235],{"title":2226,"content":2227,"codePy":2228,"codeCpp":2229},"从源码到运行","源代码（.cpp\u002F.py）经过编译\u002F解释变成计算机能懂的指令；运行时从 main\u002F首条语句开始，由 CPU 逐条执行，遇输入就读、遇输出就写、遇调用就跳进去再回来。","# Python 由解释器逐行执行\nprint(\"start\")   # 解释器读到这行才执行","\u002F\u002F C++ 先编译成可执行文件\n\u002F\u002F 再由 CPU 从 main 开始执行",{"title":2231,"content":2232,"codePy":2233,"codeCpp":2234},"顺序与跳转","默认顺序执行；遇到 if\u002F循环\u002F函数调用会发生“跳转”。函数调用时会保存现场、跳到函数体执行、返回结果后继续。","def f():\n    return 10\nprint(f())   # 跳进 f 再回来","int f() { return 10; }\nint main() { cout \u003C\u003C f(); }",{"title":2236,"content":2237,"codePy":2238,"codeCpp":2239},"时间与步骤数","每执行一条基本语句都消耗一点时间。一个问题若要做 n 步，耗时就大致随 n 增长——这正是“复杂度”要衡量的，下节展开。","for i in range(n):  # 做 n 步\n    work()","for (int i=0;i\u003Cn;i++) work(); \u002F\u002F n 步",[2241],{"title":2242,"prompt":2243,"analysis":2244,"codePy":2245,"codeCpp":2246,"run":2247},"手演执行过程","对下面程序，按执行顺序写出各变量在每步后的值：s=0; i=1; 当 i\u003C=3 时 s+=i, i++。","把程序“当 CPU”一步步走：i=1→s=1；i=2→s=3；i=3→s=6；i=4 退出。理解执行过程是分析复杂度的前提。","s = 0\nfor i in range(1, 4):\n    s += i\nprint(s)   # 6","int s = 0;\nfor (int i = 1; i \u003C= 3; i++) s += i;\ncout \u003C\u003C s;  \u002F\u002F 6","输出: 6（过程 i:1->s:1, i:2->s:3, i:3->s:6）","程序运行时从入口逐条执行，遇分支\u002F循环\u002F函数调用发生跳转。每条基本操作都耗时间，操作次数越多耗时越长——这是理解“复杂度”的基础。",{"choice":2250,"coding":2278},[2251,2260,2269],{"id":2252,"stem":2253,"options":2254,"answer":9,"explanation":2259},"02-A1-2-c1","C++ 程序通常从哪个函数开始执行？",[2255,2256,2257,2258],"start()","main()","first()","任意函数","C++ 程序入口是 main 函数。",{"id":2261,"stem":2262,"options":2263,"answer":9,"explanation":2268},"02-A1-2-c2","函数调用时会发生什么？",[2264,2265,2266,2267],"直接结束程序","保存现场跳入函数体，返回后继续","什么都不发生","变成循环","调用会跳转执行函数体，返回后继续原流程。",{"id":2270,"stem":2271,"options":2272,"answer":9,"explanation":2277},"02-A1-2-c3","“执行 n 步”与耗时关系，引出哪个概念？",[2273,2274,2275,2276],"变量类型","时间复杂度","注释规范","编译原理","操作次数随 n 增长的关系就是时间复杂度。",[2279],{"id":2280,"title":2281,"prompt":2282,"inputFormat":2283,"outputFormat":2284,"samples":2285,"starterPy":357,"starterCpp":2288,"referencePy":2289,"referenceCpp":2290,"hint":2291},"02-A1-2-p1","模拟计数器","写一个程序：从 1 数到 n，每数一个数就输出当前累计和，最后输出总和。","一行一个整数 n","多行：每行当前累计和，最后总和",[2286],{"input":890,"output":2287},"1\n3\n6\n6","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n; cin >> n;\n    int s = 0;\n    for (int i = 1; i \u003C= n; i++) {\n        s += i;\n        \u002F\u002F 输出当前累计和\n    }\n    return 0;\n}","n = int(input())\ns = 0\nfor i in range(1, n + 1):\n    s += i\n    print(s)\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n; cin >> n;\n    int s = 0;\n    for (int i = 1; i \u003C= n; i++) {\n        s += i;\n        cout \u003C\u003C s \u003C\u003C endl;\n    }\n    cout \u003C\u003C s;\n    return 0;\n}","每累加一次就输出一次 s，循环结束再输出总和。","02-A1-3",[],"基础时间复杂度",{"problems":2296,"id":2292,"title":2294,"goal":2297,"explanation":2298,"examples":2314,"summary":2322,"exercises":2323,"next":2362,"level":7,"module":2218,"seq":703,"keywords":2363,"nextTitle":2364},[],"建立“用操作次数随输入规模 n 的增长趋势来衡量算法快慢”的初步概念（大 O 思想）。",[2299,2304,2309],{"title":2300,"content":2301,"codePy":2302,"codeCpp":2303},"为什么看趋势","同一算法在不同机器上跑快慢不同，但“输入越大、耗时怎么变”的趋势是客观的。复杂度关心的就是这种“随 n 增长的快慢趋势”，而非具体秒数。","# n=10 可能 0.01s，n=10000 可能 1s\n# 关注“数量级趋势”而非绝对时间","\u002F\u002F 同一算法：n 越大耗时越长\n\u002F\u002F 关注增长趋势",{"title":2305,"content":2306,"codePy":2307,"codeCpp":2308},"大 O 记号","用 O(...) 表示“最坏\u002F上限的增长趋势”。O(1) 不随 n 变，O(n) 随 n 线性变，O(n²) 随 n 平方变。只保留“增长最快的项”，常数忽略。","# O(n): 做 n 次\ndef f(n):\n    for i in range(n):\n        pass","\u002F\u002F O(n): 循环 n 次\nfor (int i=0;i\u003Cn;i++) {}",{"title":2310,"content":2311,"codePy":2312,"codeCpp":2313},"怎么数操作次数","看循环嵌套层数：一层循环 O(n)，两层嵌套 O(n²)。初学只要能分辨“常数、线性、平方”这几档即可。","# 两层循环 -> O(n^2)\nfor i in range(n):\n    for j in range(n):\n        pass","\u002F\u002F 两层嵌套 -> O(n^2)\nfor(int i=0;i\u003Cn;i++)\n  for(int j=0;j\u003Cn;j++){}",[2315],{"title":2316,"prompt":2317,"analysis":2318,"codePy":2319,"codeCpp":2320,"run":2321},"比较两段求和","A 段用循环累加 1~n；B 段直接用公式 n*(n+1)\u002F\u002F2。分别是什么复杂度？","A 循环 n 次→O(n)；B 只做一步→O(1)。当 n 很大时，O(1) 远快于 O(n)，这就是复杂度差异。","# A: O(n)\ns=0\nfor i in range(1,n+1): s+=i\n# B: O(1)\ns=n*(n+1)\u002F\u002F2","\u002F\u002F A: O(n)\nint s=0; for(int i=1;i\u003C=n;i++) s+=i;\n\u002F\u002F B: O(1)\nint s=n*(n+1)\u002F2;","同样结果，但 B 任意 n 都只一步","复杂度用“操作数随 n 的增长趋势”衡量算法快慢，记法为 O(...)。常见档位：O(1) 常数、O(n) 线性、O(n²) 平方。看循环层数与“增长最快的项”即可粗判。",{"choice":2324,"coding":2347},[2325,2334,2341],{"id":2326,"stem":2327,"options":2328,"answer":9,"explanation":2333},"02-A1-3-c1","复杂度关注的核心是？",[2329,2330,2331,2332],"在某台机器上的具体秒数","操作数随输入规模 n 的增长趋势","代码行数","变量个数","复杂度是趋势，独立于具体硬件。",{"id":2335,"stem":2336,"options":2337,"answer":9,"explanation":2340},"02-A1-3-c2","一段只做固定几步、与 n 无关的代码，复杂度是？",[2338,1245,2339,1246],"O(n)","O(n²)","常数步数为 O(1)。",{"id":2342,"stem":2343,"options":2344,"answer":63,"explanation":2346},"02-A1-3-c3","两层嵌套循环（各 n 次）通常是？",[1245,2338,2339,2345],"O(n³)","n×n = n² 次操作。",[2348],{"id":2349,"title":2350,"prompt":2351,"inputFormat":2352,"outputFormat":2353,"samples":2354,"starterPy":2357,"starterCpp":2358,"referencePy":2359,"referenceCpp":2360,"hint":2361},"02-A1-3-p1","看代码说复杂度","给定代码：遍历数组每个元素，并在内部再遍历一次统计配对。请判断整体复杂度并说明理由（用 O 记号回答即可）。","无输入","输出复杂度记号 O(?)",[2355],{"input":357,"output":2356},"O(n^2)","def f(a):\n    n = len(a)\n    for i in range(n):\n        for j in range(n):\n            pass","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a[100], n = 100;\n    for (int i = 0; i \u003C n; i++)\n        for (int j = 0; j \u003C n; j++)\n            a[i] += a[j];\n    return 0;\n}","def f(a):\n    n = len(a)\n    for i in range(n):\n        for j in range(n):\n            pass\n# 两层各 n 次 -> O(n^2)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a[100], n = 100;\n    for (int i = 0; i \u003C n; i++)\n        for (int j = 0; j \u003C n; j++)\n            a[i] += a[j];\n    \u002F\u002F 两层各 n 次 -> O(n^2)\n    return 0;\n}","外层 n 次、内层 n 次，共 n×n。","02-A1-4",[],"常数复杂度 O(1)",{"problems":2366,"id":2362,"title":2364,"goal":2367,"explanation":2368,"examples":2384,"summary":2392,"exercises":2393,"next":2436,"level":7,"module":2218,"seq":770,"keywords":2437,"nextTitle":2438},[],"理解 O(1)：无论输入多大，操作次数都固定不变，是最高效的复杂度。",[2369,2374,2379],{"title":2370,"content":2371,"codePy":2372,"codeCpp":2373},"O(1) 的含义","O(1) 表示“执行时间不随 n 变化”，只做常数步。例如数组按下标取元素、做一次加减、判断一个条件，都是 O(1)。","a = [1,2,3]\nprint(a[0])      # O(1)：直接定位\nx = 5\ns = x + 1        # O(1)","int a[3] = {1,2,3};\ncout \u003C\u003C a[0];     \u002F\u002F O(1)\nint s = x + 1;    \u002F\u002F O(1)",{"title":2375,"content":2376,"codePy":2377,"codeCpp":2378},"为什么数组随机访问是 O(1)","数组在内存中连续存放，知道起始地址和下标，就能一步算到目标位置（地址+下标×大小），无需逐个找。","# 不论数组多长，a[k] 都是一步\nprint(a[k])","\u002F\u002F a[k] 直接计算地址，O(1)",{"title":2380,"content":2381,"codePy":2382,"codeCpp":2383},"O(1) 的价值","再大的数据量，O(1) 操作也几乎瞬时。设计算法时，把“查找\u002F更新”尽量做成 O(1) 是性能优化的方向。","d = {\"a\": 1}\nd[\"a\"] = 2      # 字典赋值 O(1) 平均","\u002F\u002F 哈希表平均 O(1) 查找\u002F插入",[2385],{"title":2386,"prompt":2387,"analysis":2388,"codePy":2389,"codeCpp":2390,"run":2391},"交换两个数","输入 a、b，交换并输出。无论 a、b 是什么值，操作步数都固定——这是什么复杂度？","交换只用到固定几步（temp=a;a=b;b=temp），与数据规模无关，所以是 O(1)。","a, b = map(int, input().split())\na, b = b, a\nprint(a, b)        # O(1)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b, t; cin >> a >> b;\n    t = a; a = b; b = t;\n    cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b;  \u002F\u002F O(1)\n    return 0;\n}","输入: 3 5\n输出: 5 3","O(1) 表示操作次数固定、不随 n 增长，是最高效的复杂度。数组按下标访问、一次赋值\u002F判断等都是 O(1)。追求算法高效时，应尽量把核心操作设计为 O(1)。",{"choice":2394,"coding":2422},[2395,2404,2413],{"id":2396,"stem":2397,"options":2398,"answer":9,"explanation":2403},"02-A1-4-c1","下列哪种操作通常是 O(1)？",[2399,2400,2401,2402],"遍历长度为 n 的数组","按下标访问数组元素","对数组排序","两层循环","下标访问直接计算地址，常数时间。",{"id":2405,"stem":2406,"options":2407,"answer":9,"explanation":2412},"02-A1-4-c2","O(1) 的意思是？",[2408,2409,2410,2411],"只运行 1 毫秒","操作步数不随 n 变化","只有一个变量","不能用循环","O(1) 是步数恒定，与规模无关。",{"id":2414,"stem":2415,"options":2416,"answer":63,"explanation":2421},"02-A1-4-c3","数组 a 长度为 1 万，访问 a[5000] 的耗时与访问 a[1] 相比？",[2417,2418,2419,2420],"明显更慢","明显更快","基本相同（都是 O(1)）","无法确定","下标访问都是一步定位，与位置无关。",[2423],{"id":2424,"title":2425,"prompt":2426,"inputFormat":2427,"outputFormat":2428,"samples":2429,"starterPy":357,"starterCpp":2432,"referencePy":2433,"referenceCpp":2434,"hint":2435},"02-A1-4-p1","最后元素","输入 n 个整数，输出最后一个（用下标直接访问，体现 O(1)）。","一行 n 个整数","最后一个整数",[2430],{"input":2431,"output":290},"1 2 3 4 5","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> a;\n    int x; while (cin >> x) a.push_back(x);\n    \u002F\u002F 输出最后一个\n    return 0;\n}","a = list(map(int, input().split()))\nprint(a[-1])","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> a;\n    int x; while (cin >> x) a.push_back(x);\n    cout \u003C\u003C a[a.size() - 1];  \u002F\u002F 下标直接访问 O(1)\n    return 0;\n}","最后一个下标 = 长度-1，一步访问。","02-A1-5",[],"线性复杂度 O(n)",{"problems":2440,"id":2436,"title":2438,"goal":2441,"explanation":2442,"examples":2458,"summary":2466,"exercises":2467,"next":2500,"level":7,"module":2218,"seq":838,"keywords":2501,"nextTitle":2502},[],"理解 O(n)：操作次数随输入规模 n 成正比增长，常见于“遍历一遍”的算法。",[2443,2448,2453],{"title":2444,"content":2445,"codePy":2446,"codeCpp":2447},"O(n) 的含义","O(n) 表示要做大约 n 步，n 翻倍耗时也大致翻倍。最典型的就是“把数组从头到尾扫一遍”：找最大值、求和、计数都常是 O(n)。","def sum(a):\n    s = 0\n    for x in a:   # 每个元素一次 -> O(n)\n        s += x\n    return s","\u002F\u002F 单层循环遍历 -> O(n)\nfor (int i = 0; i \u003C n; i++) {}",{"title":2449,"content":2450,"codePy":2451,"codeCpp":2452},"为什么遍历是 O(n)","每个元素都要处理一次，共 n 个元素，所以总步数约 n。即使循环里做了两三步常数操作，仍记作 O(n)（常数被忽略）。","for x in a:\n    y = x * 2   # 常数步\n    print(y)","for (int i=0;i\u003Cn;i++){\n    int y = a[i]*2;  \u002F\u002F 常数步\n}",{"title":2454,"content":2455,"codePy":2456,"codeCpp":2457},"O(n) 已是很好的复杂度","只要问题本身需要“看遍每个数据”，O(n) 就是下限（不可能比看一遍更少）。比 O(n²) 高效得多，是日常算法的基础档位。","# 找最大必须看每个 -> 下限 O(n)\nm = max(a)","\u002F\u002F 找最大至少看每个元素一次 -> O(n)",[2459],{"title":2460,"prompt":2461,"analysis":2462,"codePy":2463,"codeCpp":2464,"run":2465},"找最大值","输入 n 个整数，输出最大值（要求用一次遍历实现，复杂度 O(n)）。","必须看遍每个数才能确定最大，所以下限就是 O(n)；一次遍历即可完成，是线性复杂度的典型例子。","a = list(map(int, input().split()))\nm = a[0]\nfor x in a:\n    if x > m: m = x\nprint(m)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> a; int x;\n    while (cin >> x) a.push_back(x);\n    int m = a[0];\n    for (int i = 1; i \u003C a.size(); i++)\n        if (a[i] > m) m = a[i];\n    cout \u003C\u003C m;\n    return 0;\n}","输入: 3 9 2 7\n输出: 9","O(n) 表示操作数随 n 线性增长，典型于“遍历一遍数据”（求和、计数、找最值）。只要问题需要看遍每个元素，O(n) 就是最优下限，远比 O(n²) 高效。",{"choice":2468,"coding":2487},[2469,2474,2482],{"id":2470,"stem":2471,"options":2472,"answer":9,"explanation":2473},"02-A1-5-c1","“把长度为 n 的数组从头到尾处理一遍”通常是？",[1245,2338,2339,1246],"每个元素一次，共 n 次。",{"id":2475,"stem":2476,"options":2477,"answer":9,"explanation":2481},"02-A1-5-c2","若 O(n) 算法 n 从 1000 变为 2000，耗时大致？",[1307,2478,2479,2480],"翻倍","变 4 倍","减半","线性：n 翻倍耗时约翻倍。",{"id":2483,"stem":2484,"options":2485,"answer":9,"explanation":2486},"02-A1-5-c3","找数组最大值的最优复杂度下限是？",[1245,2338,2339,1246],"必须看每个元素至少一次。",[2488],{"id":2489,"title":2490,"prompt":2491,"inputFormat":2427,"outputFormat":2492,"samples":2493,"starterPy":357,"starterCpp":2496,"referencePy":2497,"referenceCpp":2498,"hint":2499},"02-A1-5-p1","计数正数","输入 n 个整数，输出其中正数的个数（一次遍历，O(n)）。","一个整数（正数个数）",[2494],{"input":2495,"output":890},"1 -2 3 -4 5","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int x, cnt = 0;\n    while (cin >> x)\n        if (x > 0) cnt++;\n    cout \u003C\u003C cnt;\n    return 0;\n}","a = list(map(int, input().split()))\ncnt = 0\nfor x in a:\n    if x > 0: cnt += 1\nprint(cnt)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int x, cnt = 0;\n    while (cin >> x) if (x > 0) cnt++;\n    cout \u003C\u003C cnt;\n    return 0;\n}","边读边判断，正则 cnt++，只遍历一次。","03-A1-1",[],"基础模拟",{"id":2504,"title":1246,"level":2505,"module":2218,"seq":9,"goal":2506,"keywords":2507,"explanation":2511,"examples":2523,"exercises":2531,"summary":2562,"next":2563,"nextTitle":2564},"02-A2-1","Lv.5","理解 O(log n) 时间复杂度：每次操作把问题规模减半（如二分查找），所需步数约为 log₂n，增长极慢。",[2508,2509,2510,1246],"对数复杂度","二分","规模减半",[2512,2517],{"title":2513,"content":2514,"codePy":2515,"codeCpp":2516},"1. 对数增长有多慢","n=1000 只需约 10 步（2^10=1024），n=10^6 约 20 步。步数等于把 n 不断除以 2 直到 1 的次数，即约 log₂n。","def steps(n):\n    s = 0; r = n\n    while r > 1:\n        r \u002F\u002F= 2; s += 1\n    return s","int steps(int n){ int s=0; while(n>1){ n\u002F=2; s++; } return s; }",{"title":2518,"content":2519,"codePy":2520,"codeCpp":2521,"tip":2522},"2. 哪些算法是 O(log n)","二分查找、二叉搜索树查找、倍增（求 2^k、LCA）等都属此类，共同点是每一步排除一半规模。","# 倍增：从大到小尝试 2^k\nk = 1\nwhile k * 2 \u003C= n: k *= 2","\u002F\u002F 倍增：k=1; while(k*2\u003C=n) k*=2;","看到\"每次规模减半\"，立刻想到 O(log n)。",[2524],{"title":2525,"prompt":2526,"analysis":2527,"codePy":2528,"codeCpp":2529,"run":2530},"例 1：二分查找的步数","二分查找一个长度为 1e6 的有序数组，最多约需多少步？","2^20≈1e6，所以约 20 步。","print(steps(1000000))","cout \u003C\u003C steps(1000000);","约 20",{"choice":2532,"coding":2550},[2533,2537,2545],{"id":38,"stem":2534,"options":2535,"answer":9,"explanation":2536},"二分查找的时间复杂度是？",[2338,1246,2339,1245],"每次砍掉一半区间。",{"id":47,"stem":2538,"options":2539,"answer":9,"explanation":2544},"O(log n) 的含义最接近？",[2540,2541,2542,2543],"规模线性增长","每步规模减半","指数增长","无增长","步数随 n 呈对数。",{"id":56,"stem":2546,"options":2547,"answer":607,"explanation":2549},"n=1024 时 log₂n 约为？",[883,816,2548,892],"1024","2^10=1024。",[2551],{"id":67,"title":2552,"prompt":2553,"inputFormat":1456,"outputFormat":2554,"samples":2555,"starterPy":2558,"starterCpp":2559,"hint":2560,"referencePy":2561,"referenceCpp":2559},"二分步数","输入 n（1≤n≤10^9），输出二分查找至多需要的比较步数（把 n 不断除以 2 直到 ≤1 的次数）。","一行整数步数。",[2556,2557],{"input":148,"output":890},{"input":892,"output":891},"n = int(input())\ns = 0\n# 计算步数\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; int s=0; while(n>1){ n\u002F=2; s++; } cout\u003C\u003Cs; }","循环 while(n>1): n\u002F=2; s++。","n = int(input())\ns = 0\nwhile n > 1:\n    n \u002F\u002F= 2; s += 1\nprint(s)","O(log n) 表示每步把规模减半，步数约 log₂n，增长极慢；二分查找、倍增等都属此类。","02-A2-2","O(n log n)",{"id":2566,"title":2567,"level":2505,"module":2218,"seq":89,"goal":2568,"keywords":2569,"explanation":2573,"examples":2585,"exercises":2592,"summary":2620,"next":2621,"nextTitle":2622},"02-A2-10","嵌套循环复杂度","掌握分析嵌套循环复杂度：内外层循环次数相乘，注意\"与 n 无关\"的内层与\"随外层变化\"的内层。",[632,2570,2571,2572],"乘法","循环分析","复杂度",[2574,2579],{"title":2575,"content":2576,"codePy":2577,"codeCpp":2578},"1. 内外相乘","外层 n、内层 m，总量 n×m；若内层是常数 c，则整体 O(n)。","for i in range(n):\n    for j in range(m):   # 共 n*m\n        pass","\u002F\u002F 外层 n 内层 m => O(n*m)",{"title":2580,"content":2581,"codePy":2582,"codeCpp":2583,"tip":2584},"2. 注意边界依赖","内层若是外层变量的函数（如 for j in range(i)），总量是 1+2+…+n = n(n+1)\u002F2 = O(n²)。","for i in range(n):\n    for j in range(i):   # 三角和 = n(n+1)\u002F2\n        pass","\u002F\u002F j\u003Ci => 三角和 O(n^2)","内层随 i 增长常导致 O(n²)。",[2586],{"title":2587,"prompt":2588,"analysis":2589,"codePy":2590,"codeCpp":2591,"run":2338},"例 1：外层 n 内层常数","外层 n、内层恒 10 次，复杂度？","10n = O(n)。","print(\"O(n)\")","\u002F\u002F O(n)",{"choice":2593,"coding":2609},[2594,2597,2601],{"id":38,"stem":2588,"options":2595,"answer":607,"explanation":2596},[2338,2339,1245,1246],"10n 线性。",{"id":47,"stem":2598,"options":2599,"answer":9,"explanation":2600},"外层 n、内层 for j in range(i)，复杂度？",[2338,2339,1246,2345],"三角和 O(n²)。",{"id":56,"stem":2602,"options":2603,"answer":9,"explanation":2608},"嵌套循环总量一般等于？",[2604,2605,2606,2607],"内外层次数相加","内外层次数相乘","取最大","取最小","乘法原理。",[2610],{"id":67,"title":2611,"prompt":2612,"inputFormat":1456,"outputFormat":2613,"samples":2614,"starterPy":2616,"starterCpp":2617,"hint":2618,"referencePy":2619,"referenceCpp":2617},"线性计数","输入 n（1≤n≤100000），模拟\"外层 n 次、内层恒 10 次\"的总操作数，输出 n×10。","一行整数 n×10。",[2615],{"input":290,"output":987},"n = int(input())\n# 外层 n 内层 10\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long n; cin>>n; cout\u003C\u003Cn*10; }","输出 n*10。","n = int(input())\nprint(n * 10)","嵌套循环总量 = 各层次数相乘；内层为常数则 O(n)，内层随 i 增长（如 j\u003Ci）则常是 O(n²)。","04-A2-1","前缀和",{"id":2563,"title":2564,"level":2505,"module":2218,"seq":63,"goal":2624,"keywords":2625,"explanation":2630,"examples":2642,"exercises":2649,"summary":2679,"next":2680,"nextTitle":2339},"理解 O(n log n)：线性乘对数，是归并、快排这类基于比较的排序能达到的最优复杂度。",[2626,2627,2628,2629],"n log n","归并排序","快速排序","排序下界",[2631,2636],{"title":2632,"content":2633,"codePy":2634,"codeCpp":2635},"1. 怎么来的","归并排序递归树高 log n 层，每层合并工作量 O(n)，总计 O(n log n)。","# 递归深度 log n，每层处理 n 个元素","\u002F\u002F T(n)=2T(n\u002F2)+O(n) => O(n log n)",{"title":2637,"content":2638,"codePy":2639,"codeCpp":2640,"tip":2641},"2. 比 O(n²) 快很多","n=10^5 时 n log n≈1.7e6，而 n²=1e10；O(n log n) 是基于比较的排序的理论最优。","import math\nn = 100000\nprint(n * math.log2(n))","\u002F\u002F n=1e5: nlogn~1.7e6 远小于 n^2=1e10","需要排序大规模数据，优先选 O(n log n) 算法。",[2643],{"title":2644,"prompt":2645,"analysis":2646,"codePy":2647,"codeCpp":2648,"run":890},"例 1：归并排序层数","对 n=8 的数组做归并排序，递归层数约为？","log₂8=3 层。","import math\nprint(int(math.log2(8)))","cout \u003C\u003C 3;",{"choice":2650,"coding":2667},[2651,2655,2663],{"id":38,"stem":2652,"options":2653,"answer":9,"explanation":2654},"归并排序的时间复杂度？",[2338,2564,2339,1246],"分治每层 O(n)，共 log n 层。",{"id":47,"stem":2656,"options":2657,"answer":9,"explanation":2662},"n=10^5 时哪个更大？",[2658,2659,2660,2661],"n log n 与 n² 差不多","n² 远大于 n log n","n log n 更大","一样","1e10 远大于 1.7e6。",{"id":56,"stem":2664,"options":2665,"answer":9,"explanation":2666},"基于比较的排序理论最优是？",[2338,2564,2339,1245],"比较排序下界 O(n log n)。",[2668],{"id":67,"title":2669,"prompt":2670,"inputFormat":1456,"outputFormat":2671,"samples":2672,"starterPy":2675,"starterCpp":2676,"hint":2677,"referencePy":2678,"referenceCpp":2676},"递归层数","输入 n（1≤n≤10^9），输出归并\u002F快排递归的层数（把 n 不断除以 2 直到 ≤1 的次数，即 ⌈log₂n⌉）。","一行整数层数。",[2673,2674],{"input":148,"output":890},{"input":892,"output":891},"import math\nn = int(input())\n# 计算层数\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long n; cin>>n; int s=0; while(n>1){ n\u002F=2; s++; } cout\u003C\u003Cs; }","同二分步数：while(n>1) n\u002F=2。","import math\nn = int(input())\ns = 0\nwhile n > 1:\n    n \u002F\u002F= 2; s += 1\nprint(s)","O(n log n) = 线性规模乘对数深度，是归并、快排这类分治排序的复杂度，也是基于比较的排序的理论下界。","02-A2-3",{"id":2680,"title":2339,"level":2505,"module":2218,"seq":703,"goal":2682,"keywords":2683,"explanation":2688,"examples":2700,"exercises":2708,"summary":2737,"next":2738,"nextTitle":2739},"理解 O(n²)：双重循环的典型复杂度，常见于朴素排序与朴素枚举，适合 n 较小的场景。",[2684,2685,2686,2687],"平方复杂度","双重循环","朴素算法","冒泡",[2689,2694],{"title":2690,"content":2691,"codePy":2692,"codeCpp":2693},"1. 双层循环","外层 n 次、内层平均 n\u002F2 次，总量约 n²\u002F2，记 O(n²)。","cnt = 0\nfor i in range(n):\n    for j in range(n):\n        cnt += 1  # 约 n^2 次","\u002F\u002F 两层 for 各 n 次 => O(n^2)",{"title":2695,"content":2696,"codePy":2697,"codeCpp":2698,"tip":2699},"2. 何时可接受","n≤几千时 O(n²) 没问题；n=10^5 时 1e10 次会超时，需要换成 O(n log n) 或更低。","# n=3000 时 n^2=9e6 可接受；n=1e5 超时","\u002F\u002F n 大时避免朴素 O(n^2)","看到三层及以上嵌套循环要警惕超时。",[2701],{"title":2702,"prompt":2703,"analysis":2704,"codePy":2705,"codeCpp":2706,"run":2707},"例 1：双重循环次数","n=3 的双重循环执行多少次？","3×3=9 次。","print(3*3)","cout \u003C\u003C 9;","9",{"choice":2709,"coding":2726},[2710,2714,2722],{"id":38,"stem":2711,"options":2712,"answer":9,"explanation":2713},"两层各 n 次的循环复杂度？",[2338,2339,1246,2345],"n×n=n²。",{"id":47,"stem":2715,"options":2716,"answer":9,"explanation":2721},"n=10^5 时 O(n²) 大约多少次？",[2717,2718,2719,2720],"1e5","1e10","1e3","1e8","10^5 的平方是 10^10。",{"id":56,"stem":2723,"options":2724,"answer":9,"explanation":2725},"冒泡排序复杂度？",[2338,2339,2564,1245],"双层比较交换。",[2727],{"id":67,"title":2728,"prompt":2729,"inputFormat":1456,"outputFormat":2730,"samples":2731,"starterPy":2733,"starterCpp":2734,"hint":2735,"referencePy":2736,"referenceCpp":2734},"平方计数","输入 n（1≤n≤5000），输出 n×n（即双重循环的总执行次数）。","一行整数 n²。",[2732],{"input":890,"output":2707},"n = int(input())\n# 输出 n*n\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long n; cin>>n; cout\u003C\u003Cn*n; }","直接输出 n*n。","n = int(input())\nprint(n * n)","O(n²) 来自双层（或嵌套）循环，约 n²\u002F2 次操作；适合 n 较小（几千内），大规模需优化为 O(n log n) 或更低。","02-A2-4","多项式复杂度",{"id":2738,"title":2739,"level":2505,"module":2218,"seq":770,"goal":2741,"keywords":2742,"explanation":2745,"examples":2757,"exercises":2764,"summary":2790,"next":2791,"nextTitle":2792},"理解多项式复杂度 O(n^k)：运行时间是关于 n 的多项式，次数 k 越低越快。",[2743,2338,2356,2744],"多项式","O(n^3)",[2746,2751],{"title":2747,"content":2748,"codePy":2749,"codeCpp":2750},"1. 多项式家族","O(1)＜O(log n)＜O(n)＜O(n log n)＜O(n²)＜O(n³)… 都是 n 的多项式，随 n 增长可控。","# O(n): 单层循环；O(n^2): 双层；O(n^3): 三层","\u002F\u002F 多项式次数越低越好",{"title":2752,"content":2753,"codePy":2754,"codeCpp":2755,"tip":2756},"2. 如何判断次数","看嵌套循环层数（常量系数忽略）。三层循环一般是 O(n³)。","for i in range(n):\n    for j in range(n):\n        for k in range(n):\n            pass  # O(n^3)","\u002F\u002F 三层 for => O(n^3)","复杂度只关心最高次项与 n 的关系。",[2758],{"title":2759,"prompt":2760,"analysis":2761,"codePy":2762,"codeCpp":2763,"run":2744},"例 1：三层嵌套","三层各 n 次的嵌套循环复杂度？","n×n×n = n³，即 O(n³)。","print(\"O(n^3)\")","\u002F\u002F O(n^3)",{"choice":2765,"coding":2778},[2766,2770,2774],{"id":38,"stem":2767,"options":2768,"answer":9,"explanation":2769},"单层循环（n 次）的复杂度？",[1245,2338,2339,1246],"线性 O(n)。",{"id":47,"stem":2771,"options":2772,"answer":63,"explanation":2773},"三层各 n 次的循环复杂度？",[2338,2339,2345,1246],"n 的三次方。",{"id":56,"stem":2775,"options":2776,"answer":703,"explanation":2777},"下面最慢的是？",[2338,2564,2339,2345],"次数越高越慢。",[2779],{"id":67,"title":2780,"prompt":2781,"inputFormat":1456,"outputFormat":2782,"samples":2783,"starterPy":2786,"starterCpp":2787,"hint":2788,"referencePy":2789,"referenceCpp":2787},"立方计数","输入 n（1≤n≤200），输出 n³。","一行整数 n³。",[2784],{"input":883,"output":2785},"1000","n = int(input())\n# 输出 n^3\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long n; cin>>n; cout\u003C\u003Cn*n*n; }","输出 n*n*n（用 long long 防溢出）。","n = int(input())\nprint(n ** 3)","多项式复杂度 O(n^k) 是关于 n 的多项式，次数 k 越低越优；单层 O(n)、双层 O(n²)、三层 O(n³)。","02-A2-5","指数复杂度",{"id":2791,"title":2792,"level":2505,"module":2218,"seq":838,"goal":2794,"keywords":2795,"explanation":2799,"examples":2811,"exercises":2818,"summary":2853,"next":2854,"nextTitle":2855},"理解指数复杂度 O(2^n) 与 O(c^n)：随 n 爆炸增长，常见于暴力枚举\u002F回溯，需要剪枝或换算法。",[2792,2796,2797,2798],"回溯","暴力","爆炸增长",[2800,2805],{"title":2801,"content":2802,"codePy":2803,"codeCpp":2804},"1. 指数增长多恐怖","n=20 时 2^20≈1e6 还行；n=40 时 2^40≈1e12 已超时。每个决策点\"选\u002F不选\"导致分支翻倍。","# 子集枚举：每个元素选或不选 => 2^n 种\nfor mask in range(1 \u003C\u003C n):\n    pass","\u002F\u002F 枚举子集：2^n 种，n 大必爆",{"title":2806,"content":2807,"codePy":2808,"codeCpp":2809,"tip":2810},"2. 对策","用记忆化\u002F动态规划把重复子问题合并（把指数降为多项式），或剪枝。","from functools import lru_cache\n@lru_cache(None)\ndef f(n):\n    ...  # 记忆化避免重复","\u002F\u002F 记忆化 \u002F DP 降低复杂度","看到 2^n、n! 要立刻警惕超时。",[2812],{"title":2813,"prompt":2814,"analysis":2815,"codePy":2816,"codeCpp":2817,"run":148},"例 1：子集总数","枚举 n 个元素的子集共有几种？","每个元素选或不选，共 2^n 种。","n=3; print(1 \u003C\u003C n)","\u002F\u002F 2^3 = 8",{"choice":2819,"coding":2841},[2820,2825,2833],{"id":38,"stem":2821,"options":2822,"answer":9,"explanation":2824},"枚举 n 个元素的子集，复杂度？",[2338,2823,2339,1246],"O(2^n)","共 2^n 个子集。",{"id":47,"stem":2826,"options":2827,"answer":63,"explanation":2832},"n=40 时 2^n 大约？",[2828,2829,2830,2831],"40","1e6","1e12","40!","2^40≈1.1e12。",{"id":56,"stem":2834,"options":2835,"answer":9,"explanation":2840},"降低指数复杂度的常用手段？",[2836,2837,2838,2839],"加循环","记忆化\u002FDP","用数组","减少变量","合并重复子问题。",[2842],{"id":67,"title":2843,"prompt":2844,"inputFormat":1456,"outputFormat":2845,"samples":2846,"starterPy":2849,"starterCpp":2850,"hint":2851,"referencePy":2852,"referenceCpp":2850},"子集总数","输入 n（0≤n≤30），输出 n 个元素的子集总数 2^n。","一行整数 2^n。",[2847,2848],{"input":890,"output":148},{"input":891,"output":892},"n = int(input())\n# 输出 2^n\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; long long ans=1\u003C\u003Cn; cout\u003C\u003Cans; }","用 1\u003C\u003Cn（n≤30 在 long long 内）。","n = int(input())\nprint(1 \u003C\u003C n)","指数复杂度 O(2^n) 随 n 爆炸增长，常见于暴力\u002F回溯；n 稍大就超时，应改用 DP、记忆化或剪枝。","02-A2-6","空间复杂度",{"id":2854,"title":2855,"level":2505,"module":2218,"seq":936,"goal":2857,"keywords":2858,"explanation":2861,"examples":2873,"exercises":2880,"summary":2905,"next":2906,"nextTitle":2907},"区分时间复杂度与空间复杂度：空间复杂度衡量算法额外占用的内存随 n 的增长。",[2855,2859,1434,2860],"额外内存","递归栈",[2862,2867],{"title":2863,"content":2864,"codePy":2865,"codeCpp":2866},"1. 常见空间","用长度为 n 的数组 → O(n)；两层 n×n 矩阵 → O(n²)；递归深度 n → 栈空间 O(n)。","a = [0] * n      # O(n) 空间\nb = [[0]*n for _ in range(n)]  # O(n^2)","int a[N];       \u002F\u002F O(n)\nint b[N][N];    \u002F\u002F O(n^2)",{"title":2868,"content":2869,"codePy":2870,"codeCpp":2871,"tip":2872},"2. 原地算法","若只用常量额外变量（如冒泡排序在原数组上交换），空间 O(1)。","# 原地排序，额外 O(1)\nfor i in range(n):\n    ...","\u002F\u002F 原地：O(1) 额外空间","空间不够时要优先用 O(1) 或 O(n) 的做法。",[2874],{"title":2875,"prompt":2876,"analysis":2877,"codePy":2878,"codeCpp":2879,"run":1245},"例 1：求和的空间","求 n 个数的和，额外数组空间是？","边读边加，只用一个累加变量，O(1)。","s=0\nfor x in a: s+=x","\u002F\u002F O(1) 额外空间",{"choice":2881,"coding":2894},[2882,2886,2890],{"id":38,"stem":2883,"options":2884,"answer":9,"explanation":2885},"使用长度为 n 的数组，空间复杂度？",[1245,2338,2339,1246],"一维数组 O(n)。",{"id":47,"stem":2887,"options":2888,"answer":9,"explanation":2889},"冒泡排序（原地交换）的空间？",[2338,1245,2339,1246],"只用常量额外变量。",{"id":56,"stem":2891,"options":2892,"answer":9,"explanation":2893},"递归深度 n 的空间？",[1245,2338,2339,1246],"调用栈 O(深度)。",[2895],{"id":67,"title":2896,"prompt":2897,"inputFormat":1801,"outputFormat":2898,"samples":2899,"starterPy":2902,"starterCpp":2903,"hint":2904,"referencePy":1879,"referenceCpp":2903},"常数空间求和","输入 n（1≤n≤10^6）和 n 个整数，输出它们的和（只用 O(1) 额外空间）。","一行和。",[2900],{"input":2901,"output":883},"4\n1 2 3 4","n = int(input())\na = list(map(int, input().split()))\ns = 0\n# 累加\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; long long s=0,x; while(n--){ cin>>x; s+=x; } cout\u003C\u003Cs; }","边读边加，不存整个数组也可。","空间复杂度衡量额外内存：O(1) 常量、O(n) 一维数组、O(n²) 矩阵、递归 O(深度)；原地算法尽量 O(1)。","02-A2-7","最坏情况",{"id":2906,"title":2907,"level":2505,"module":2218,"seq":998,"goal":2909,"keywords":2910,"explanation":2913,"examples":2925,"exercises":2930,"summary":2964,"next":2965,"nextTitle":2966},"理解最坏情况复杂度：输入最不利时算法需要的资源上界，是可靠性的保证。",[2907,2911,2912,1069],"上界","保证",[2914,2919],{"title":2915,"content":2916,"codePy":2917,"codeCpp":2918},"1. 为什么要看最坏","算法在不同输入下表现不同，最坏情况给出\"再差也不会超过\"的保证，便于系统设计。","# 顺序查找最坏：目标在末尾或不存在 => O(n)","\u002F\u002F 最坏：扫完整个数组",{"title":2920,"content":2921,"codePy":2922,"codeCpp":2923,"tip":2924},"2. 例子","快排最坏（已有序且总选末位为轴）退化为 O(n²)；平均 O(n log n)。因此常随机化选轴。","# 快排最坏退化：每次划分极不均衡","\u002F\u002F 随机化选轴避免最坏","在要求\"必须稳定不超时\"的场合，看最坏情况。",[2926],{"title":2927,"prompt":2928,"analysis":2929,"codePy":2590,"codeCpp":2591,"run":2338},"例 1：顺序查找最坏","顺序查找一个不存在的元素，复杂度？","扫完整个数组，O(n)（最坏）。",{"choice":2931,"coding":2951},[2932,2940,2944],{"id":38,"stem":2933,"options":2934,"answer":9,"explanation":2939},"最坏情况复杂度是？",[2935,2936,2937,2938],"平均表现","最不利输入的资源上界","最好表现","输入规模","保证再差也不超。",{"id":47,"stem":2941,"options":2942,"answer":9,"explanation":2943},"快排最坏复杂度？",[2564,2339,2338,1246],"已有序且轴固定时退化。",{"id":56,"stem":2945,"options":2946,"answer":9,"explanation":2950},"避免快排最坏常用？",[2947,2948,2949,2838],"增加循环","随机化选轴","减少递归","随机选轴期望 O(n log n)。",[2952],{"id":67,"title":2953,"prompt":2954,"inputFormat":2955,"outputFormat":2956,"samples":2957,"starterPy":2960,"starterCpp":2961,"hint":2962,"referencePy":2963,"referenceCpp":2961},"最坏比较次数","输入 n（1≤n≤1000）和 n 个整数与待查值 x，用顺序查找输出最坏情况下的比较次数（即扫完整个数组的次数）。","第一行 n；第二行 n 个整数；第三行 x。","一行整数 n（最坏比较次数）。",[2958],{"input":2959,"output":1663},"4\n1 2 3 4\n9","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\n# 最坏比较次数\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n,x; cin>>n; for(int i=0;i\u003Cn;i++){int t; cin>>t;} cin>>x; cout\u003C\u003Cn; }","最坏即比较 n 次。","n = int(input())\n_ = input()\nx = int(input())\nprint(n)","最坏情况复杂度是输入最不利时的资源上界，给出可靠保证；如快排最坏 O(n²)，常靠随机化避免。","02-A2-8","最好情况",{"id":2965,"title":2966,"level":2505,"module":2218,"seq":1066,"goal":2968,"keywords":2969,"explanation":2973,"examples":2985,"exercises":2990,"summary":3019,"next":3020,"nextTitle":3021},"理解最好情况复杂度：最有利输入下的资源消耗下界，常用于\"已接近目标\"的快速路径。",[2966,2970,2971,2972],"下界","已排序","快速路径",[2974,2979],{"title":2975,"content":2976,"codePy":2977,"codeCpp":2978},"1. 最好情况例子","冒泡排序遇到已排序数组（加\"无交换即停\"优化）只需 O(n) 一趟扫描；插入排序对已排序数据 O(n)。","def bubble(a):\n    for i in range(n):\n        ok = True\n        for j in range(n - 1 - i):\n            if a[j] > a[j + 1]: swap; ok = False\n        if ok: break   # 已有序，O(n)","\u002F\u002F 已有序时一趟结束 => O(n)",{"title":2980,"content":2981,"codePy":2982,"codeCpp":2983,"tip":2984},"2. 别只看好情况","最好情况不代表通常表现，要综合最坏\u002F平均来评价算法。","# 只看最好会误判；需结合平均\u002F最坏","\u002F\u002F 综合评价","产品宣传常只看最好情况，工程要综合看。",[2986],{"title":2987,"prompt":2988,"analysis":2989,"codePy":2590,"codeCpp":2591,"run":2338},"例 1：已排序的冒泡","对已升序数组做带优化的冒泡排序，最好复杂度？","一趟无交换即停，O(n)。",{"choice":2991,"coding":3008},[2992,2996,3004],{"id":38,"stem":2993,"options":2994,"answer":9,"explanation":2995},"已排序数组 + 优化冒泡的最好复杂度？",[2339,2338,1246,1245],"一趟扫描即停。",{"id":47,"stem":2997,"options":2998,"answer":63,"explanation":3003},"评价算法通常不能只看？",[2999,3000,3001,3002],"最坏","平均","最好","空间","最好不具代表性。",{"id":56,"stem":3005,"options":3006,"answer":9,"explanation":3007},"插入排序对已排序数据复杂度？",[2339,2338,2564,1245],"几乎不搬移。",[3009],{"id":67,"title":3010,"prompt":3011,"inputFormat":3012,"outputFormat":3013,"samples":3014,"starterPy":3016,"starterCpp":3017,"hint":3018,"referencePy":3016,"referenceCpp":3017},"无交换即停","输入 n（1≤n≤1000）和已升序的 n 个整数，用\"无交换即停\"的冒泡排序，输出实际发生的交换次数（已有序应为 0）。","第一行 n；第二行升序 n 个整数。","一行整数交换次数。",[3015],{"input":2901,"output":891},"n = int(input())\na = list(map(int, input().split()))\nsw = 0\nfor i in range(n):\n    ok = True\n    for j in range(n - 1 - i):\n        if a[j] > a[j + 1]:\n            a[j], a[j + 1] = a[j + 1], a[j]; sw += 1; ok = False\n    if ok: break\nprint(sw)","#include \u003Ciostream>\nusing namespace std;\nint a[1005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; int sw=0; for(int i=0;i\u003Cn;i++){ bool ok=1; for(int j=0;j\u003Cn-1-i;j++) if(a[j]>a[j+1]){ int t=a[j]; a[j]=a[j+1]; a[j+1]=t; sw++; ok=0; } if(ok) break; } cout\u003C\u003Csw; }","加\"某轮无交换则 break\"，统计交换次数。","最好情况是输入最有利时的消耗（如已排序时冒泡 O(n)）；它是快速路径，但不能单独用来评价算法。","02-A2-9","平均情况",{"id":3020,"title":3021,"level":2505,"module":2218,"seq":1131,"goal":3023,"keywords":3024,"explanation":3028,"examples":3040,"exercises":3048,"summary":3080,"next":2566,"nextTitle":2567},"理解平均情况复杂度：对所有可能输入取期望，比最坏更贴近\"典型\"表现。",[3021,3025,3026,3027],"期望","典型表现","概率",[3029,3034],{"title":3030,"content":3031,"codePy":3032,"codeCpp":3033},"1. 平均更贴近实际","如快排平均 O(n log n)，顺序查找平均比较 n\u002F2 次。平均假设输入等概率分布。","# 顺序查找平均比较次数 ≈ n\u002F2","\u002F\u002F 平均 ~ n\u002F2 次比较",{"title":3035,"content":3036,"codePy":3037,"codeCpp":3038,"tip":3039},"2. 与最坏结合","设计时要同时考虑最坏（保证）与平均（体验）。","# 既保证最坏上界，也关注平均","\u002F\u002F 平衡保证与体验","平均情况依赖输入分布假设。",[3041],{"title":3042,"prompt":3043,"analysis":3044,"codePy":3045,"codeCpp":3046,"run":3047},"例 1：平均比较","在随机顺序数组中顺序查找成功，平均比较几次？","目标等概率出现在各位置，平均 n\u002F2 次。","print(\"约 n\u002F2\")","\u002F\u002F ~n\u002F2","约 n\u002F2",{"choice":3049,"coding":3069},[3050,3057,3061],{"id":38,"stem":3051,"options":3052,"answer":9,"explanation":3056},"顺序查找成功的平均比较次数约为？",[892,3053,3054,3055],"n\u002F2","n","log n","等概率平均。",{"id":47,"stem":3058,"options":3059,"answer":9,"explanation":3060},"快排的平均复杂度？",[2339,2564,2338,1246],"平均 O(n log n)。",{"id":56,"stem":3062,"options":3063,"answer":9,"explanation":3068},"平均情况依赖？",[3064,3065,3066,3067],"编译器","输入分布假设","CPU","内存","需假设输入概率分布。",[3070],{"id":67,"title":3071,"prompt":3072,"inputFormat":1456,"outputFormat":3073,"samples":3074,"starterPy":3076,"starterCpp":3077,"hint":3078,"referencePy":3079,"referenceCpp":3077},"平均比较次数","输入 n（1≤n≤1000），输出顺序查找成功的平均比较次数（理论值，取 n\u002F2 的整数除法结果）。","一行整数 n\u002F\u002F2。",[3075],{"input":1663,"output":759},"n = int(input())\n# 平均比较次数\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; cout\u003C\u003Cn\u002F2; }","输出 n\u002F2（整数除法）。","n = int(input())\nprint(n \u002F\u002F 2)","平均情况是对所有输入取期望的复杂度，更贴近典型表现（如快排平均 O(n log n)）；应与最坏情况一起考量。",{"problems":3082,"id":2500,"title":2502,"goal":3083,"explanation":3084,"examples":3100,"summary":3108,"exercises":3109,"next":3151,"level":7,"module":3152,"seq":9,"keywords":3153,"nextTitle":3154},[],"理解“模拟”这一基础算法思想：按题目\u002F过程的原样描述，一步步把流程用代码复现出来。",[3085,3090,3095],{"title":3086,"content":3087,"codePy":3088,"codeCpp":3089},"什么是模拟","模拟就是“题目怎么说，你就怎么写”：把现实过程或题意原封不动地翻译成代码步骤。不需要巧妙数学，重点是“忠实复现 + 细心”。","# 题意：每秒钟数 +1，共 t 秒\n# 直接按描述写循环\nt = int(input())\nprint(t)        # 每秒+1，t 秒后为 t","\u002F\u002F 按题意：t 秒每秒+1 -> 结果 t\nint t; cin >> t; cout \u003C\u003C t;",{"title":3091,"content":3092,"codePy":3093,"codeCpp":3094},"模拟的关键能力","① 准确理解步骤顺序；② 用变量维护“当前状态”；③ 按条件切换分支。把过程拆成“状态+操作”即可。","state = 0\nfor step in steps:\n    if step == \"A\": state += 1\n    else: state -= 1","int state = 0;\nfor (auto step : steps) {\n    if (step == \"A\") state++;\n    else state--;\n}",{"title":3096,"content":3097,"codePy":3098,"codeCpp":3099},"易错点","模拟题最易在“边界、顺序、初始化”上出错。写完后用小样例逐帧核对状态变化，是制胜法宝。","# 拿 n=3 小样例手算对比\n# 不一致就检查顺序\u002F初始值","\u002F\u002F 小样例逐帧验证状态",[3101],{"title":3102,"prompt":3103,"analysis":3104,"codePy":3105,"codeCpp":3106,"run":3107},"计数器模拟","每秒钟计数器加 1，经过 t 秒后输出计数器的值（t 由输入给定）。","最朴素的模拟：题意即“t 次加 1”，用循环或一步计算复现。","t = int(input())\nprint(t)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int t; cin >> t; cout \u003C\u003C t; return 0;\n}","输入: 10\n输出: 10","模拟=“题目怎么说就怎么写”，把过程翻译成代码。核心是用变量维护状态、按条件操作，并注意边界与顺序。写完用小样例逐帧核对。",{"choice":3110,"coding":3137},[3111,3120,3128],{"id":3112,"stem":3113,"options":3114,"answer":9,"explanation":3119},"03-A1-1-c1","“模拟”算法的核心思想是？",[3115,3116,3117,3118],"找数学公式","按题意原样把过程翻译成代码","排序","二分查找","模拟强调忠实复现过程。",{"id":3121,"stem":3122,"options":3123,"answer":9,"explanation":3127},"03-A1-1-c2","模拟题最常在哪些地方出错？",[2273,3124,3125,3126],"边界、顺序、初始化","注释","循环语法","模拟易在边界\u002F顺序\u002F初始化出错。",{"id":3129,"stem":3130,"options":3131,"answer":63,"explanation":3136},"03-A1-1-c3","做模拟题的重要习惯是？",[3132,3133,3134,3135],"直接交","用大数测试","用小样例逐帧核对状态","随机改","小样例手算对拍能抓出模拟疏漏。",[3138],{"id":3139,"title":3140,"prompt":3141,"inputFormat":3142,"outputFormat":3143,"samples":3144,"starterPy":357,"starterCpp":3147,"referencePy":3148,"referenceCpp":3149,"hint":3150},"03-A1-1-p1","奇偶步模拟","从 0 开始，输入 t：第奇数秒 +1，第偶数秒 -1。输出 t 秒后的结果。","一行一个整数 t","一个整数",[3145,3146],{"input":1663,"output":891},{"input":890,"output":892},"#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int t; cin >> t;\n    int s = 0;\n    \u002F\u002F 模拟奇偶秒\n    return 0;\n}","t = int(input())\ns = 0\nfor i in range(1, t + 1):\n    if i % 2 == 1: s += 1\n    else: s -= 1\nprint(s)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int t; cin >> t;\n    int s = 0;\n    for (int i = 1; i \u003C= t; i++) {\n        if (i % 2 == 1) s += 1; else s -= 1;\n    }\n    cout \u003C\u003C s;\n    return 0;\n}","i 为奇数秒加、偶数秒减，逐秒模拟。","03-A1-2","模拟与枚举",[],"数值模拟",{"problems":3156,"id":3151,"title":3154,"goal":3157,"explanation":3158,"examples":3173,"summary":3181,"exercises":3182,"next":3221,"level":7,"module":3152,"seq":63,"keywords":3222,"nextTitle":3223},[],"掌握“按数值规则逐步演算”的模拟：根据给定公式\u002F规则，循环更新数值直至结束。",[3159,3164,3169],{"title":3160,"content":3161,"codePy":3162,"codeCpp":3163},"数值模拟的形态","给定初始值与更新规则（如“每次减半”“每次加固定值”），用循环反复套用规则，直到满足条件。本质是“状态随规则演化”。","x = 100\nsteps = 0\nwhile x > 1:\n    x = x \u002F\u002F 2   # 每次减半\n    steps += 1\nprint(steps)","int x = 100, steps = 0;\nwhile (x > 1) { x \u002F= 2; steps++; }\ncout \u003C\u003C steps;",{"title":3165,"content":3166,"codePy":3167,"codeCpp":3168},"维护状态变量","用一两个变量记录“当前值”和“步数\u002F答案”，每轮按规则更新它们。注意更新顺序与是否整数除法。","bal = 1000\nwhile bal >= 100:\n    bal -= 100   # 每次取 100","int bal = 1000;\nwhile (bal >= 100) bal -= 100;",{"title":1406,"content":3170,"codePy":3171,"codeCpp":3172},"数值模拟必须有清晰的“什么时候停”（如小于阈值、达到目标）。否则会死循环。","while balance > 0:\n    balance -= cost","while (balance > 0) balance -= cost;",[3174],{"title":3175,"prompt":3176,"analysis":3177,"codePy":3178,"codeCpp":3179,"run":3180},"细菌翻倍","一种细菌每小时数量翻倍，初始 1 个，输入小时数 h，输出 h 小时后的数量（用循环模拟）。","规则清晰：每小时 ×2。用循环 h 次更新当前数量，体现“数值随规则演化”的模拟。","h = int(input())\nc = 1\nfor _ in range(h):\n    c *= 2\nprint(c)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int h; cin >> h;\n    long long c = 1;\n    for (int i = 0; i \u003C h; i++) c *= 2;\n    cout \u003C\u003C c;\n    return 0;\n}","输入: 5\n输出: 32","数值模拟按给定规则循环更新数值直到结束。关键是用变量维护当前状态与步数、明确终止条件，并注意整数除法\u002F溢出等细节。",{"choice":3183,"coding":3208},[3184,3193,3201],{"id":3185,"stem":3186,"options":3187,"answer":9,"explanation":3192},"03-A1-2-c1","数值模拟中最关键的是？",[3188,3189,3190,3191],"用多少变量","明确的更新规则与终止条件","必须用递归","不用循环","规则+终止条件决定模拟能否正确结束。",{"id":3194,"stem":3195,"options":3196,"answer":9,"explanation":3200},"03-A1-2-c2","细菌每小时翻倍，模拟 h 小时应循环几次？",[3197,3198,398,3199],"h-1 次","h 次","2^h 次","每小时一次，共 h 次更新。",{"id":3202,"stem":3203,"options":3204,"answer":9,"explanation":3207},"03-A1-2-c3","数值模拟缺少终止条件会？",[3205,340,3206,342],"自动结束","结果变对","无终止条件会无限循环。",[3209],{"id":3210,"title":3211,"prompt":3212,"inputFormat":2283,"outputFormat":3213,"samples":3214,"starterPy":357,"starterCpp":3217,"referencePy":3218,"referenceCpp":3219,"hint":3220},"03-A1-2-p1","折纸厚度","一张纸厚 0.1mm，每次对折厚度翻倍。输入对折次数 n，输出最终厚度（毫米，保留 1 位小数）。","一个小数",[3215],{"input":883,"output":3216},"102.4","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main() {\n    int n; cin >> n;\n    double t = 0.1;\n    \u002F\u002F 每次翻倍\n    return 0;\n}","n = int(input())\nt = 0.1\nfor _ in range(n):\n    t *= 2\nprint(f\"{t:.1f}\")","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main() {\n    int n; cin >> n;\n    double t = 0.1;\n    for (int i = 0; i \u003C n; i++) t *= 2;\n    cout \u003C\u003C fixed \u003C\u003C setprecision(1) \u003C\u003C t;\n    return 0;\n}","厚度每次 ×2，循环 n 次。","03-A1-3",[],"过程模拟",{"problems":3225,"id":3221,"title":3223,"goal":3226,"explanation":3227,"examples":3243,"summary":3251,"exercises":3252,"next":3293,"level":7,"module":3152,"seq":703,"keywords":3294,"nextTitle":3295},[],"掌握“按步骤\u002F阶段推进”的过程模拟：用状态机思想，依输入或规则在不同阶段间切换。",[3228,3233,3238],{"title":3229,"content":3230,"codePy":3231,"codeCpp":3232},"过程模拟=状态演化","很多过程由“一连串阶段”组成（如红绿灯、自动售货机）。用变量记录当前处于哪一阶段（状态），再根据输入转移到下一阶段。","state = \"待机\"   # 状态\nif cmd == \"投币\": state = \"已投币\"\nif cmd == \"出货\": state = \"待机\"","string state = \"idle\";\nif (cmd == \"coin\") state = \"paid\";\nif (cmd == \"out\") state = \"idle\";",{"title":3234,"content":3235,"codePy":3236,"codeCpp":3237},"用变量跟踪阶段","把“当前在做什么”存进变量，每收到一个输入就按规则改变它。这样复杂的多步流程就被拆成“状态+转移”。","for cmd in cmds:\n    if state == \"A\" and cmd == \"x\": state = \"B\"","for (cmd : cmds) {\n    if (state==\"A\" && cmd==\"x\") state=\"B\";\n}",{"title":3239,"content":3240,"codePy":3241,"codeCpp":3242},"典型例子","如“自动门”：传感器检测到人→开门→延时→关门。把“开\u002F关\u002F延时”作为状态，按事件转移即可模拟。","# 状态: closed\u002Fopen\n# 事件: 有人\u002F无人\n# 转移规则写成 if","\u002F\u002F 状态机: closed\u002Fopen + 转移规则",[3244],{"title":3245,"prompt":3246,"analysis":3247,"codePy":3248,"codeCpp":3249,"run":3250},"自动门模拟","输入一串事件（1=有人经过，0=无人），模拟自动门：初始关闭；遇到 1 则开门（输出 OPEN），连续累计无人达到 2 次则关门（输出 CLOSE）。","用状态变量 door（开\u002F关）和无人计数 noCnt 维护阶段；每来一个事件按规则转移状态，这正是过程模拟的状态机思路。","evs = list(map(int, input().split()))\ndoor = 0   # 0关 1开\nno = 0\nfor e in evs:\n    if e == 1:\n        if door == 0: print(\"OPEN\"); door = 1\n        no = 0\n    else:\n        no += 1\n        if door == 1 and no >= 2:\n            print(\"CLOSE\"); door = 0; no = 0","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> evs; int e; while (cin >> e) evs.push_back(e);\n    int door = 0, no = 0;\n    for (int e : evs) {\n        if (e == 1) { if (!door) { cout \u003C\u003C \"OPEN\\n\"; door = 1; } no = 0; }\n        else { no++; if (door && no >= 2) { cout \u003C\u003C \"CLOSE\\n\"; door = 0; no = 0; } }\n    }\n    return 0;\n}","输入: 1 0 0 1 0 0\n输出: OPEN\nCLOSE\nOPEN\nCLOSE","过程模拟用“状态+转移”复现多阶段流程：用变量记录当前阶段，每个输入\u002F事件按规则切换状态。把复杂流程拆成状态机，逻辑清晰且不易乱。",{"choice":3253,"coding":3278},[3254,3261,3269],{"id":3255,"stem":3256,"options":3257,"answer":9,"explanation":3260},"03-A1-3-c1","过程模拟常用什么思想组织代码？",[3117,3258,2509,3259],"状态机（状态+转移）","哈希","用状态变量+转移规则描述阶段。",{"id":3262,"stem":3263,"options":3264,"answer":9,"explanation":3268},"03-A1-3-c2","自动门“开门后无人两秒关门”，需要额外维护什么？",[3265,3266,3267,775],"随机数","无人计时\u002F计数","数组长度","需要计数\u002F计时“连续无人”以触发关门。",{"id":3270,"stem":3271,"options":3272,"answer":9,"explanation":3277},"03-A1-3-c3","状态机中“转移规则”的作用是？",[3273,3274,3275,3276],"决定程序名字","根据输入改变当前状态","排序数据","无关紧要","转移规则定义状态如何随输入变化。",[3279],{"id":3280,"title":3281,"prompt":3282,"inputFormat":3283,"outputFormat":3284,"samples":3285,"starterPy":357,"starterCpp":3289,"referencePy":3290,"referenceCpp":3291,"hint":3292},"03-A1-3-p1","红绿灯","输入一串时刻事件：每个数字是“剩余秒数”，模拟红绿灯循环 红(3秒)->绿(3秒)->黄(1秒)->红... 初始为红。输出每次“切换”时的灯色（只输出发生的切换）。","一行若干整数（各阶段秒数）","切换时的灯色序列",[3286],{"input":3287,"output":3288},"3 3 1 3","绿\n黄\n红\n绿","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> t; int x; while (cin >> x) t.push_back(x);\n    \u002F\u002F 状态: R\u002FG\u002FY 循环\n    return 0;\n}","t = list(map(int, input().split()))\nlight = \"R\"\norder = {\"R\":\"G\",\"G\":\"Y\",\"Y\":\"R\"}\nfor sec in t:\n    for _ in range(sec):\n        pass\n    light = order[light]\n    print(light)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> t; int x; while (cin >> x) t.push_back(x);\n    char light = 'R';\n    for (int sec : t) {\n        \u002F\u002F 经过 sec 秒后切换到下一灯\n        light = (light == 'R') ? 'G' : (light == 'G' ? 'Y' : 'R');\n        cout \u003C\u003C light \u003C\u003C endl;\n    }\n    return 0;\n}","每经过给定秒数就切换到下一灯色，循环 R→G→Y→R。","03-A1-4",[],"条件模拟",{"problems":3297,"id":3293,"title":3295,"goal":3298,"explanation":3299,"examples":3315,"summary":3323,"exercises":3324,"next":3365,"level":7,"module":3152,"seq":770,"keywords":3366,"nextTitle":3367},[],"掌握“带分支判定的模拟”：在模拟过程中根据条件选择不同操作（if\u002F多分支），忠实复现含规则的流程。",[3300,3305,3310],{"title":3301,"content":3302,"codePy":3303,"codeCpp":3304},"模拟中嵌入判断","真实过程往往“视情况而定”：满足条件 A 这样做，否则那样做。把 if\u002Felse 嵌进模拟循环，就能复现带规则的流程。","for step in steps:\n    if cond(step): do_a()\n    else: do_b()","for (step : steps) {\n    if (cond(step)) doA();\n    else doB();\n}",{"title":3306,"content":3307,"codePy":3308,"codeCpp":3309},"先判条件再行动","每一步先判断“现在该走哪条分支”，再执行对应操作。条件可能来自输入值、当前状态或计数。","for x in data:\n    if x > 0: pos += x\n    elif x \u003C 0: neg += x\n    else: zero += 1","for (x : data) {\n    if (x > 0) pos += x;\n    else if (x \u003C 0) neg += x;\n    else zero++;\n}",{"title":3311,"content":3312,"codePy":3313,"codeCpp":3314},"与纯模拟的区别","基础模拟是“无脑照做”；条件模拟是“看条件再做”。同一套输入，不同条件分支产生不同演化路径。","# 条件决定走向","\u002F\u002F 条件分支决定走向",[3316],{"title":3317,"prompt":3318,"analysis":3319,"codePy":3320,"codeCpp":3321,"run":3322},"账单模拟","输入若干笔金额（正为收入、负为支出），模拟账户：收入则余额加、支出则余额减，并在余额首次低于 0 时输出 \"OVERDRAFT\"。","每一步先判断正负（条件），再决定加或减；同时维护余额状态，体现“条件模拟=分支+状态更新”。","amts = list(map(int, input().split()))\nbal = 0\nflag = False\nfor a in amts:\n    bal += a\n    if bal \u003C 0 and not flag:\n        print(\"OVERDRAFT\"); flag = True\nprint(bal)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main() {\n    vector\u003Cint> a; int x; while (cin >> x) a.push_back(x);\n    int bal = 0; bool flag = false;\n    for (int v : a) {\n        bal += v;\n        if (bal \u003C 0 && !flag) { cout \u003C\u003C \"OVERDRAFT\\n\"; flag = true; }\n    }\n    cout \u003C\u003C bal;\n    return 0;\n}","输入: 10 -15 5\n输出: OVERDRAFT\n0","条件模拟在模拟循环中嵌入 if\u002F多分支：先判条件、再决定操作，并维护状态。它是“基础模拟 + 分支判断”的结合，用于复现带规则的真实流程。",{"choice":3325,"coding":3350},[3326,3332,3341],{"id":3327,"stem":3328,"options":3329,"answer":9,"explanation":3331},"03-A1-4-c1","条件模拟相比基础模拟，多了什么？",[192,3330,1434,2017],"按条件选择不同分支","条件模拟依据条件走不同分支。",{"id":3333,"stem":3334,"options":3335,"answer":9,"explanation":3340},"03-A1-4-c2","“收入加、支出减”的模拟，每步要先判断？",[3336,3337,3338,3339],"金额是否为 0","金额正负","金额大小","无需判断","按正负决定加或减。",{"id":3342,"stem":3343,"options":3344,"answer":9,"explanation":3349},"03-A1-4-c3","条件模拟中维护“余额首次低于0”标记，目的是？",[3345,3346,3347,3348],"好看","避免重复触发同一事件","加快速度","必须","用 flag 防止同一事件被多次报告。",[3351],{"id":3352,"title":3353,"prompt":3354,"inputFormat":3355,"outputFormat":3356,"samples":3357,"starterPy":357,"starterCpp":3361,"referencePy":3362,"referenceCpp":3363,"hint":3364},"03-A1-4-p1","成绩分段模拟","输入若干成绩（0~100），模拟统计：>=90 计 A、>=60 计 B、否则计 C，输出三类各出现的总次数（一行三个数）。","一行若干整数成绩","A B C 的次数",[3358],{"input":3359,"output":3360},"95 70 50 88","1 2 1","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a = 0, b = 0, c = 0, x;\n    while (cin >> x) {\n        if (x >= 90) a++;\n        else if (x >= 60) b++;\n        else c++;\n    }\n    cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b \u003C\u003C \" \" \u003C\u003C c;\n    return 0;\n}","a = b = c = 0\nfor x in map(int, input().split()):\n    if x >= 90: a += 1\n    elif x >= 60: b += 1\n    else: c += 1\nprint(a, b, c)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a = 0, b = 0, c = 0, x;\n    while (cin >> x) {\n        if (x >= 90) a++; else if (x >= 60) b++; else c++;\n    }\n    cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b \u003C\u003C \" \" \u003C\u003C c;\n    return 0;\n}","每步按分数段分支计数。","03-A1-5",[],"简单枚举",{"problems":3369,"id":3365,"title":3367,"goal":3370,"explanation":3371,"examples":3387,"summary":3395,"exercises":3396,"next":3434,"level":7,"module":3152,"seq":838,"keywords":3435,"nextTitle":3436},[],"掌握“枚举（穷举）”思想：把所有可能情况一一尝试，找到满足条件的答案。",[3372,3377,3382],{"title":3373,"content":3374,"codePy":3375,"codeCpp":3376},"什么是枚举","枚举就是“把所有候选都试一遍，留下符合条件的”。当搜索空间不大时，枚举是最直接可靠的方法。","# 找 1~100 中能被 7 整除的数\nfor i in range(1, 101):\n    if i % 7 == 0:\n        print(i)","for (int i = 1; i \u003C= 100; i++)\n    if (i % 7 == 0) cout \u003C\u003C i \u003C\u003C \" \";",{"title":3378,"content":3379,"codePy":3380,"codeCpp":3381},"枚举的要素","① 确定“枚举范围”（从哪试到哪）；② 确定“判断条件”（什么算满足）；③ 在循环里逐个检验。","for i in range(L, R + 1):\n    if ok(i):\n        answer = i\n        break","for (int i = L; i \u003C= R; i++)\n    if (ok(i)) { ans = i; break; }",{"title":3383,"content":3384,"codePy":3385,"codeCpp":3386},"效率与范围","枚举的代价随范围线性增长 O(n)。范围太大就慢，此时要用更聪明的算法（如二分）。初学阶段先会“写对”，再谈“写快”。","# 范围小才适合枚举\n# 范围大考虑优化","\u002F\u002F 范围大时枚举会变慢，需优化",[3388],{"title":3389,"prompt":3390,"analysis":3391,"codePy":3392,"codeCpp":3393,"run":3394},"百元百鸡雏形","在 1 到 20 内，找出所有“既是偶数又能被 3 整除”的数并输出。","典型的简单枚举：固定范围 1~20，逐个判断是否满足条件（偶且被3整除）。","for i in range(1, 21):\n    if i % 2 == 0 and i % 3 == 0:\n        print(i)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int i = 1; i \u003C= 20; i++)\n        if (i % 2 == 0 && i % 3 == 0) cout \u003C\u003C i \u003C\u003C \" \";\n    return 0;\n}","输出: 6 12 18","枚举=把候选一一尝试、保留满足条件的。三要素：枚举范围、判断条件、循环检验。范围不大时枚举最稳，范围过大则需更优算法。",{"choice":3397,"coding":3421},[3398,3404,3413],{"id":3399,"stem":3400,"options":3401,"answer":9,"explanation":3403},"03-A1-5-c1","枚举法的核心操作是？",[3117,3402,2509,1405],"逐一尝试所有可能","枚举就是穷举候选并检验。",{"id":3405,"stem":3406,"options":3407,"answer":703,"explanation":3412},"03-A1-5-c2","枚举法三要素不包括？",[3408,3409,3410,3411],"枚举范围","判断条件","循环检验","二分加速","二分是优化手段，不是枚举要素。",{"id":3414,"stem":3415,"options":3416,"answer":9,"explanation":3420},"03-A1-5-c3","枚举的复杂度通常随范围？",[1307,3417,3418,3419],"线性 O(n)","平方 O(n²)","对数 O(log n)","逐个尝试，约 n 次，O(n)。",[3422],{"id":3423,"title":3424,"prompt":3425,"inputFormat":2352,"outputFormat":3426,"samples":3427,"starterPy":357,"starterCpp":3430,"referencePy":3431,"referenceCpp":3432,"hint":3433},"03-A1-5-p1","枚举水仙花数","输出 100 到 999 之间所有“水仙花数”（各位数字立方和等于自身），每行一个。","若干整数",[3428],{"input":357,"output":3429},"153\n370\n371\n407","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 100; n \u003C= 999; n++) {\n        int a = n\u002F100, b = n\u002F10%10, c = n%10;\n        \u002F\u002F 判断立方和\n    }\n    return 0;\n}","for n in range(100, 1000):\n    a, b, c = n \u002F\u002F 100, n \u002F\u002F 10 % 10, n % 10\n    if a**3 + b**3 + c**3 == n:\n        print(n)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int n = 100; n \u003C= 999; n++) {\n        int a = n\u002F100, b = n\u002F10%10, c = n%10;\n        if (a*a*a + b*b*b + c*c*c == n) cout \u003C\u003C n \u003C\u003C endl;\n    }\n    return 0;\n}","枚举 100~999，拆分三位并验证立方和。","03-A1-6",[],"分类讨论",{"problems":3438,"id":3434,"title":3436,"goal":3439,"explanation":3440,"examples":3456,"summary":3464,"exercises":3465,"next":357,"level":7,"module":3152,"seq":936,"keywords":3506},[],"掌握“分类讨论”思想：把问题按条件分成若干情况，对每种情况分别处理，避免遗漏。",[3441,3446,3451],{"title":3442,"content":3443,"codePy":3444,"codeCpp":3445},"为什么分类讨论","很多问题“不同情况做法不同”，比如“成绩分级”“符号判断”。先列出所有可能类别，再逐一处理，能保证不漏不重。","if x > 0: ...\nelif x \u003C 0: ...\nelse: ...","if (x > 0) {}\nelse if (x \u003C 0) {}\nelse {}",{"title":3447,"content":3448,"codePy":3449,"codeCpp":3450},"分类要“互斥且穷尽”","类别之间不要重叠（避免重复处理），合起来要覆盖全部可能（避免遗漏）。常用“穷举情况+else 兜底”来实现。","if day in (6,7): print(\"周末\")\nelse: print(\"工作日\")","if (day==6||day==7) cout\u003C\u003C\"周末\";\nelse cout\u003C\u003C\"工作日\";",{"title":3452,"content":3453,"codePy":3454,"codeCpp":3455},"与枚举的关系","分类讨论常和枚举配合：先枚举所有可能，再按类别分别统计或处理。","for x in data:\n    if x \u003C 0: neg++\n    elif x == 0: zero++\n    else: pos++","for (x:data){ if(x\u003C0)neg++; else if(x==0)zero++; else pos++; }",[3457],{"title":3458,"prompt":3459,"analysis":3460,"codePy":3461,"codeCpp":3462,"run":3463},"按位数分类","输入一个 1~9999 的整数，输出它是几位数（分类讨论：1位\u002F2位\u002F3位\u002F4位）。","把“几位数”按数值范围分成四类，分别判断，体现分类讨论的“互斥且穷尽”。","n = int(input())\nif n \u003C 10: print(1)\nelif n \u003C 100: print(2)\nelif n \u003C 1000: print(3)\nelse: print(4)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n; cin >> n;\n    if (n \u003C 10) cout \u003C\u003C 1;\n    else if (n \u003C 100) cout \u003C\u003C 2;\n    else if (n \u003C 1000) cout \u003C\u003C 3;\n    else cout \u003C\u003C 4;\n    return 0;\n}","输入: 357\n输出: 3","分类讨论把问题按条件分成互斥且穷尽的若干类，分别处理，避免遗漏与重复。常与枚举配合：先枚举、再按类别统计。注意用 else 兜底覆盖剩余情况。",{"choice":3466,"coding":3487},[3467,3476,3481],{"id":3468,"stem":3469,"options":3470,"answer":9,"explanation":3475},"03-A1-6-c1","好的分类应当满足？",[3471,3472,3473,3474],"越多越好","互斥且穷尽","只用 if 不用 else","只看部分情况","分类要互不重叠且覆盖全部。",{"id":3477,"stem":3478,"options":3479,"answer":9,"explanation":3480},"03-A1-6-c2","“成绩分级”最适合用什么思想？",[3117,3436,2509,3259],"按分数段分情况处理。",{"id":3482,"stem":3483,"options":3484,"answer":9,"explanation":3486},"03-A1-6-c3","为防遗漏，分类讨论常加？",[438,3485,482,484],"else 兜底","else 覆盖剩余所有情况。",[3488],{"id":3489,"title":3490,"prompt":3491,"inputFormat":3492,"outputFormat":3493,"samples":3494,"starterPy":357,"starterCpp":3502,"referencePy":3503,"referenceCpp":3504,"hint":3505},"03-A1-6-p1","三角形类型","输入三个正整数 a、b、c（一行），先判断能否构成三角形（任意两边和大于第三边），能则再分类为等边\u002F等腰\u002F一般三角形，输出对应中文；不能则输出 \"NO\"。","一行三个正整数","中文类型或 NO",[3495,3498,3501],{"input":3496,"output":3497},"3 3 3","等边",{"input":3499,"output":3500},"3 4 5","一般",{"input":74,"output":1943},"#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b, c; cin >> a >> b >> c;\n    \u002F\u002F 先判断能否构成三角形，再分类\n    return 0;\n}","a, b, c = map(int, input().split())\nif a + b > c and a + c > b and b + c > a:\n    if a == b == c: print(\"等边\")\n    elif a == b or b == c or a == c: print(\"等腰\")\n    else: print(\"一般\")\nelse:\n    print(\"NO\")","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a, b, c; cin >> a >> b >> c;\n    if (a+b>c && a+c>b && b+c>a) {\n        if (a==b && b==c) cout\u003C\u003C\"等边\";\n        else if (a==b||b==c||a==c) cout\u003C\u003C\"等腰\";\n        else cout\u003C\u003C\"一般\";\n    } else cout\u003C\u003C\"NO\";\n    return 0;\n}","先按三角形不等式判断，再用分类讨论区分等边\u002F等腰\u002F一般。",[],{"id":3508,"title":3509,"level":3510,"module":3152,"seq":9,"goal":3511,"keywords":3512,"explanation":3516,"examples":3528,"exercises":3536,"summary":3580,"next":3581,"nextTitle":3582},"03-A2-1","多状态模拟","Lv.2","掌握\"多状态模拟\"：用多个状态变量同时刻画一个过程，按规则逐步推进直到结束。",[3513,3514,3515],"模拟","状态变量","多状态",[3517,3522],{"title":3518,"content":3519,"codePy":3520,"codeCpp":3521},"1. 一个过程常常有多个\"状态\"","真实的模拟对象往往不止一个数：比如红绿灯有\"当前颜色 + 剩余秒数\"，游戏角色有\"位置 + 血量 + 朝向\"。把这些会变的量都定义成变量，就组成了\"状态\"。每一步按规则更新它们，就是在模拟过程。","color = \"绿\"\nremain = 30\nwhile remain > 0:\n    remain -= 1\n    if remain == 0:\n        color = \"红\"\n        remain = 20","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    string color = \"绿\";\n    int remain = 30;\n    while (remain > 0) {\n        remain -= 1;\n        if (remain == 0) { color = \"红\"; remain = 20; }\n    }\n    cout \u003C\u003C color \u003C\u003C endl;\n    return 0;\n}",{"title":3523,"content":3524,"codePy":3525,"codeCpp":3526,"tip":3527},"2. 用\"状态变量 + 规则\"代替死记步骤","多状态模拟的关键是把过程抽象成\"状态 + 转移规则\"。不要一步一步手写，而是写一个循环：每轮读取当前状态、按规则算出下一状态。这样既简洁又不容易漏。","pos = 0; hp = 100\nfor ev in events:\n    pos += ev.dx\n    hp += ev.dhp\n    if hp \u003C= 0: break","int pos = 0, hp = 100;\nfor (auto& ev : events) {\n    pos += ev.dx;\n    hp += ev.dhp;\n    if (hp \u003C= 0) break;\n}","先想清楚\"有哪些状态、每步怎么变\"，再写循环。",[3529],{"title":3530,"prompt":3531,"analysis":3532,"codePy":3533,"codeCpp":3534,"run":3535},"例 1：红绿灯倒计时","红绿灯初始为\"绿\"亮 30 秒，然后变\"红\"亮 20 秒，如此循环。模拟 100 秒后灯的颜色。","用 color 与 remain 两个状态变量，每过 1 秒 remain 减 1，归零就切换颜色并重置剩余时间。","color, remain = \"绿\", 30\nfor t in range(100):\n    remain -= 1\n    if remain == 0:\n        color = \"红\" if color == \"绿\" else \"绿\"\n        remain = 20 if color == \"红\" else 30\nprint(color)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    string color = \"绿\"; int remain = 30;\n    for (int t = 0; t \u003C 100; t++) {\n        remain -= 1;\n        if (remain == 0) {\n            color = (color == \"绿\") ? \"红\" : \"绿\";\n            remain = (color == \"红\") ? 20 : 30;\n        }\n    }\n    cout \u003C\u003C color \u003C\u003C endl;\n    return 0;\n}","红",{"choice":3537,"coding":3562},[3538,3546,3554],{"id":38,"stem":3539,"options":3540,"answer":9,"explanation":3545},"多状态模拟中，描述过程最常用的做法是？",[3541,3542,3543,3544],"写死每一步","用多个状态变量 + 循环按规则更新","只用一个数","不用变量","把状态拆成多个变量，循环里按规则更新它们。",{"id":47,"stem":3547,"options":3548,"answer":9,"explanation":3553},"红绿灯模拟除了\"颜色\"，通常还需要哪个状态？",[3549,3550,3551,3552],"温度","剩余秒数","音量","亮度","\"剩余秒数\"决定何时切换颜色，是多状态之一。",{"id":56,"stem":3555,"options":3556,"answer":9,"explanation":3561},"当血量 hp 减到 0 时，模拟通常应？",[3557,3558,3559,3560],"继续运行","结束（break）","变负数继续","忽略","血量耗尽意味着过程结束，应及时 break。",[3563],{"id":67,"title":3564,"prompt":3565,"inputFormat":3566,"outputFormat":3567,"samples":3568,"starterPy":3575,"starterCpp":3576,"hint":3577,"referencePy":3578,"referenceCpp":3579},"机器人走路","机器人从坐标 0 出发，依次收到 n 条指令，每条是 +d 或 -d（向前后移动 d 米）。输出最终坐标；若任意时刻坐标 \u003C -100 则输出 \"OUT\"。","第一行 n；接下来 n 行每行一个整数 d（可正可负）。","一行：最终坐标或 \"OUT\"。",[3569,3572],{"input":3570,"output":3571},"3\n10\n-30\n5","-15",{"input":3573,"output":3574},"2\n-80\n-30","OUT","pos = 0\nn = int(input())\nfor _ in range(n):\n    d = int(input())\n    # 更新 pos 并判断是否越界\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, pos = 0, d;\n    cin >> n;\n    \u002F\u002F 逐条指令更新 pos\n    return 0;\n}","每读一条 d 就 pos += d，立刻判断 pos \u003C -100。","pos = 0\nn = int(input())\nfor _ in range(n):\n    d = int(input())\n    pos += d\n    if pos \u003C -100:\n        print(\"OUT\"); exit()\nprint(pos)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, pos = 0, d; cin >> n;\n    for (int i = 0; i \u003C n; i++) {\n        cin >> d; pos += d;\n        if (pos \u003C -100) { cout \u003C\u003C \"OUT\"; return 0; }\n    }\n    cout \u003C\u003C pos; return 0;\n}","多状态模拟用\"多个状态变量 + 循环按规则更新\"来刻画复杂过程，比死记步骤更稳健。先抽象出状态与转移规则，再写循环。","03-A2-2","数组模拟",{"id":3581,"title":3582,"level":3510,"module":3152,"seq":63,"goal":3584,"keywords":3585,"explanation":3587,"examples":3599,"exercises":3606,"summary":3645,"next":3646,"nextTitle":3647},"掌握\"用数组来模拟\"：把一排对象放进数组，通过下标访问和修改，完成排队、摆放、计数等过程。",[1434,3586,3513],"下标",[3588,3594],{"title":3589,"content":3590,"codePy":3591,"codeCpp":3592,"tip":3593},"1. 数组是\"一排带编号的盒子\"","模拟一排座位、一串灯泡、一排书架时，用数组最自然：下标就是编号，a[i] 就是第 i 个对象的状态。初始化后，按事件逐个修改对应下标即可。","seat = [False]*10          # 10 个座位，False 表示空\nseat[3] = True             # 第 4 个座位被占\nprint(seat[3])","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    bool seat[10] = {false};   \u002F\u002F 10 个座位\n    seat[3] = true;            \u002F\u002F 第 4 个被占\n    cout \u003C\u003C seat[3];           \u002F\u002F 1\n    return 0;\n}","数组下标从 0 开始，第 i 个对象用 a[i-1]（若编号从 1 起）。",{"title":3595,"content":3596,"codePy":3597,"codeCpp":3598},"2. 边模拟边改，最后统计","数组模拟的常见套路：先用数组记录初始状态，再按输入的事件逐条更新对应元素，最后遍历数组统计结果（如被占几个、亮了几个）。","a = [0]*5\nfor x in [2,2,4,2]:\n    a[x-1] += 1      # 编号从 1，落到下标 x-1\nprint(sum(1 for v in a if v>0))","int a[5] = {0};\nint x; while (cin >> x) a[x-1] += 1;\nint cnt = 0; for (int i=0;i\u003C5;i++) if (a[i]>0) cnt++;\ncout \u003C\u003C cnt;",[3600],{"title":3601,"prompt":3602,"analysis":3603,"codePy":3604,"codeCpp":3605,"run":890},"例 1：投票计数","有 5 个候选（编号 1~5），依次读入若干选票（候选编号），输出得票最多的候选编号（数据保证唯一）。","用长度 5 的数组计数，读一张票就 a[编号-1]++，最后找最大值下标 +1。","a = [0]*5\nwhile True:\n    try: x = int(input())\n    except: break\n    if 1 \u003C= x \u003C= 5: a[x-1] += 1\nprint(a.index(max(a)) + 1)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a[5] = {0}, x;\n    while (cin >> x) if (1 \u003C= x && x \u003C= 5) a[x-1]++;\n    int mi = 0; for (int i=1;i\u003C5;i++) if (a[i]>a[mi]) mi = i;\n    cout \u003C\u003C mi+1; return 0;\n}",{"choice":3607,"coding":3632},[3608,3616,3624],{"id":38,"stem":3609,"options":3610,"answer":9,"explanation":3615},"一排座位用数组 seat 模拟，第 4 个座位应访问？",[3611,3612,3613,3614],"seat[4]","seat[3]","seat[5]","seat[0]","下标从 0 开始，第 4 个是 seat[3]。",{"id":47,"stem":3617,"options":3618,"answer":9,"explanation":3623},"数组模拟\"计数\"时，读入编号 x（1~5）应执行？",[3619,3620,3621,3622],"a[x]++","a[x-1]++","a[0]++","a[x+1]++","把编号 1-based 映射到下标 0-based：x-1。",{"id":56,"stem":3625,"options":3626,"answer":9,"explanation":3631},"数组模拟最后常做什么？",[3627,3628,3629,3630],"立即结束","遍历统计结果","再读一遍","清空数组","模拟完成后遍历数组汇总（计数、找最值等）。",[3633],{"id":67,"title":3634,"prompt":3635,"inputFormat":3636,"outputFormat":490,"samples":3637,"starterPy":3640,"starterCpp":3641,"hint":3642,"referencePy":3643,"referenceCpp":3644},"灯泡开关","有 n 个灯泡初始全灭（0）。依次进行 m 次操作，每次输入一个编号 k，把第 k 个灯泡状态翻转（0→1 或 1→0）。最后输出亮着的灯泡数量。","第一行 n m；接下来 m 行每行一个整数 k（1≤k≤n）。",[3638],{"input":3639,"output":892},"5 3\n2\n2\n4","n, m = map(int, input().split())\nb = [0]*n\n# 翻转并统计\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, m, k; cin >> n >> m;\n    int b[1000] = {0};\n    \u002F\u002F 翻转 b[k-1]\n    return 0;\n}","翻转用 b[k-1] = 1 - b[k-1]，最后数 1 的个数。","n, m = map(int, input().split())\nb = [0]*n\nfor _ in range(m):\n    k = int(input()); b[k-1] = 1 - b[k-1]\nprint(sum(b))","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, m, k; cin >> n >> m;\n    int b[1000] = {0};\n    while (m--) { cin >> k; b[k-1] = 1 - b[k-1]; }\n    int s = 0; for (int i=0;i\u003Cn;i++) s += b[i];\n    cout \u003C\u003C s; return 0;\n}","数组模拟把\"一排对象\"映射成数组，通过下标访问\u002F修改，最后遍历统计。注意编号 1-based 与下标 0-based 的换算。","03-A2-3","字符串模拟",{"id":3646,"title":3647,"level":3510,"module":3152,"seq":703,"goal":3649,"keywords":3650,"explanation":3652,"examples":3664,"exercises":3672,"summary":3710,"next":3711,"nextTitle":3712},"掌握\"用字符串来模拟\"：把过程看成对一串字符的逐个处理，按规则生成或修改字符串。",[775,3651,3513],"逐字符",[3653,3659],{"title":3654,"content":3655,"codePy":3656,"codeCpp":3657,"tip":3658},"1. 字符串也可以像数组一样遍历","在 GESP 2 级里，字符串常被当作\"字符数组\"来模拟：从左到右看每个字符，根据它是什么决定怎么做。例如把小写变成大写、把数字字符累加成数值。","s = \"a1b2c3\"\nout = \"\"\nfor ch in s:\n    if ch.isdigit(): out += \"#\"\n    else: out += ch.upper()\nprint(out)","#include \u003Ciostream>\n#include \u003Ccctype>\nusing namespace std;\nint main() {\n    string s = \"a1b2c3\", out = \"\";\n    for (char ch : s) {\n        if (isdigit(ch)) out += \"#\";\n        else out += toupper(ch);\n    }\n    cout \u003C\u003C out; return 0;\n}","C++ 里用 s[i] 取第 i 个字符；Python 直接 for ch in s。",{"title":3660,"content":3661,"codePy":3662,"codeCpp":3663},"2. 用整数存数字字符串的值","模拟\"数字字符串转整数\"时，从左到右：每读一位，先把已有结果乘以 10，再加上这一位的值（字符 '0' 的差）。这就是手算进位的模拟。","s = \"123\"; val = 0\nfor ch in s: val = val*10 + (ord(ch)-ord(\"0\"))\nprint(val)","string s=\"123\"; int val=0;\nfor (char ch: s) val = val*10 + (ch - \"0\");\ncout \u003C\u003C val;",[3665],{"title":3666,"prompt":3667,"analysis":3668,"codePy":3669,"codeCpp":3670,"run":3671},"例 1：字母翻转大小写","读入一个仅含英文字母的字符串，把其中所有大写改成小写、小写改大写，输出结果。","逐字符判断大小写并翻转，拼接成新字符串。","s = input()\nprint(\"\".join(c.lower() if c.isupper() else c.upper() for c in s))","#include \u003Ciostream>\n#include \u003Ccctype>\nusing namespace std;\nint main() {\n    string s; cin >> s;\n    for (char &ch : s)\n        ch = islower(ch) ? toupper(ch) : tolower(ch);\n    cout \u003C\u003C s; return 0;\n}","AbC\naBc",{"choice":3673,"coding":3697},[3674,3682,3689],{"id":38,"stem":3675,"options":3676,"answer":9,"explanation":3681},"要把数字字符 ch 转成数值，正确做法是？",[3677,3678,3679,3680],"int(ch)","ch - \"0\"","ch + 0","ord(ch)","字符 '5' 减 '0' 得到整数 5，这是 C++\u002FASCII 下的常用技巧。",{"id":47,"stem":3683,"options":3684,"answer":9,"explanation":3688},"字符串模拟\"逐字符处理\"通常用什么结构？",[3685,3686,3687,1405],"while True","for 遍历每个字符","只处理第一个","从左到右对每个字符做处理。",{"id":56,"stem":3690,"options":3691,"answer":9,"explanation":3696},"把 \"123\" 转成整数，循环里 val 的更新是？",[3692,3693,3694,3695],"val = val + ch","val = val*10 + (ch-\"0\")","val = ch","val += 1","左移一位（×10）再加当前位，等价于手算。",[3698],{"id":67,"title":3699,"prompt":3700,"inputFormat":3701,"outputFormat":490,"samples":3702,"starterPy":3705,"starterCpp":3706,"hint":3707,"referencePy":3708,"referenceCpp":3709},"数字求和","读入一个仅由数字组成的字符串，输出其中所有数字相加的和。","一行一个字符串（长度 ≤ 100，只含数字）。",[3703],{"input":3704,"output":291},"12345","s = input()\n# 累加每个数字字符对应的数值\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string s; cin >> s;\n    \u002F\u002F 累加\n    return 0;\n}","每个字符 c 的值是 c - \"0\"，全部加起来。","s = input()\nprint(sum(int(c) for c in s))","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main() {\n    string s; cin >> s; int sum = 0;\n    for (char c : s) sum += c - \"0\";\n    cout \u003C\u003C sum; return 0;\n}","字符串模拟把过程看作对字符序列的逐字符处理：用 s[i] 取字符、用 ch-\"0\" 取数字值、用拼接生成新串。","03-A2-4","二维网格模拟",{"id":3711,"title":3712,"level":3510,"module":3152,"seq":770,"goal":3714,"keywords":3715,"explanation":3719,"examples":3731,"exercises":3739,"summary":3776,"next":3777,"nextTitle":3778},"掌握\"二维网格模拟\"：用二维数组表示棋盘、地图、矩阵，通过 (行,列) 坐标更新状态。",[3716,3717,3718],"二维数组","网格","坐标",[3720,3726],{"title":3721,"content":3722,"codePy":3723,"codeCpp":3724,"tip":3725},"1. 二维数组就是\"表格\"","棋盘、地图、像素图都是网格，用二维数组 a[row][col] 表示最直观：行号和列号定位一个格子。遍历用两层循环。","g = [[0]*4 for _ in range(3)]   # 3 行 4 列\ng[1][2] = 9\nprint(g[1][2])","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int g[3][4] = {0};   \u002F\u002F 3 行 4 列\n    g[1][2] = 9;\n    cout \u003C\u003C g[1][2];\n    return 0;\n}","行、列都从 0 计数；注意别把行列写反。",{"title":3727,"content":3728,"codePy":3729,"codeCpp":3730},"2. 沿四个方向扩散\u002F遍历","网格模拟常遇到\"从某格向上下左右扩散\"。用方向数组 dr\u002Fdc = [-1,1,0,0]、[0,0,-1,1] 配合循环，避免写四遍重复代码。","dr, dc = [-1,1,0,0], [0,0,-1,1]\nfor k in range(4):\n    r2, c2 = r+dr[k], c+dc[k]","int dr[4]={-1,1,0,0}, dc[4]={0,0,-1,1};\nfor (int k=0;k\u003C4;k++){ int r2=r+dr[k], c2=c+dc[k]; }",[3732],{"title":3733,"prompt":3734,"analysis":3735,"codePy":3736,"codeCpp":3737,"run":3738},"例 1：矩阵加 1","读入一个 3×4 的整数矩阵，把每个元素加 1 后输出（行列间空格，行间换行）。","用二维数组读入，两层循环每个 +1 再输出。","g = [[int(x) for x in input().split()] for _ in range(3)]\nfor i in range(3):\n    print(*[g[i][j]+1 for j in range(4)])","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int g[3][4];\n    for (int i=0;i\u003C3;i++) for (int j=0;j\u003C4;j++) cin >> g[i][j];\n    for (int i=0;i\u003C3;i++){ for (int j=0;j\u003C4;j++) cout \u003C\u003C g[i][j]+1 \u003C\u003C \" \"; cout \u003C\u003C endl; }\n    return 0;\n}","1 2 3 4\n5 6 7 8\n9 0 1 2\n2 3 4 5\n6 7 8 9\n10 1 2 3",{"choice":3740,"coding":3763},[3741,3749,3757],{"id":38,"stem":3742,"options":3743,"answer":9,"explanation":3748},"3 行 4 列的网格应声明为？",[3744,3745,3746,3747],"int a[4][3]","int a[3][4]","int a[3][3]","int a[4][4]","第一维是行、第二维是列：[行][列]。",{"id":47,"stem":3750,"options":3751,"answer":9,"explanation":3756},"访问第 2 行第 3 列（均从 1 计）应写？",[3752,3753,3754,3755],"a[2][3]","a[1][2]","a[3][2]","a[2][1]","从 0 计数：行 2-1=1，列 3-1=2 → a[1][2]。",{"id":56,"stem":3758,"options":3759,"answer":607,"explanation":3762},"表示\"上下左右\"四个方向，常用？",[3760,3761,1405,163],"两个长度为 4 的方向数组","一个变量","dr\u002Fdc 两个数组配合循环最清爽。",[3764],{"id":67,"title":3765,"prompt":3766,"inputFormat":3767,"outputFormat":490,"samples":3768,"starterPy":3771,"starterCpp":3772,"hint":3773,"referencePy":3774,"referenceCpp":3775},"边界计数","读入 n、m 和一个 n×m 的 01 矩阵（0 空 1 障碍）。统计\"被障碍包围以外的空地\"——即输出矩阵四条边界上所有 0 的数量（边界指第 1 行、第 n 行、第 1 列、第 m 列）。","第一行 n m；接下来 n 行每行 m 个整数。",[3769],{"input":3770,"output":624},"3 3\n0 1 0\n1 0 1\n0 0 0","n, m = map(int, input().split())\ng = [list(map(int, input().split())) for _ in range(n)]\n# 统计边界上的 0\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, m, g[50][50]; cin >> n >> m;\n    for (int i=0;i\u003Cn;i++) for (int j=0;j\u003Cm;j++) cin >> g[i][j];\n    \u002F\u002F 统计边界 0\n    return 0;\n}","遍历所有格子，当下标 i==0 或 i==n-1 或 j==0 或 j==m-1 且 g[i][j]==0 时计数。","n, m = map(int, input().split())\ng = [list(map(int, input().split())) for _ in range(n)]\ncnt = 0\nfor i in range(n):\n    for j in range(m):\n        if g[i][j]==0 and (i==0 or i==n-1 or j==0 or j==m-1):\n            cnt += 1\nprint(cnt)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int n, m, g[50][50]; cin >> n >> m;\n    for (int i=0;i\u003Cn;i++) for (int j=0;j\u003Cm;j++) cin >> g[i][j];\n    int cnt = 0;\n    for (int i=0;i\u003Cn;i++) for (int j=0;j\u003Cm;j++)\n        if (g[i][j]==0 && (i==0||i==n-1||j==0||j==m-1)) cnt++;\n    cout \u003C\u003C cnt; return 0;\n}","二维网格模拟用 a[row][col] 表示格子，注意行列 0-based 与\"行\u002F列\"别写反；扩散用 dr\u002Fdc 方向数组。","03-A2-5","多重枚举",{"id":3777,"title":3778,"level":3510,"module":3152,"seq":838,"goal":3780,"keywords":3781,"explanation":3782,"examples":3794,"exercises":3801,"summary":3838,"next":3839,"nextTitle":3840},"掌握\"多重枚举\"：同时枚举两个或更多变量（嵌套循环），在组合空间里找满足条件的解。",[1542,632,1473],[3783,3788],{"title":3784,"content":3785,"codePy":3786,"codeCpp":3787},"1. 一个变量不够，就枚举多个","当答案由两个或更多量共同决定时（如\"两数之和\"\"鸡兔同笼\"），用嵌套循环分别枚举每个量的可能取值，再检查组合是否满足约束。","for x in range(1, 100):\n    for y in range(1, 100):\n        if x + y == 50 and 2*x + 4*y == 120:\n            print(x, y)","for (int x=1;x\u003C100;x++)\n  for (int y=1;y\u003C100;y++)\n    if (x+y==50 && 2*x+4*y==120) cout \u003C\u003C x \u003C\u003C \" \" \u003C\u003C y;",{"title":3789,"content":3790,"codePy":3791,"codeCpp":3792,"tip":3793},"2. 用约束缩小范围","多重枚举最怕组合太多。先用数学关系把内层变量的范围压小：比如已知 x+y=50，内层 y 的范围就只需 1..50-x，而不是盲目 1..100。","for x in range(1, 50):\n    y = 50 - x\n    if 2*x + 4*y == 120: print(x, y)","for (int x=1;x\u003C50;x++){ int y=50-x; if (2*x+4*y==120) cout \u003C\u003C x\u003C\u003C\" \"\u003C\u003Cy; }","先列方程、再枚举，往往能把两层循环压成一层。",[3795],{"title":3796,"prompt":3797,"analysis":3798,"codePy":3799,"codeCpp":3800,"run":1256},"例 1：两数平方和","在 1~100 内找出所有满足 a² + b² = 25 的正整数对 (a,b)（a≤b），每行输出一对。","两层循环枚举 a、b，检查平方和；用 a≤b 避免重复。","for a in range(1, 101):\n    for b in range(a, 101):\n        if a*a + b*b == 25: print(a, b)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int a=1;a\u003C=100;a++)\n      for (int b=a;b\u003C=100;b++)\n        if (a*a+b*b==25) cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b \u003C\u003C endl;\n    return 0;\n}",{"choice":3802,"coding":3825},[3803,3811,3819],{"id":38,"stem":3804,"options":3805,"answer":9,"explanation":3810},"\"鸡兔同笼\"更适合用？",[3806,3807,3808,3809],"单层循环","多重枚举（嵌套循环）","只枚举头","不枚举","头、脚两个量，需同时枚举或列方程后枚举。",{"id":47,"stem":3812,"options":3813,"answer":9,"explanation":3818},"为减小多重枚举量，应优先？",[3814,3815,3816,3817],"加大范围","用约束缩小内层范围","增加循环层数","随机猜","用已知关系压缩内层循环范围。",{"id":56,"stem":3820,"options":3821,"answer":9,"explanation":3824},"枚举 a≤b 的主要好处是？",[1448,3822,3823,3339],"避免重复解","代码更短","限定顺序可避免 (a,b) 与 (b,a) 重复。",[3826],{"id":67,"title":3827,"prompt":3828,"inputFormat":353,"outputFormat":3829,"samples":3830,"starterPy":3833,"starterCpp":3834,"hint":3835,"referencePy":3836,"referenceCpp":3837},"百钱百鸡","公鸡 5 元\u002F只，母鸡 3 元\u002F只，小鸡 1 元\u002F3 只。用 100 元买 100 只鸡，输出所有方案（每行 公鸡 母鸡 小鸡）。","每种方案一行三个整数。",[3831],{"input":357,"output":3832},"0 25 75\n3 20 77\n4 18 78\n7 13 80\n8 11 81\n11 6 83\n12 4 84","for g in range(0, 21):\n    for m in range(0, 34):\n        # 小鸡数量与总价约束\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int g=0;g\u003C=20;g++)\n      for (int m=0;m\u003C=33;m++)\n        ;\n    return 0;\n}","设公鸡 g、母鸡 m，则小鸡=100-g-m，且 5g+3m+(100-g-m)\u002F3==100 且小鸡能被 3 整除。","for g in range(0, 21):\n    for m in range(0, 34):\n        c = 100 - g - m\n        if c >= 0 and c % 3 == 0 and 5*g + 3*m + c\u002F\u002F3 == 100:\n            print(g, m, c)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    for (int g=0;g\u003C=20;g++)\n      for (int m=0;m\u003C=33;m++){\n        int c = 100-g-m;\n        if (c>=0 && c%3==0 && 5*g+3*m+c\u002F3==100)\n          cout \u003C\u003C g \u003C\u003C \" \" \u003C\u003C m \u003C\u003C \" \" \u003C\u003C c \u003C\u003C endl;\n      }\n    return 0;\n}","多重枚举用嵌套循环同时枚举多个变量，在组合空间里筛选满足条件的解；先用约束压缩内层范围，并用顺序约定（如 a≤b）去重。","03-A2-6","枚举优化",{"id":3839,"title":3840,"level":3510,"module":3152,"seq":936,"goal":3842,"keywords":3843,"explanation":3847,"examples":3859,"exercises":3867,"summary":3901,"next":3902,"nextTitle":3903},"掌握\"枚举优化\"：通过缩小枚举范围、提前剪枝、利用单调性，让枚举从 O(n²) 降到可接受的复杂度。",[3844,3845,3846],"剪枝","缩小范围","单调性",[3848,3853],{"title":3849,"content":3850,"codePy":3851,"codeCpp":3852},"1. 缩小范围是最直接的优化","枚举前先用已知信息压边界：比如求\"不超过 N 的最大平方数\"，只需枚举 i=1..√N，而不是 1..N。范围砍到一半平方根，速度快很多。","import math\nn = 100\nfor i in range(1, int(math.isqrt(n))+1):\n    pass  # i*i \u003C= n","#include \u003Ccmath>\nint n=100; for (int i=1;i*i\u003C=n;i++) { }",{"title":3854,"content":3855,"codePy":3856,"codeCpp":3857,"tip":3858},"2. 提前剪枝：不行就跳","枚举过程中一旦发现当前分支不可能满足条件，立刻 continue 或 break，省去无意义的后续枚举。例如两数之和已超目标，再加大只会更大，可 break。","for a in range(1, n):\n    for b in range(1, n):\n        if a + b > T: break   # 再大也不行\n        if a*a + b*b == T: ...","for (int a=1;a\u003Cn;a++)\n  for (int b=1;b\u003Cn;b++){\n    if (a+b>T) break;\n    if (a*a+b*b==T) {}\n  }","循环内层按从小到大枚举时，超出上界可 break 而非 continue。",[3860],{"title":3861,"prompt":3862,"analysis":3863,"codePy":3864,"codeCpp":3865,"run":3866},"例 1：最近的平方数","读入 N，输出不超过 N 的最大完全平方数。","只需枚举 i=1..√N，取最大 i*i。","import math\nN = int(input())\ni = int(math.isqrt(N))\nprint(i*i)","#include \u003Ciostream>\n#include \u003Ccmath>\nusing namespace std;\nint main() { int N; cin>>N; int i=sqrt(N); cout\u003C\u003Ci*i; return 0; }","30\n25",{"choice":3868,"coding":3888},[3869,3877,3881],{"id":38,"stem":3870,"options":3871,"answer":63,"explanation":3876},"求不超过 N 的最大平方数，最省事的枚举上界是？",[3872,3873,3874,3875],"N","N\u002F2","√N","2N","i*i≤N 即 i≤√N，范围最小。",{"id":47,"stem":3878,"options":3879,"answer":9,"explanation":3880},"内层枚举已超目标值且单调递增，应？",[482,438,484,3560],"再枚举只会更大，直接 break 剪枝。",{"id":56,"stem":3882,"options":3883,"answer":9,"explanation":3887},"枚举优化的核心思想是？",[3884,3885,3886,1568],"枚举越多越好","少枚举、早剪枝","不用枚举","减少无效枚举量。",[3889],{"id":67,"title":3890,"prompt":3891,"inputFormat":3892,"outputFormat":490,"samples":3893,"starterPy":3896,"starterCpp":3897,"hint":3898,"referencePy":3899,"referenceCpp":3900},"两数之和接近","读入 T。找出满足 a+b≤T 且 a*b 最大的正整数对 (a,b)（1≤a≤b），输出最大的 a*b。","一行一个整数 T（T≤10000）。",[3894],{"input":883,"output":3895},"25","T = int(input())\nbest = 0\n# 枚举 a，内层枚举 b 并剪枝\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int T, best=0; cin>>T;\n    \u002F\u002F 枚举 a,b\n    return 0;\n}","外层 a 从 1 到 T\u002F2，内层 b 从 a 到 T-a，一旦 a*b≤best 可停止内层（b 增大乘积先增后减，到峰值后下降时可 break）。为简单也可直接枚举到 T-a。","T = int(input())\nbest = 0\nfor a in range(1, T\u002F\u002F2+1):\n    for b in range(a, T-a+1):\n        if a*b > best: best = a*b\n        else: break\nprint(best)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int T, best=0; cin>>T;\n    for (int a=1;a\u003C=T\u002F2;a++)\n      for (int b=a;b\u003C=T-a;b++){\n        if (a*b>best) best=a*b; else break;\n      }\n    cout\u003C\u003Cbest; return 0;\n}","枚举优化的两板斧：先用数学关系把枚举上界砍到最小；枚举中一旦发现分支无望就 break\u002Fcontinue 剪枝。","03-A2-7","枚举边界",{"id":3902,"title":3903,"level":3510,"module":3152,"seq":998,"goal":3905,"keywords":3906,"explanation":3910,"examples":3922,"exercises":3930,"summary":3967,"next":3968,"nextTitle":3969},"掌握\"枚举边界\"：小心循环的开闭区间，避免漏解（off-by-one）或多解，尤其注意等号与最大\u002F最小取值。",[3907,3908,3909],"边界","off-by-one","开闭区间",[3911,3916],{"title":3912,"content":3913,"codePy":3914,"codeCpp":3915},"1. 看清\"小于等于\"还是\"小于\"","枚举范围最容易出错的地方是端点。题目说\"不超过 N\"就枚举 1..N（含 N）；说\"严格小于 N\"就到 N-1。`range(1, N+1)` 与 `range(1, N)` 差一个，常导致少算或多算。","for i in range(1, N+1): ...   # 包含 N\nfor i in range(1, N):   ...   # 不含 N","for (int i=1;i\u003C=N;i++) ...   \u002F\u002F 含 N\nfor (int i=1;i\u003CN;i++)  ...   \u002F\u002F 不含 N",{"title":3917,"content":3918,"codePy":3919,"codeCpp":3920,"tip":3921},"2. 检查\"最大\u002F最小\"是否取到边界","找最大满足某条件的数时，枚举要从大到小或保留候选；找最值时初始值要够极端（如 max 初值设很小）。边界处的特例（如 0、1、N）往往就是坑。","best = -1\nfor i in range(N+1):\n    if ok(i): best = i   # 自然保留最大","int best=-1; for(int i=0;i\u003C=N;i++) if(ok(i)) best=i;","枚举前在纸上列几个边界样例（最小、最大、临界）验证。",[3923],{"title":3924,"prompt":3925,"analysis":3926,"codePy":3927,"codeCpp":3928,"run":3929},"例 1：含端点的计数","统计 1 到 N（含 N）中能被 3 整除的数的个数。","枚举 1..N 都要覆盖到，注意用 \u003C=N。","N = int(input()); c = 0\nfor i in range(1, N+1):\n    if i % 3 == 0: c += 1\nprint(c)","#include \u003Ciostream>\nusing namespace std;\nint main(){ int N,c=0; cin>>N; for(int i=1;i\u003C=N;i++) if(i%3==0) c++; cout\u003C\u003Cc; return 0; }","10\n3",{"choice":3931,"coding":3954},[3932,3940,3948],{"id":38,"stem":3933,"options":3934,"answer":9,"explanation":3939},"\"1 到 N 含 N\"在 Python 应写？",[3935,3936,3937,3938],"range(1,N)","range(1,N+1)","range(N)","range(0,N)","range 右端开区间，要含 N 得写 N+1。",{"id":47,"stem":3941,"options":3942,"answer":9,"explanation":3947},"C++ 中\"含 N\"的循环条件是？",[3943,3944,3945,3946],"i\u003CN","i\u003C=N","i==N","i>N","\u003C= 才包含右端点。",{"id":56,"stem":3949,"options":3950,"answer":9,"explanation":3953},"找最大值时，初始值 best 应设为？",[891,3951,3872,3952],"一个很小的数（如 -1）","任意","初始要足够小，否则会漏掉真正的最大值。",[3955],{"id":67,"title":3956,"prompt":3957,"inputFormat":3958,"outputFormat":490,"samples":3959,"starterPy":3962,"starterCpp":3963,"hint":3964,"referencePy":3965,"referenceCpp":3966},"区间内整除","读入 L 和 R（1≤L≤R≤10^6），输出 L 到 R（含两端）中能被 7 整除的数的个数。","一行两个整数 L R。",[3960],{"input":3961,"output":759},"1 20","L, R = map(int, input().split())\nc = 0\n# 枚举 L..R 含端点\n","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int L, R, c=0; cin >> L >> R;\n    \u002F\u002F 枚举 L..R\n    return 0;\n}","用 for i in range(L, R+1)（Python）或 i\u003C=R（C++），注意两端都包含。","L, R = map(int, input().split())\nc = sum(1 for i in range(L, R+1) if i % 7 == 0)\nprint(c)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int L,R,c=0; cin>>L>>R;\n    for(int i=L;i\u003C=R;i++) if(i%7==0) c++;\n    cout\u003C\u003Cc; return 0;\n}","枚举边界是 off-by-one 的高发区：看清\"含\u002F不含\"端点、用对 \u003C= 与 \u003C；找最值时初始值要极端，并用边界样例验证。","04-A1-1","一维数组",{"id":3968,"title":3969,"level":3510,"module":3971,"seq":9,"goal":3972,"keywords":3973,"explanation":3975,"examples":3987,"exercises":3994,"summary":4030,"next":4031,"nextTitle":3716},"数组与基础预处理","理解一维数组：连续编号的同类型容器，能用下标读写，知道数组长度是固定的（静态数组）或可变（动态数组）。",[1434,3586,3974],"连续存储",[3976,3982],{"title":3977,"content":3978,"codePy":3979,"codeCpp":3980,"tip":3981},"1. 数组是\"一排连续编号的盒子\"","一维数组把多个同类型数据挨在一起存放，用下标访问。C++ 中静态数组大小在编译时确定：int a[10] 表示 10 个 int；Python 用列表 a=[0]*10 得到同样效果。下标从 0 开始。","a = [0]*5\na[0] = 3\na[4] = 9\nprint(a)","#include \u003Ciostream>\nusing namespace std;\nint main() {\n    int a[5] = {0};\n    a[0] = 3; a[4] = 9;\n    cout \u003C\u003C a[0] \u003C\u003C \" \" \u003C\u003C a[4];\n    return 0;\n}","下标越界是常见错误：a[5] 在长度为 5 的数组里是越界的。",{"title":3983,"content":3984,"codePy":3985,"codeCpp":3986},"2. 内存里数组是连续的","数组元素在内存中连续排列，所以按下标访问是 O(1)。这也意味着同一类型的元素占用相同字节（如 int 占 4 字节），地址依次递增。","a = [10,20,30]\nfor i,v in enumerate(a): print(i, v)","int a[3]={10,20,30};\nfor (int i=0;i\u003C3;i++) cout \u003C\u003C i \u003C\u003C \" \" \u003C\u003C a[i];",[3988],{"title":3989,"prompt":3990,"analysis":3991,"codePy":1879,"codeCpp":3992,"run":3993},"例 1：读入 n 个数求和","读入 n 和 n 个整数，存入数组后输出它们的和。","先读 n，再循环读入存到 a[i]，最后累加。","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, a[1000], s=0; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; s+=a[i]; }\n    cout\u003C\u003Cs; return 0;\n}","4\n1 2 3 4\n10",{"choice":3995,"coding":4016},[3996,4004,4012],{"id":38,"stem":3997,"options":3998,"answer":9,"explanation":4003},"长度为 5 的数组，合法的下标范围是？",[3999,4000,4001,4002],"1~5","0~4","0~5","1~4","下标从 0 开始，到长度-1 即 4。",{"id":47,"stem":4005,"options":4006,"answer":9,"explanation":4011},"a[10] 在 int a[10] 中？",[4007,4008,4009,4010],"合法，是最后一个","越界","是第一个","语法错误","下标 0~9，a[10] 越界。",{"id":56,"stem":4013,"options":4014,"answer":607,"explanation":4015},"按下标随机访问数组的时间复杂度是？",[1245,2338,2339,1246],"连续存储，直接定位，O(1)。",[4017],{"id":67,"title":4018,"prompt":4019,"inputFormat":1801,"outputFormat":4020,"samples":4021,"starterPy":4025,"starterCpp":4026,"hint":4027,"referencePy":4028,"referenceCpp":4029},"倒序输出","读入 n 和 n 个整数，按输入顺序的逆序输出它们（空格分隔）。","一行逆序的整数，空格分隔。",[4022],{"input":4023,"output":4024},"5\n1 2 3 4 5","5 4 3 2 1","n = int(input())\na = list(map(int, input().split()))\n# 逆序输出\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n, a[1000]; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    \u002F\u002F 逆序输出\n    return 0;\n}","从 i=n-1 递减到 0 输出 a[i]。","n=int(input()); a=list(map(int,input().split()))\nprint(*a[::-1])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000]; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    for(int i=n-1;i>=0;i--) cout\u003C\u003Ca[i]\u003C\u003C\" \";\n    return 0;\n}","一维数组是同类型数据的连续容器，下标 0-based；注意长度固定（静态）或可变（动态），并严防下标越界。","04-A1-2",{"id":4031,"title":3716,"level":3510,"module":3971,"seq":63,"goal":4033,"keywords":4034,"explanation":4037,"examples":4049,"exercises":4057,"summary":4093,"next":4094,"nextTitle":4095},"理解二维数组：用\"行、列\"两个下标表示表格\u002F矩阵，掌握声明、读写与遍历。",[3716,4035,4036],"矩阵","行列",[4038,4044],{"title":4039,"content":4040,"codePy":4041,"codeCpp":4042,"tip":4043},"1. 二维数组就是\"数组的数组\"","用两个下标 a[row][col] 访问一个格子。C++ 声明 int a[3][4] 表示 3 行 4 列；Python 用 a=[[0]*4 for _ in range(3)]（注意不能用 [0]*4*3 复用同一行）。","a = [[0]*4 for _ in range(3)]\na[1][2] = 5\nprint(a[1][2])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int a[3][4] = {0};\n    a[1][2] = 5;\n    cout \u003C\u003C a[1][2];\n    return 0;\n}","Python 必须用列表推导创建每行独立，否则会\"一行改、处处改\"。",{"title":4045,"content":4046,"codePy":4047,"codeCpp":4048},"2. 行列顺序别写反","遍历二维数组通常用外层行、内层列。读入时也要按\"先行后列\"的顺序读取，避免把行列弄反导致错位。","for i in range(3):\n    for j in range(4):\n        a[i][j] = int(input())","for(int i=0;i\u003C3;i++) for(int j=0;j\u003C4;j++) cin>>a[i][j];",[4050],{"title":4051,"prompt":4052,"analysis":4053,"codePy":4054,"codeCpp":4055,"run":4056},"例 1：矩阵行列和","读入 3×4 矩阵，输出每一行的元素和。","两层循环，内层累加每行。","a = [[int(x) for x in input().split()] for _ in range(3)]\nfor r in a: print(sum(r))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int a[3][4];\n    for(int i=0;i\u003C3;i++) for(int j=0;j\u003C4;j++) cin>>a[i][j];\n    for(int i=0;i\u003C3;i++){ int s=0; for(int j=0;j\u003C4;j++) s+=a[i][j]; cout\u003C\u003Cs\u003C\u003Cendl; }\n    return 0;\n}","1 2 3 4\n5 6 7 8\n9 0 1 2\n10\n26\n12",{"choice":4058,"coding":4079},[4059,4067,4075],{"id":38,"stem":4060,"options":4061,"answer":9,"explanation":4066},"int a[3][4] 表示？",[4062,4063,4064,4065],"3 列 4 行","3 行 4 列","12 行 1 列","1 行 12 列","第一维行、第二维列。",{"id":47,"stem":4068,"options":4069,"answer":9,"explanation":4074},"Python 创建 3 行 4 列应写？",[4070,4071,4072,4073],"[[0]*4]*3","[[0]*4 for _ in range(3)]","[0]*12","[[0]*3]*4","推导式保证每行独立对象。",{"id":56,"stem":4076,"options":4077,"answer":9,"explanation":4078},"访问第 2 行第 3 列（0-based）写？",[3754,3752,3753,3755],"从 0 计数：第 2 行是下标 2、第 3 列是下标 3，所以是 a[2][3]。",[4080],{"id":67,"title":4081,"prompt":4082,"inputFormat":3767,"outputFormat":4083,"samples":4084,"starterPy":4088,"starterCpp":4089,"hint":4090,"referencePy":4091,"referenceCpp":4092},"矩阵转置","读入一个 n×m 的整数矩阵（n,m≤20），输出它的转置（行列互换，即第 i 行变成第 i 列）。","m 行 n 列，元素空格分隔，行间换行。",[4085],{"input":4086,"output":4087},"2 3\n1 2 3\n4 5 6","1 4\n2 5\n3 6","n,m=map(int,input().split())\na=[list(map(int,input().split())) for _ in range(n)]\n# 转置输出\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,m,a[20][20]; cin>>n>>m;\n    for(int i=0;i\u003Cn;i++) for(int j=0;j\u003Cm;j++) cin>>a[i][j];\n    \u002F\u002F 转置\n    return 0;\n}","输出时外层遍历列 j，内层遍历行 i，打印 a[i][j]。","n,m=map(int,input().split())\na=[list(map(int,input().split())) for _ in range(n)]\nfor j in range(m):\n    print(*[a[i][j] for i in range(n)])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,m,a[20][20]; cin>>n>>m;\n    for(int i=0;i\u003Cn;i++) for(int j=0;j\u003Cm;j++) cin>>a[i][j];\n    for(int j=0;j\u003Cm;j++){ for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i][j]\u003C\u003C\" \"; cout\u003C\u003Cendl; }\n    return 0;\n}","二维数组用 a[row][col] 表示格子，声明与遍历都是\"先行后列\"；Python 务必用推导式创建独立行，谨防行列写反与浅拷贝。","04-A1-3","数组遍历",{"id":4094,"title":4095,"level":3510,"module":3971,"seq":703,"goal":4097,"keywords":4098,"explanation":4101,"examples":4113,"exercises":4121,"summary":4159,"next":4160,"nextTitle":4161},"掌握数组遍历：用循环依次访问每个元素，完成查找、打印、变换等任务。",[4099,233,4100],"遍历","访问",[4102,4107],{"title":4103,"content":4104,"codePy":4105,"codeCpp":4106},"1. 用循环逐个访问","遍历就是用 for 把数组每个元素看一遍。C++ 用下标 for(int i=0;i\u003Cn;i++)；Python 用 for v in a，或需要下标时 for i,v in enumerate(a)。","for i, v in enumerate(a):\n    print(i, v)","for (int i=0;i\u003Cn;i++) cout \u003C\u003C i \u003C\u003C \" \" \u003C\u003C a[i];",{"title":4108,"content":4109,"codePy":4110,"codeCpp":4111,"tip":4112},"2. 遍历时同时维护信息","遍历过程中可以边看边算：找最大值、统计个数、收集满足条件的下标等。一次遍历就能得到多个结果。","mx = a[0]\nfor v in a:\n    if v > mx: mx = v","int mx=a[0]; for(int i=1;i\u003Cn;i++) if(a[i]>mx) mx=a[i];","找最值初始化为 a[0]，而不是 0，避免全负数时出错。",[4114],{"title":4115,"prompt":4116,"analysis":4117,"codePy":4118,"codeCpp":4119,"run":4120},"例 1：打印偶数下标","读入 n 和数组，输出所有\"下标为偶数\"的元素（下标从 0 计）。","遍历下标，i 为偶数时输出 a[i]。","n=int(input()); a=list(map(int,input().split()))\nprint([a[i] for i in range(n) if i%2==0])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000]; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    for(int i=0;i\u003Cn;i++) if(i%2==0) cout\u003C\u003Ca[i]\u003C\u003C\" \";\n    return 0;\n}","6\n1 2 3 4 5 6\n1 3 5",{"choice":4122,"coding":4146},[4123,4131,4138],{"id":38,"stem":4124,"options":4125,"answer":9,"explanation":4130},"Python 中既需要下标又需要值，应？",[4126,4127,4128,4129],"for v in a","for i,v in enumerate(a)","for i in a","while a","enumerate 同时给出下标和值。",{"id":47,"stem":4132,"options":4133,"answer":9,"explanation":4137},"找最大值时把初值设为 0，数组全为负数会？",[4134,4135,567,4136],"正确","得到 0 而非真正最大值","无影响","应初始化为 a[0] 或极小值。",{"id":56,"stem":4139,"options":4140,"answer":9,"explanation":4145},"遍历二维数组通常嵌套几层循环？",[4141,4142,4143,4144],"1 层","2 层（行、列）","3 层","0 层","外层行、内层列。",[4147],{"id":67,"title":4148,"prompt":4149,"inputFormat":1801,"outputFormat":4150,"samples":4151,"starterPy":4154,"starterCpp":4155,"hint":4156,"referencePy":4157,"referenceCpp":4158},"元素翻倍","读入 n 和数组，把每个元素翻倍后输出（空格分隔）。","一行翻倍后的值，空格分隔。",[4152],{"input":2901,"output":4153},"2 4 6 8","n=int(input()); a=list(map(int,input().split()))\n# 翻倍输出\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000]; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    \u002F\u002F 翻倍输出\n    return 0;\n}","遍历输出 a[i]*2。","n=int(input()); a=list(map(int,input().split()))\nprint(*[x*2 for x in a])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000]; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]*2\u003C\u003C\" \";\n    return 0;\n}","数组遍历用循环逐个访问元素；C++ 用下标、Python 用 for\u002Fin 或 enumerate。遍历时可顺带维护最值、计数等，找最值初值要用 a[0]。","04-A1-4","数组统计",{"id":4160,"title":4161,"level":3510,"module":3971,"seq":770,"goal":4163,"keywords":4164,"explanation":4167,"examples":4178,"exercises":4186,"summary":4225,"next":4226,"nextTitle":4227},"掌握数组统计：在遍历数组时计算总和、平均值、个数等汇总信息。",[4165,1799,4166],"求和","计数",[4168,4173],{"title":4169,"content":4170,"codePy":4171,"codeCpp":4172},"1. 累加与计数","统计的核心是\"遍历 + 累加器\"。总和用一个变量 s 每步加 a[i]；个数用 c 每步 +1；平均值 = 总和 \u002F 个数。","s = 0\nfor v in a: s += v\navg = s \u002F len(a)","long long s=0; for(int i=0;i\u003Cn;i++) s+=a[i];\ndouble avg = (double)s\u002Fn;",{"title":4174,"content":4175,"codePy":4176,"codeCpp":4177},"2. 用\"前缀和思想\"避免重复求和","如果后面会反复求\"某段区间的和\"，可以预处理一个前缀和数组 p，使 p[i] 表示前 i 个之和，区间和用 p[r]-p[l-1] 一步得到。这是 GESP 更高阶的内容，这里先打好单遍统计的基础。","p=[0]\nfor v in a: p.append(p[-1]+v)\nprint(p[r]-p[l-1])","long long p[1001]={0};\nfor(int i=1;i\u003C=n;i++) p[i]=p[i-1]+a[i-1];\ncout\u003C\u003Cp[r]-p[l-1];",[4179],{"title":4180,"prompt":4181,"analysis":4182,"codePy":4183,"codeCpp":4184,"run":4185},"例 1：平均分","读入 n 个成绩，输出它们的总和与平均值（平均值保留两位小数）。","遍历累加求和，再除以 n 并格式化。","n=int(input()); a=list(map(int,input().split()))\ns=sum(a)\nprint(s, f\"{s\u002Fn:.2f}\")","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main(){\n    int n,a[1000],s=0; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; s+=a[i]; }\n    cout\u003C\u003Cs\u003C\u003C\" \"\u003C\u003Cfixed\u003C\u003Csetprecision(2)\u003C\u003C(double)s\u002Fn;\n    return 0;\n}","4\n80 90 70 100\n340 85.00",{"choice":4187,"coding":4210},[4188,4196,4204],{"id":38,"stem":4189,"options":4190,"answer":703,"explanation":4195},"求平均值错误的是？",[4191,4192,4193,4194],"总和\u002F个数","先每个加再除","用浮点除","用整数 n 当分母但总和用 int 直接除","整数相除会截断，需转 double。",{"id":47,"stem":4197,"options":4198,"answer":9,"explanation":4203},"前缀和 p[i] 通常表示？",[4199,4200,4201,4202],"第 i 个元素","前 i 个元素之和","从第 i 个到末尾的和","最大值","前缀和记录前缀累计。",{"id":56,"stem":4205,"options":4206,"answer":9,"explanation":4209},"统计个数通常用？",[4207,4208,4202,3117],"s 累加","c 计数","用一个计数器 c 每步 +1。",[4211],{"id":67,"title":4212,"prompt":4213,"inputFormat":4214,"outputFormat":4215,"samples":4216,"starterPy":4220,"starterCpp":4221,"hint":4222,"referencePy":4223,"referenceCpp":4224},"区间和","读入 n、数组，以及 q 次询问。每次询问给出 l、r（1-based，含端点），输出 a[l..r] 的和。","第一行 n q；第二行 n 个整数；接下来 q 行每行 l r。","每行一个询问的答案。",[4217],{"input":4218,"output":4219},"5 2\n1 2 3 4 5\n1 3\n2 5","6\n14","n,q=map(int,input().split())\na=list(map(int,input().split()))\np=[0]\nfor v in a: p.append(p[-1]+v)\n# 回答询问\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,q,a[1000],p[1001]={0}; cin>>n>>q;\n    for(int i=1;i\u003C=n;i++){ cin>>a[i-1]; p[i]=p[i-1]+a[i-1]; }\n    \u002F\u002F 回答询问\n    return 0;\n}","用前缀和 p，区间 [l,r] 的和为 p[r]-p[l-1]。","n,q=map(int,input().split())\na=list(map(int,input().split()))\np=[0]\nfor v in a: p.append(p[-1]+v)\nfor _ in range(q):\n    l,r=map(int,input().split())\n    print(p[r]-p[l-1])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,q,a[1000],p[1001]={0}; cin>>n>>q;\n    for(int i=1;i\u003C=n;i++){ cin>>a[i-1]; p[i]=p[i-1]+a[i-1]; }\n    while(q--){ int l,r; cin>>l>>r; cout\u003C\u003Cp[r]-p[l-1]\u003C\u003Cendl; }\n    return 0;\n}","数组统计就是\"遍历 + 累加器\u002F计数器\"，平均值要用浮点除。需要反复求区间和时，预处理前缀和可 O(1) 回答。","04-A1-5","最大值\u002F最小值",{"id":4226,"title":4227,"level":3510,"module":3971,"seq":838,"goal":4229,"keywords":4230,"explanation":4232,"examples":4243,"exercises":4251,"summary":4287,"next":4288,"nextTitle":4289},"掌握在数组中找最大值\u002F最小值，以及对应的下标（位置）。",[4231,3586,939],"最值",[4233,4238],{"title":4234,"content":4235,"codePy":4236,"codeCpp":4111,"tip":4237},"1. 擂台法找最值","设一个\"擂台\"初值为第一个元素，遍历其余元素，谁比擂主强就替换。找最大值用 >，找最小值用 \u003C。","mx = a[0]\nfor v in a[1:]:\n    if v > mx: mx = v","初值用 a[0] 而不是 0，避免数组全负时答案错误。",{"title":4239,"content":4240,"codePy":4241,"codeCpp":4242},"2. 同时记录最值下标","很多时候还要知道\"最值在哪儿\"。用一个 idx 记录当前最值的下标，更新最值时一起更新 idx。","mi, idx = a[0], 0\nfor i, v in enumerate(a):\n    if v \u003C mi: mi, idx = v, i","int mi=a[0], idx=0;\nfor(int i=1;i\u003Cn;i++) if(a[i]\u003Cmi){ mi=a[i]; idx=i; }",[4244],{"title":4245,"prompt":4246,"analysis":4247,"codePy":4248,"codeCpp":4249,"run":4250},"例 1：最高分与位置","读入 n 个成绩，输出最高分及其下标（下标从 1 计）。若有并列，输出最先出现的。","擂台法，更新最值时记录下标 +1。","n=int(input()); a=list(map(int,input().split()))\nmi,idx=a[0],1\nfor i,v in enumerate(a):\n    if v>mi: mi,idx=v,i+1\nprint(mi,idx)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],mx=a[0],idx=1; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; if(a[i]>mx){ mx=a[i]; idx=i+1; } }\n    cout\u003C\u003Cmx\u003C\u003C\" \"\u003C\u003Cidx; return 0;\n}","5\n90 88 95 70 95\n95 3",{"choice":4252,"coding":4273},[4253,4259,4265],{"id":38,"stem":4254,"options":4255,"answer":9,"explanation":4258},"找最大值时初值应设？",[891,4256,3054,4257],"a[0]","无穷大","用 a[0] 适配负数等情况。",{"id":47,"stem":4260,"options":4261,"answer":9,"explanation":4264},"要输出最值\"位置\"，需要额外维护？",[4166,4262,4263,3117],"下标变量","总和","记录当前最值对应的下标。",{"id":56,"stem":4266,"options":4267,"answer":9,"explanation":4272},"并列最值时\"输出最先出现的\"，更新条件应写？",[4268,4269,4270,4271],">",">=","\u003C","\u003C=","用 > 而非 >=，后出现的相等值不会覆盖前面的。",[4274],{"id":67,"title":4275,"prompt":4276,"inputFormat":1801,"outputFormat":4277,"samples":4278,"starterPy":4282,"starterCpp":4283,"hint":4284,"referencePy":4285,"referenceCpp":4286},"最小与最大","读入 n 和 n 个整数，输出其中的最小值和最大值（空格分隔）。","一行两个整数：最小值 最大值。",[4279],{"input":4280,"output":4281},"5\n3 1 4 1 5","1 5","n=int(input()); a=list(map(int,input().split()))\n# 找最小和最大\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],mn=0,mx=0; cin>>n;\n    for(int i=0;i\u003Cn;i++) cin>>a[i];\n    \u002F\u002F 找最值\n    return 0;\n}","先用 a[0] 初始化 mn、mx，再遍历比较。","n=int(input()); a=list(map(int,input().split()))\nprint(min(a), max(a))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],mn,mx; cin>>n; cin>>a[0]; mn=mx=a[0];\n    for(int i=1;i\u003Cn;i++){ cin>>a[i]; if(a[i]\u003Cmn) mn=a[i]; if(a[i]>mx) mx=a[i]; }\n    cout\u003C\u003Cmn\u003C\u003C\" \"\u003C\u003Cmx; return 0;\n}","找最值用擂台法，初值取 a[0]；要位置就额外维护下标；并列时按需求用 > 或 >= 决定保留哪一个。","04-A1-6","计数数组",{"id":4288,"title":4289,"level":3510,"module":3971,"seq":936,"goal":4291,"keywords":4292,"explanation":4295,"examples":4307,"exercises":4315,"summary":4348,"next":4349,"nextTitle":4350},"掌握计数数组（桶）：用数组下标表示\"值\"，数组元素表示\"出现次数\"，高效统计频率。",[4289,4293,4294],"频率","桶",[4296,4302],{"title":4297,"content":4298,"codePy":4299,"codeCpp":4300,"tip":4301},"1. 用下标当\"值\"","当数据范围不大（如成绩 0~100、字母 a~z）时，开一个数组 cnt，让 cnt[v] 表示值 v 出现了几次。每读到一个 v，就 cnt[v]++。这比每次都去遍历统计快得多。","cnt = [0]*101\nfor v in a: cnt[v] += 1","int cnt[101]={0}; for(int i=0;i\u003Cn;i++) cnt[a[i]]++;","计数数组大小 = 值的上限 + 1；下标越界要小心。",{"title":4303,"content":4304,"codePy":4305,"codeCpp":4306},"2. 字母也能当\"值\"","字符也能映射成下标：字母 c 的频率用 cnt[c-\"0\"] 或 cnt[c-97]（小写）。统计字符串里每种字符出现几次，计数数组最合适。","cnt=[0]*26\nfor ch in s: cnt[ord(ch)-97]+=1","int cnt[26]={0}; for(char ch:s) cnt[ch-\"a\"]++;",[4308],{"title":4309,"prompt":4310,"analysis":4311,"codePy":4312,"codeCpp":4313,"run":4314},"例 1：成绩分布","读入 n 和 n 个 0~100 的成绩，输出每个分数出现了几次（只输出次数>0 的分数，从小到大）。","先计数到 cnt[v]，再遍历 0~100 打印非零项。","n=int(input()); a=list(map(int,input().split()))\ncnt=[0]*101\nfor v in a: cnt[v]+=1\nfor v in range(101):\n    if cnt[v]: print(v, cnt[v])","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],cnt[101]={0}; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; cnt[a[i]]++; }\n    for(int v=0;v\u003C=100;v++) if(cnt[v]) cout\u003C\u003Cv\u003C\u003C\" \"\u003C\u003Ccnt[v]\u003C\u003Cendl;\n    return 0;\n}","5\n80 90 80 100 90\n80 2\n90 2\n100 1",{"choice":4316,"coding":4337},[4317,4323,4329],{"id":38,"stem":4318,"options":4319,"answer":9,"explanation":4322},"统计 0~100 的成绩，计数数组要开多大？",[816,4320,4321,2785],"101","102","下标 0~100 共 101 个。",{"id":47,"stem":4324,"options":4325,"answer":9,"explanation":4328},"小写字母 a~z 的计数数组大小是？",[3895,1984,4326,4327],"27","52","a~z 共 26 个字母。",{"id":56,"stem":4330,"options":4331,"answer":9,"explanation":4336},"字符 ch（小写）对应的计数下标是？",[4332,4333,4334,4335],"ch","ch-\"a\"","ch-97","ch-\"0\"","用 ch-\"a\" 把 \"a\"..\"z\" 映射到 0..25；ch-97 等价但 \"a\" 更直观。",[4338],{"id":67,"title":4339,"prompt":4340,"inputFormat":1801,"outputFormat":490,"samples":4341,"starterPy":4344,"starterCpp":4345,"hint":4346,"referencePy":4347,"referenceCpp":4345},"多数元素","读入 n 和 n 个 1~1000 的整数，输出出现次数超过 n\u002F2 的那个数（数据保证存在且唯一）。",[4342],{"input":4343,"output":759},"7\n2 2 1 2 2 3 2","n=int(input()); a=list(map(int,input().split()))\ncnt=[0]*1001\nfor v in a: cnt[v]+=1\nfor v in range(1001):\n    if cnt[v]>n\u002F\u002F2: print(v)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],cnt[1001]={0}; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; cnt[a[i]]++; }\n    for(int v=1;v\u003C=1000;v++) if(cnt[v]>n\u002F2){ cout\u003C\u003Cv; break; }\n    return 0;\n}","计数数组记录频率，再找超过半数的下标。","n=int(input()); a=list(map(int,input().split()))\ncnt=[0]*1001\nfor v in a: cnt[v]+=1\nfor v in range(1001):\n    if cnt[v]>n\u002F\u002F2: print(v); break","计数数组用\"下标=值、元素=次数\"把频率统计变成 O(1) 累加；值范围要小且非负才适用，字母用 ch-\"a\" 映射。","04-A1-7","标记数组",{"id":4349,"title":4350,"level":3510,"module":3971,"seq":998,"goal":4352,"keywords":4353,"explanation":4357,"examples":4369,"exercises":4377,"summary":4407,"next":4408,"nextTitle":4409},"掌握标记数组（visited\u002Fflag）：用布尔或整型数组记录\"某格\u002F某人\u002F某值是否已被处理过\"，防止重复或遗漏。",[4354,4355,4356],"标记","visited","去重",[4358,4363],{"title":4359,"content":4360,"codePy":4361,"codeCpp":4362},"1. 标记\"是否已经做过\"","很多题目需要判断\"这个位置来没来过\u002F这个数字出现过没\"。用 seen[v]=true 标记，下次遇到先看 marked 再决定。常用于去重、判重。","seen = [False]*101\nfor v in a:\n    if seen[v]: continue\n    seen[v] = True   # 首次出现才处理","bool seen[101]={false};\nfor(int i=0;i\u003Cn;i++){ if(seen[a[i]]) continue; seen[a[i]]=true; }",{"title":4364,"content":4365,"codePy":4366,"codeCpp":4367,"tip":4368},"2. 网格中的访问标记","在网格搜索\u002F模拟里，用 vis[r][c] 标记走过的格子，避免重复走同一格导致死循环。这是后面 BFS\u002FDFS 的前置基础。","vis = [[False]*m for _ in range(n)]\nvis[r][c] = True","bool vis[50][50]={false}; vis[r][c]=true;","使用前先初始化 False，访问即置 True。",[4370],{"title":4371,"prompt":4372,"analysis":4373,"codePy":4374,"codeCpp":4375,"run":4376},"例 1：去重输出","读入 n 和 n 个 0~100 的整数，按出现顺序输出每个\"首次出现\"的数（重复的不输出）。","用 seen 标记是否出现过，首次出现才打印并标记。","n=int(input()); a=list(map(int,input().split()))\nseen=[False]*101\nfor v in a:\n    if not seen[v]:\n        print(v, end=\" \"); seen[v]=True","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],seen[101]={0}; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i];\n        if(!seen[a[i]]){ cout\u003C\u003Ca[i]\u003C\u003C\" \"; seen[a[i]]=1; } }\n    return 0;\n}","8\n1 2 2 3 1 4 3 5\n1 2 3 4 5",{"choice":4378,"coding":4397},[4379,4385,4391],{"id":38,"stem":4380,"options":4381,"answer":9,"explanation":4384},"标记数组通常用来？",[4165,4382,3117,4383],"记录是否已处理\u002F访问","找最大","标记访问状态防止重复。",{"id":47,"stem":4386,"options":4387,"answer":9,"explanation":4390},"判重时\"首次出现\"应？",[4388,4389,505,4166],"直接输出","先看标记、未标记才输出并置标记","先看 seen，再决定是否处理。",{"id":56,"stem":4392,"options":4393,"answer":9,"explanation":4396},"网格 BFS 不标记 visited 会？",[1448,4394,4395,4136],"可能重复访问甚至死循环","结果更准","重复入队会指数爆炸或死循环。",[4398],{"id":67,"title":4399,"prompt":4400,"inputFormat":1801,"outputFormat":490,"samples":4401,"starterPy":4404,"starterCpp":4405,"hint":4406,"referencePy":4404,"referenceCpp":4405},"唯一值个数","读入 n 和 n 个 1~1000 的整数，输出其中\"不同值\"的个数。",[4402],{"input":4403,"output":1663},"7\n2 2 1 2 3 3 4","n=int(input()); a=list(map(int,input().split()))\nseen=[False]*1001; c=0\nfor v in a:\n    if not seen[v]: seen[v]=True; c+=1\nprint(c)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n,a[1000],seen[1001]={0},c=0; cin>>n;\n    for(int i=0;i\u003Cn;i++){ cin>>a[i]; if(!seen[a[i]]){ seen[a[i]]=1; c++; } }\n    cout\u003C\u003Cc; return 0;\n}","遇到未标记的值就计数并标记。","标记数组用布尔\u002F整型记录\"是否访问过\"，用于去重、判重、网格搜索防回环；使用前初始化、访问即置位。","27-A1-1","字符串遍历",{"id":2621,"title":2622,"level":2505,"module":3971,"seq":9,"goal":4411,"keywords":4412,"explanation":4414,"examples":4426,"exercises":4433,"summary":4464,"next":4465,"nextTitle":4466},"掌握一维前缀和：预处理 s[i]=a[0..i-1] 的和，使任意区间和 O(1) 查询。",[2622,4212,4413,1245],"预处理",[4415,4420],{"title":4416,"content":4417,"codePy":4418,"codeCpp":4419},"1. 构造与前缀查询","s[0]=0，s[i]=s[i-1]+a[i-1]；区间 [l,r] 的和 = s[r+1]-s[l]。","s = [0] * (n + 1)\nfor i in range(n): s[i + 1] = s[i] + a[i]\ndef sum_lr(l, r): return s[r + 1] - s[l]","\u002F\u002F s[i]=前i项和；sum(l,r)=s[r+1]-s[l]",{"title":4421,"content":4422,"codePy":4423,"codeCpp":4424,"tip":4425},"2. 应用","频繁求区间和的题目（如多次询问子数组和）先 O(n) 预处理，再 O(1) 回答。","# 多次询问区间和，总 O(n+q)","\u002F\u002F 预处理一次，查询 O(1)","区间和、区间平均都可用前缀和。",[4427],{"title":4428,"prompt":4429,"analysis":4430,"codePy":4431,"codeCpp":4432,"run":624},"例 1：区间和","a=[3,1,4,1,5]，求下标 [1,3]（含）的和。","1+4+1=6；s[4]-s[1]=(3+1+4+1)-3=6。","print(sum_lr(1, 3))","\u002F\u002F 6",{"choice":4434,"coding":4451},[4435,4443,4447],{"id":38,"stem":4436,"options":4437,"answer":9,"explanation":4442},"前缀和查询区间 [l,r] 的和公式是？",[4438,4439,4440,4441],"s[r]-s[l]","s[r+1]-s[l]","s[r]-s[l-1]","s[l]+s[r]","用 r+1 与 l 的差。",{"id":47,"stem":4444,"options":4445,"answer":9,"explanation":4446},"一维前缀和预处理复杂度？",[1245,2338,2339,1246],"一趟扫描。",{"id":56,"stem":4448,"options":4449,"answer":9,"explanation":4450},"前缀和查询复杂度？",[2338,1245,1246,2339],"一次减法。",[4452],{"id":67,"title":4453,"prompt":4454,"inputFormat":4455,"outputFormat":4456,"samples":4457,"starterPy":4460,"starterCpp":4461,"hint":4462,"referencePy":4463,"referenceCpp":4461},"区间和查询","输入 n（1≤n≤100000）、n 个整数和 q（1≤q≤100000）次询问 l r（0-indexed，闭区间），每行输出区间和。","第一行 n；第二行 n 个整数；第三行 q；其后 q 行每行 l r。","q 行，每行一个区间和。",[4458],{"input":4459,"output":4219},"5\n3 1 4 1 5\n2\n1 3\n0 4","n = int(input())\na = list(map(int, input().split()))\ns = [0] * (n + 1)\nfor i in range(n): s[i + 1] = s[i] + a[i]\nq = int(input())\nfor _ in range(q):\n    l, r = map(int, input().split())\n    # 输出区间和\n","#include \u003Ciostream>\nusing namespace std;\nlong long s[100005];\nint main(){ int n,q; cin>>n; for(int i=1;i\u003C=n;i++){ long long x; cin>>x; s[i]=s[i-1]+x; } cin>>q; while(q--){ int l,r; cin>>l>>r; cout\u003C\u003Cs[r+1]-s[l]\u003C\u003Cendl; } }","先构造前缀和数组，每次输出 s[r+1]-s[l]。","n = int(input())\na = list(map(int, input().split()))\ns = [0] * (n + 1)\nfor i in range(n): s[i + 1] = s[i] + a[i]\nq = int(input())\nfor _ in range(q):\n    l, r = map(int, input().split())\n    print(s[r + 1] - s[l])","一维前缀和用 s[i]=前 i 项和，使区间和查询 O(1)：sum(l,r)=s[r+1]-s[l]；适合频繁区间求和。","04-A2-2","二维前缀和",{"id":4465,"title":4466,"level":2505,"module":3971,"seq":63,"goal":4468,"keywords":4469,"explanation":4472,"examples":4484,"exercises":4491,"summary":4519,"next":4520,"nextTitle":4521},"掌握二维前缀和：预处理后任意子矩阵和 O(1) 查询，使用容斥公式。",[4466,4470,4471,1245],"子矩阵","容斥",[4473,4478],{"title":4474,"content":4475,"codePy":4476,"codeCpp":4477},"1. 构造","S[i][j] 表示以 (0,0) 到 (i-1,j-1) 为对角的小矩形和。递推 S[i][j]=a[i-1][j-1]+S[i-1][j]+S[i][j-1]-S[i-1][j-1]。","S = [[0]*(m+1) for _ in range(n+1)]\nfor i in range(1, n+1):\n    for j in range(1, m+1):\n        S[i][j] = a[i-1][j-1] + S[i-1][j] + S[i][j-1] - S[i-1][j-1]","\u002F\u002F 容斥构造二维前缀和",{"title":4479,"content":4480,"codePy":4481,"codeCpp":4482,"tip":4483},"2. 查询","子矩阵 (r1,c1)-(r2,c2) 的和 = S[r2+1][c2+1]-S[r1][c2+1]-S[r2+1][c1]+S[r1][c1]。","def q(r1, c1, r2, c2):\n    return S[r2+1][c2+1]-S[r1][c2+1]-S[r2+1][c1]+S[r1][c1]","\u002F\u002F 容斥查询","二维前缀和是二维区间求和的标配。",[4485],{"title":4486,"prompt":4487,"analysis":4488,"codePy":4489,"codeCpp":4490,"run":1663},"例 1：全 1 矩阵","2×2 全 1 矩阵，子矩阵（整块）和？","4 个 1，和为 4。","print(q(0,0,1,1))","\u002F\u002F 4",{"choice":4492,"coding":4507},[4493,4497,4501],{"id":38,"stem":4494,"options":4495,"answer":703,"explanation":4496},"二维前缀和查询子矩阵用了几次加减？",[892,759,890,1663],"容斥含 4 项两减一加。",{"id":47,"stem":4498,"options":4499,"answer":9,"explanation":4500},"二维前缀和查询复杂度？",[2338,1245,2339,1246],"常数次运算。",{"id":56,"stem":4502,"options":4503,"answer":9,"explanation":4506},"构造二维前缀和复杂度？",[2338,4504,2339,4505],"O(nm)","O(nm log nm)","遍历每个格子一次。",[4508],{"id":67,"title":4509,"prompt":4510,"inputFormat":4511,"outputFormat":4512,"samples":4513,"starterPy":4516,"starterCpp":4517,"hint":4518,"referencePy":4516,"referenceCpp":4517},"子矩阵和","输入 n m（1≤n,m≤500）、n×m 矩阵和 q 次询问 (r1,c1,r2,c2)（0-indexed，闭区间），输出每次子矩阵和。","第一行 n m；其后 n 行每行 m 个整数；一行 q；其后 q 行每行四个整数。","q 行，每行一个和。",[4514],{"input":4515,"output":1663},"2 2\n1 1\n1 1\n1\n0 0 1 1","n, m = map(int, input().split())\na = [list(map(int, input().split())) for _ in range(n)]\nS = [[0]*(m+1) for _ in range(n+1)]\nfor i in range(1, n+1):\n    for j in range(1, m+1):\n        S[i][j] = a[i-1][j-1] + S[i-1][j] + S[i][j-1] - S[i-1][j-1]\nq = int(input())\nfor _ in range(q):\n    r1, c1, r2, c2 = map(int, input().split())\n    print(S[r2+1][c2+1] - S[r1][c2+1] - S[r2+1][c1] + S[r1][c1])","#include \u003Ciostream>\nusing namespace std;\nlong long S[505][505];\nint main(){ int n,m; cin>>n>>m; for(int i=1;i\u003C=n;i++) for(int j=1;j\u003C=m;j++){ long long x; cin>>x; S[i][j]=x+S[i-1][j]+S[i][j-1]-S[i-1][j-1]; } int q; cin>>q; while(q--){ int r1,c1,r2,c2; cin>>r1>>c1>>r2>>c2; cout\u003C\u003CS[r2+1][c2+1]-S[r1][c2+1]-S[r2+1][c1]+S[r1][c1]\u003C\u003Cendl; } }","先容斥构造 S，再容斥查询。","二维前缀和用容斥预处理，子矩阵和 O(1) 查询：S[r2+1][c2+1]-S[r1][c2+1]-S[r2+1][c1]+S[r1][c1]。","04-A2-3","差分",{"id":4520,"title":4521,"level":2505,"module":3971,"seq":703,"goal":4523,"keywords":4524,"explanation":4527,"examples":4539,"exercises":4547,"summary":4581,"next":4582,"nextTitle":4583},"掌握一维差分：对区间加减的多次操作后，用 O(1) 标记、最后 O(n) 还原，避免每次 O(n) 修改。",[4521,4525,4526,2338],"区间加减","还原",[4528,4533],{"title":4529,"content":4530,"codePy":4531,"codeCpp":4532},"1. 思想","d[i]=a[i]-a[i-1]。对区间 [l,r] 整体加 v，只需 d[l]+=v，d[r+1]-=v；最后前缀和还原。","d = [0] * (n + 1)\ndef add(l, r, v): d[l] += v; d[r + 1] -= v\nfor _ in range(q): add(l, r, v)\ns = 0\nfor i in range(n): s += d[i]; a[i] += s","\u002F\u002F 区间加：d[l]+=v; d[r+1]-=v; 最后前缀和",{"title":4534,"content":4535,"codePy":4536,"codeCpp":4537,"tip":4538},"2. 适用","多次\"区间加\u002F减同一个值\"再统一查询，总复杂度 O(n+q)。","# 比每次遍历区间快得多","\u002F\u002F O(n+q)","差分是区间修改的标配技巧。",[4540],{"title":4541,"prompt":4542,"analysis":4543,"codePy":4544,"codeCpp":4545,"run":4546},"例 1：区间加","a=[0]*5，对 [1,3] 加 2，还原后数组？","d[1]+=2,d[4]-=2；前缀和得 [0,2,2,2,0]。","print(a)","\u002F\u002F 0 2 2 2 0","[0, 2, 2, 2, 0]",{"choice":4548,"coding":4568},[4549,4557,4561],{"id":38,"stem":4550,"options":4551,"answer":9,"explanation":4556},"对 [l,r] 加 v 的差分标记是？",[4552,4553,4554,4555],"d[l]+=v","d[l]+=v; d[r+1]-=v","d[r]+=v","d[l]-=v","两端打标记。",{"id":47,"stem":4558,"options":4559,"answer":9,"explanation":4560},"差分还原需要？",[3117,2622,2509,3259],"前缀和还原。",{"id":56,"stem":4562,"options":4563,"answer":9,"explanation":4567},"多次区间加再查询的总复杂度？",[4564,4565,2339,4566],"O(nq)","O(n+q)","O(q log n)","O(n+q)。",[4569],{"id":67,"title":4570,"prompt":4571,"inputFormat":4572,"outputFormat":4573,"samples":4574,"starterPy":4578,"starterCpp":4579,"hint":4580,"referencePy":4578,"referenceCpp":4579},"区间加还原","输入 n（1≤n≤100000）、q（1≤q≤100000）次区间加操作 l r v（0-indexed，闭区间），初始数组全 0，输出最终数组（空格分隔）。","第一行 n q；其后 q 行每行 l r v。","一行 n 个整数。",[4575],{"input":4576,"output":4577},"5 1\n1 3 2","0 2 2 2 0","n, q = map(int, input().split())\nd = [0] * (n + 1)\nfor _ in range(q):\n    l, r, v = map(int, input().split())\n    d[l] += v; d[r + 1] -= v\ns = 0\nres = []\nfor i in range(n):\n    s += d[i]; res.append(str(s))\nprint(*res)","#include \u003Ciostream>\nusing namespace std;\nlong long d[100005];\nint main(){ int n,q; cin>>n>>q; while(q--){ int l,r,v; cin>>l>>r>>v; d[l]+=v; d[r+1]-=v; } long long s=0; for(int i=0;i\u003Cn;i++){ s+=d[i]; cout\u003C\u003Cs\u003C\u003C\" \"; } }","d[l]+=v, d[r+1]-=v，再做一遍前缀和。","差分用 d[l]+=v, d[r+1]-=v 标记区间加减，最后前缀和 O(n) 还原；适合多次区间修改。","04-A2-4","二维差分",{"id":4582,"title":4583,"level":2505,"module":3971,"seq":770,"goal":4585,"keywords":4586,"explanation":4588,"examples":4600,"exercises":4608,"summary":4639,"next":4640,"nextTitle":4641},"掌握二维差分：对子矩阵整体加减，用 O(1) 标记、最后 O(nm) 还原。",[4583,4587,4471,4526],"子矩阵加减",[4589,4594],{"title":4590,"content":4591,"codePy":4592,"codeCpp":4593},"1. 标记","对子矩阵 (r1,c1)-(r2,c2) 加 v，做四点更新：d[r1][c1]+=v, d[r2+1][c1]-=v, d[r1][c2+1]-=v, d[r2+1][c2+1]+=v。","def add(r1, c1, r2, c2, v):\n    d[r1][c1] += v; d[r2+1][c1] -= v\n    d[r1][c2+1] -= v; d[r2+1][c2+1] += v","\u002F\u002F 四点更新二维差分",{"title":4595,"content":4596,"codePy":4597,"codeCpp":4598,"tip":4599},"2. 还原","对 d 做二维前缀和（两遍累加）得到每个格子的累加值。","for i in range(n):\n    for j in range(m):\n        if i: d[i][j] += d[i-1][j]\n        if j: d[i][j] += d[i][j-1]\n        if i and j: d[i][j] -= d[i-1][j-1]","\u002F\u002F 二维前缀和还原","二维差分对应二维前缀和的逆操作。",[4601],{"title":4602,"prompt":4603,"analysis":4604,"codePy":4605,"codeCpp":4606,"run":4607},"例 1：整块加 1","2×2 矩阵对全部加 1，还原后？","每格都 +1。","print(\"全 1\")","\u002F\u002F 全 1","全 1",{"choice":4609,"coding":4626},[4610,4614,4619],{"id":38,"stem":4611,"options":4612,"answer":63,"explanation":4613},"二维差分标记一个子矩阵用几次更新？",[892,759,1663,148],"四点更新。",{"id":47,"stem":4615,"options":4616,"answer":9,"explanation":4618},"二维差分还原用？",[3117,4466,2509,4617],"队列","两遍前缀和。",{"id":56,"stem":4620,"options":4621,"answer":9,"explanation":4625},"二维差分适合？",[4622,4623,3117,4624],"单点修改","子矩阵整体加减","查找","子矩阵加减。",[4627],{"id":67,"title":4628,"prompt":4629,"inputFormat":4630,"outputFormat":4631,"samples":4632,"starterPy":4636,"starterCpp":4637,"hint":4638,"referencePy":4636,"referenceCpp":4637},"子矩阵加","输入 n m（1≤n,m≤300）、q 次子矩阵加 (r1,c1,r2,c2,v)（0-indexed，闭区间），初始全 0，输出最终矩阵（n 行）。","第一行 n m q；其后 q 行每行五个整数。","n 行，每行 m 个整数空格分隔。",[4633],{"input":4634,"output":4635},"2 2 1\n0 0 1 1 1","1 1\n1 1","n, m, q = map(int, input().split())\nd = [[0]*(m+2) for _ in range(n+2)]\nfor _ in range(q):\n    r1, c1, r2, c2, v = map(int, input().split())\n    d[r1][c1] += v; d[r2+1][c1] -= v\n    d[r1][c2+1] -= v; d[r2+1][c2+1] += v\nfor i in range(n):\n    for j in range(m):\n        if i: d[i][j] += d[i-1][j]\n        if j: d[i][j] += d[i][j-1]\n        if i and j: d[i][j] -= d[i-1][j-1]\n    print(*d[i][:m])","#include \u003Ciostream>\nusing namespace std;\nlong long d[302][302];\nint main(){ int n,m,q; cin>>n>>m>>q; while(q--){ int r1,c1,r2,c2,v; cin>>r1>>c1>>r2>>c2>>v; d[r1][c1]+=v; d[r2+1][c1]-=v; d[r1][c2+1]-=v; d[r2+1][c2+1]+=v; } for(int i=0;i\u003Cn;i++){ for(int j=0;j\u003Cm;j++){ if(i) d[i][j]+=d[i-1][j]; if(j) d[i][j]+=d[i][j-1]; if(i&&j) d[i][j]-=d[i-1][j-1]; cout\u003C\u003Cd[i][j]\u003C\u003C\" \"; } cout\u003C\u003Cendl; } }","先四点更新 d，再做两遍二维前缀和。","二维差分用四点更新标记子矩阵加减，再做二维前缀和还原；适合多次子矩阵整体修改。","04-A2-5","前缀最值",{"id":4640,"title":4641,"level":2505,"module":3971,"seq":838,"goal":4643,"keywords":4644,"explanation":4647,"examples":4659,"exercises":4667,"summary":4699,"next":4700,"nextTitle":4701},"掌握前缀最值：预处理前缀的最大值\u002F最小值，支持 O(1) 查询\"从开头到某位置\"的极值。",[4641,4645,4646,4413],"前缀最大值","RMQ",[4648,4653],{"title":4649,"content":4650,"codePy":4651,"codeCpp":4652},"1. 前缀最大值","pmax[i]=max(pmax[i-1], a[i])；查询 [0,r] 最大值即 pmax[r]。","pmax = [0] * n\npmax[0] = a[0]\nfor i in range(1, n): pmax[i] = max(pmax[i-1], a[i])","\u002F\u002F pmax[i]=max(pmax[i-1],a[i])",{"title":4654,"content":4655,"codePy":4656,"codeCpp":4657,"tip":4658},"2. 用途","常与\"从开头到 i\"的约束结合（如接雨水、最大子段前缀）。","# 求 [0,r] 最大值 O(1)","\u002F\u002F 预处理后 O(1)","前缀最值不能做区间最值（那要用线段树\u002FST表），只能做\"前缀\"段。",[4660],{"title":4661,"prompt":4662,"analysis":4663,"codePy":4664,"codeCpp":4665,"run":4666},"例 1：前缀最大","a=[3,1,4,1,5]，前缀最大值序列？","[3,3,4,4,5]。","print(pmax)","\u002F\u002F 3 3 4 4 5","[3, 3, 4, 4, 5]",{"choice":4668,"coding":4687},[4669,4677,4681],{"id":38,"stem":4670,"options":4671,"answer":9,"explanation":4676},"前缀最大值的递推？",[4672,4673,4674,4675],"pmax[i]=a[i]","pmax[i]=max(pmax[i-1],a[i])","pmax[i]=min","pmax[i]=pmax[i-1]","取左前缀与当前较大者。",{"id":47,"stem":4678,"options":4679,"answer":9,"explanation":4680},"前缀最值查询复杂度？",[2338,1245,1246,2339],"直接读数组。",{"id":56,"stem":4682,"options":4683,"answer":9,"explanation":4686},"前缀最值能查任意区间最值吗？",[1367,4684,4685,1865],"不能（只能前缀段）","能但慢","任意区间要用 RMQ 结构。",[4688],{"id":67,"title":4645,"prompt":4689,"inputFormat":4690,"outputFormat":4691,"samples":4692,"starterPy":4696,"starterCpp":4697,"hint":4698,"referencePy":4696,"referenceCpp":4697},"输入 n（1≤n≤100000）和 n 个整数，再输入 q 次询问 r（0-indexed），输出 a[0..r] 的最大值。","第一行 n；第二行 n 个整数；第三行 q；其后 q 行每行 r。","q 行，每行一个最大值。",[4693],{"input":4694,"output":4695},"5\n3 1 4 1 5\n3\n0\n2\n4","3\n4\n5","n = int(input())\na = list(map(int, input().split()))\npmax = [0]*n; pmax[0] = a[0]\nfor i in range(1, n): pmax[i] = max(pmax[i-1], a[i])\nq = int(input())\nfor _ in range(q):\n    r = int(input()); print(pmax[r])","#include \u003Ciostream>\nusing namespace std;\nint a[100005], pmax[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ cin>>a[i]; pmax[i]= i? max(pmax[i-1],a[i]) : a[i]; } int q; cin>>q; while(q--){ int r; cin>>r; cout\u003C\u003Cpmax[r]\u003C\u003Cendl; } }","先构造前缀最大数组，再直接回答。","前缀最值用 pmax[i]=max(pmax[i-1],a[i]) 预处理，使\"开头到 i\"的极值 O(1) 查询（注意只能前缀段，非任意区间）。","04-A2-6","后缀最值",{"id":4700,"title":4701,"level":2505,"module":3971,"seq":936,"goal":4703,"keywords":4704,"explanation":4708,"examples":4720,"exercises":4728,"summary":4761,"next":4762,"nextTitle":4763},"掌握后缀最值：从某位置到结尾的极值，常与前缀最值配合解决\"两侧约束\"问题。",[4701,4705,4706,4707],"后缀最大值","对称","约束",[4709,4714],{"title":4710,"content":4711,"codePy":4712,"codeCpp":4713},"1. 后缀最大值","smax[i]=max(a[i], smax[i+1])，从右往左扫。","smax = [0] * n\nsmax[n-1] = a[n-1]\nfor i in range(n-2, -1, -1): smax[i] = max(a[i], smax[i+1])","\u002F\u002F smax[i]=max(a[i],smax[i+1])",{"title":4715,"content":4716,"codePy":4717,"codeCpp":4718,"tip":4719},"2. 与前缀配合","很多题需要\"左边最大且右边最大\"等，前后缀一起预处理。","# 例：某位置左右最大值都 >= 它","\u002F\u002F 前后缀结合","前后缀最值是接雨水等题的关键。",[4721],{"title":4722,"prompt":4723,"analysis":4724,"codePy":4725,"codeCpp":4726,"run":4727},"例 1：后缀最大","a=[3,1,4,1,5]，后缀最大值序列？","从右：5,5,5,5,5。","print(smax)","\u002F\u002F 5 5 5 5 5","[5, 5, 5, 5, 5]",{"choice":4729,"coding":4750},[4730,4737,4745],{"id":38,"stem":4731,"options":4732,"answer":9,"explanation":4736},"后缀最大值的递推方向？",[4733,4734,3952,4735],"从左往右","从右往左","不用递推","依赖右侧，从右扫。",{"id":47,"stem":4738,"options":4739,"answer":9,"explanation":4744},"smax[i] 表示？",[4740,4741,4742,4743],"开头到 i 最大","i 到结尾最大","全局最大","最小值","后缀段最值。",{"id":56,"stem":4746,"options":4747,"answer":9,"explanation":4749},"前后缀最值常一起用于？",[3117,4748,2509,3259],"两侧约束问题（如接雨水）","左右两侧条件。",[4751],{"id":67,"title":4705,"prompt":4752,"inputFormat":4753,"outputFormat":4691,"samples":4754,"starterPy":4758,"starterCpp":4759,"hint":4760,"referencePy":4758,"referenceCpp":4759},"输入 n（1≤n≤100000）和 n 个整数，再输入 q 次询问 i（0-indexed），输出 a[i..n-1] 的最大值。","第一行 n；第二行 n 个整数；第三行 q；其后 q 行每行 i。",[4755],{"input":4756,"output":4757},"5\n3 1 4 1 5\n2\n0\n4","5\n5","n = int(input())\na = list(map(int, input().split()))\nsmax = [0]*n; smax[n-1] = a[n-1]\nfor i in range(n-2, -1, -1): smax[i] = max(a[i], smax[i+1])\nq = int(input())\nfor _ in range(q):\n    i = int(input()); print(smax[i])","#include \u003Ciostream>\nusing namespace std;\nint a[100005], smax[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; smax[n-1]=a[n-1]; for(int i=n-2;i>=0;i--) smax[i]=max(a[i],smax[i+1]); int q; cin>>q; while(q--){ int i; cin>>i; cout\u003C\u003Csmax[i]\u003C\u003Cendl; } }","从右往左构造后缀最大，再回答。","后缀最值从右往左预处理 smax[i]=max(a[i],smax[i+1])；常与前缀最值配合处理两侧约束（如接雨水）。","04-A2-7","前后缀统计",{"id":4762,"title":4763,"level":2505,"module":3971,"seq":998,"goal":4765,"keywords":4766,"explanation":4769,"examples":4781,"exercises":4787,"summary":4817,"next":4818,"nextTitle":4819},"综合运用前缀与后缀预处理，解决\"左侧\u002F右侧满足条件的个数或极值\"类问题。",[4767,4166,4768,4413],"前后缀","左右",[4770,4775],{"title":4771,"content":4772,"codePy":4773,"codeCpp":4774},"1. 左右计数","如\"左边比当前小的个数\"\"右边第一个更大元素\"，可分别从左、右扫并维护状态。","left = [0]*n\nseen = set()\nfor i, x in enumerate(a):\n    left[i] = len(seen); seen.add(x)","\u002F\u002F 从左扫维护已见集合计数",{"title":4776,"content":4777,"codePy":4778,"codeCpp":4779,"tip":4780},"2. 经典应用","接雨水需要每个位置\"左边最大\"和\"右边最大\"取较小值减高度。","for i in range(n):\n    water += max(0, min(pmax[i], smax[i]) - a[i])","\u002F\u002F 前后缀最值求接雨水","这类\"两侧看\"的问题，先想前后缀预处理。",[4782],{"title":4783,"prompt":4784,"analysis":4785,"codePy":4786,"codeCpp":4432,"run":624},"例 1：接雨水思路","高度为 [0,1,0,2,1,0,1,3,2,1,2,1]，能接多少雨水？","每个位置取左右最大较小值减高度累加 = 6。","print(6)",{"choice":4788,"coding":4805},[4789,4794,4800],{"id":38,"stem":4790,"options":4791,"answer":607,"explanation":4793},"接雨水需要每个位置的？",[4792,2622,4521,3117],"前后缀最大","左右最大较小值。",{"id":47,"stem":4795,"options":4796,"answer":9,"explanation":4799},"\"左边比当前小的个数\"用？",[4797,4798,2509,4617],"后缀","从左扫计数","从左维护状态。",{"id":56,"stem":4801,"options":4802,"answer":9,"explanation":4804},"前后缀统计的目的是？",[3117,4803,2509,3259],"O(1) 回答两侧条件","预处理后快速回答。",[4806],{"id":67,"title":4807,"prompt":4808,"inputFormat":4809,"outputFormat":4810,"samples":4811,"starterPy":4814,"starterCpp":4815,"hint":4816,"referencePy":4814,"referenceCpp":4815},"接雨水","输入 n（1≤n≤100000）和高度数组，输出能接的雨水总量（每个位置接水量 = max(0, min(左最大,右最大)-高度)）。","第一行 n；第二行 n 个整数（高度）。","一行整数雨水总量。",[4812],{"input":4813,"output":624},"12\n0 1 0 2 1 0 1 3 2 1 2 1","n = int(input())\na = list(map(int, input().split()))\npmax = [0]*n; pmax[0]=a[0]\nfor i in range(1,n): pmax[i]=max(pmax[i-1],a[i])\nsmax = [0]*n; smax[n-1]=a[n-1]\nfor i in range(n-2,-1,-1): smax[i]=max(a[i],smax[i+1])\nans = sum(max(0, min(pmax[i],smax[i])-a[i]) for i in range(n))\nprint(ans)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[100005], pmax[100005], smax[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; pmax[0]=a[0]; for(int i=1;i\u003Cn;i++) pmax[i]=max(pmax[i-1],a[i]); smax[n-1]=a[n-1]; for(int i=n-2;i>=0;i--) smax[i]=max(a[i],smax[i+1]); long long ans=0; for(int i=0;i\u003Cn;i++) ans+=max(0,min(pmax[i],smax[i])-a[i]); cout\u003C\u003Cans; }","构造前后缀最大，逐位累加 min(左最大,右最大)-高度。","前后缀统计同时从左、右预处理，处理\"左侧\u002F右侧极值或个数\"问题，如接雨水、左右第一个更大元素。","04-A2-8","离线预处理",{"id":4818,"title":4819,"level":2505,"module":3971,"seq":1066,"goal":4821,"keywords":4822,"explanation":4826,"examples":4838,"exercises":4846,"summary":4880,"next":4881,"nextTitle":2628},"理解离线思想：先把所有询问收集起来，按某种顺序统一处理（常配合排序或前缀结构），降低总复杂度。",[4823,4413,4824,4825],"离线","排序询问","批量",[4827,4832],{"title":4828,"content":4829,"codePy":4830,"codeCpp":4831},"1. 离线 vs 在线","在线是来一个答一个；离线是先收齐所有询问，统一安排处理顺序（如按右端点排序）。","qs = [(l, r, i) for i in range(q)]\nqs.sort(key=lambda x: x[1])   # 按右端点","\u002F\u002F 询问按右端点排序后统一处理",{"title":4833,"content":4834,"codePy":4835,"codeCpp":4836,"tip":4837},"2. 优势","可以把\"动态\"问题转为\"累积\"问题，用前缀和\u002F树状数组等高效回答。","# 按右端点递增，边加边答","\u002F\u002F 离线降低复杂度","离线常配合\"按右端点排序 + 滚动维护\"。",[4839],{"title":4840,"prompt":4841,"analysis":4842,"codePy":4843,"codeCpp":4844,"run":4845},"例 1：区间和离线","多个区间和询问，离线按右端点排序后用滚动前缀和。","右端点递增时前缀和只增不减，边扩边答。","print(\"离线更优\")","\u002F\u002F 批量处理","离线更优",{"choice":4847,"coding":4868},[4848,4856,4861],{"id":38,"stem":4849,"options":4850,"answer":9,"explanation":4855},"离线的核心做法是？",[4851,4852,4853,4854],"来一个答一个","先收齐询问再批量处理","用递归","用栈","批量统一处理。",{"id":47,"stem":4857,"options":4858,"answer":9,"explanation":4860},"离线常配合？",[1568,4859,3259,3118],"按关键字排序","排序后顺序处理。",{"id":56,"stem":4862,"options":4863,"answer":9,"explanation":4867},"离线相比在线的好处？",[4864,4865,3823,4866],"更省内存","常能降低总复杂度","不用排序","把动态转累积。",[4869],{"id":67,"title":4870,"prompt":4871,"inputFormat":4872,"outputFormat":4873,"samples":4874,"starterPy":4877,"starterCpp":4878,"hint":4879,"referencePy":4877,"referenceCpp":4878},"离线区间和","输入 n（1≤n≤100000）、数组、q（1≤q≤100000）个区间询问 (l,r)（0-indexed，闭区间），按输入顺序输出各区间和（请使用离线：按右端点排序后用前缀和数组统一回答）。","第一行 n q；第二行 n 个整数；其后 q 行每行 l r。","q 行，每行一个区间和（顺序同输入）。",[4875],{"input":4876,"output":4219},"5 2\n3 1 4 1 5\n1 3\n0 4","n, q = map(int, input().split())\na = list(map(int, input().split()))\nqs = []\nfor i in range(q):\n    l, r = map(int, input().split()); qs.append((r, l, i))\nqs.sort()\npref = [0]*(n+1)\nfor k in range(n): pref[k+1]=pref[k]+a[k]\nans=[0]*q\nfor r, l, i in qs:\n    ans[i] = pref[r+1]-pref[l]\nfor v in ans: print(v)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Q{ int r,l,i; };\nlong long pref[100005];\nQ qs[100005];\nint main(){ int n,q; cin>>n>>q; for(int i=0;i\u003Cn;i++){ long long x; cin>>x; pref[i+1]=pref[i]+x; } for(int i=0;i\u003Cq;i++){ cin>>qs[i].l>>qs[i].r; qs[i].i=i; } sort(qs,qs+q,[](Q a,Q b){ return a.r\u003Cb.r; }); for(int i=0;i\u003Cq;i++) cout\u003C\u003Cpref[qs[i].r+1]-pref[qs[i].l]\u003C\u003Cendl; }","按右端点排序后，用前缀和数组统一回答（保持原顺序输出）。","离线预处理先收齐所有询问再统一处理（常按右端点排序），把动态查询转为累积计算，降低总复杂度。","05-A2-1",{"id":4883,"title":4884,"level":4885,"module":3117,"seq":9,"goal":4886,"keywords":4887,"explanation":4890,"examples":4902,"exercises":4910,"summary":4947,"next":4948,"nextTitle":4949},"05-A1-1","冒泡排序","Lv.4","理解冒泡排序：反复比较相邻元素，把\"大的\"一步步换到右边，像气泡上浮一样把最大值冒到末尾。",[4884,4888,4889,2339],"相邻交换","稳定",[4891,4896],{"title":4892,"content":4893,"codePy":4894,"codeCpp":4895},"1. 每一轮把当前最大值\"冒\"到末尾","从左边开始，依次比较相邻两个元素 a[j] 与 a[j+1]，若前者大于后者就交换。这样一轮下来，最大的元素一定被换到了最右边。下一轮只需处理左边 n-1 个元素。","def bubble(a):\n    n = len(a)\n    for i in range(n):\n        for j in range(n - 1 - i):\n            if a[j] > a[j + 1]:\n                a[j], a[j + 1] = a[j + 1], a[j]\n    return a","void bubble(int a[], int n) {\n    for (int i = 0; i \u003C n; i++)\n        for (int j = 0; j \u003C n - 1 - i; j++)\n            if (a[j] > a[j + 1]) {\n                int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;\n            }\n}",{"title":4897,"content":4898,"codePy":4899,"codeCpp":4900,"tip":4901},"2. 优化：某轮没交换就提前结束","如果某一整轮都没有发生交换，说明数组已经有序，可以提前停止。这能让最好情况（已有序）的时间复杂度降到 O(n)。","def bubble(a):\n    n = len(a)\n    for i in range(n):\n        ok = True\n        for j in range(n - 1 - i):\n            if a[j] > a[j + 1]:\n                a[j], a[j + 1] = a[j + 1], a[j]; ok = False\n        if ok: break\n    return a","void bubble(int a[], int n) {\n    for (int i = 0; i \u003C n; i++) {\n        bool ok = true;\n        for (int j = 0; j \u003C n - 1 - i; j++)\n            if (a[j] > a[j + 1]) { int t = a[j]; a[j] = a[j+1]; a[j+1] = t; ok = false; }\n        if (ok) break;\n    }\n}","冒泡排序是\"稳定\"的：相等元素不会被交换，相对顺序保持不变。",[4903],{"title":4904,"prompt":4905,"analysis":4906,"codePy":4907,"codeCpp":4908,"run":4909},"例 1：手写一轮冒泡","数组 [5, 3, 8, 1] 经过一轮冒泡（从左到右相邻比较交换）后变成什么？","比较 5>3 交换→[3,5,8,1]；5>8 不换；8>1 交换→[3,5,1,8]。最大值 8 已到末尾。","a = [5, 3, 8, 1]\nfor j in range(len(a) - 1):\n    if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j]\nprint(a)","int a[] = {5,3,8,1};\nfor (int j = 0; j \u003C 3; j++)\n    if (a[j] > a[j+1]) { int t = a[j]; a[j] = a[j+1]; a[j+1] = t; }","[3, 5, 1, 8]",{"choice":4911,"coding":4934},[4912,4918,4926],{"id":38,"stem":4913,"options":4914,"answer":9,"explanation":4917},"冒泡排序每一轮会把什么元素移到正确位置？",[4743,4202,4915,4916],"随机一个","中位数","一轮后最大值被冒到最右端。",{"id":47,"stem":4919,"options":4920,"answer":607,"explanation":4925},"对 [3,2,1] 做一轮冒泡后，数组变为？",[4921,4922,4923,4924],"[2,1,3]","[1,2,3]","[3,1,2]","[2,3,1]","3>2 交换→[2,3,1]；3>1 交换→[2,1,3]，最大值 3 到末尾。",{"id":56,"stem":4927,"options":4928,"answer":9,"explanation":4933},"关于冒泡排序的稳定性，正确的是？",[4929,4930,4931,4932],"不稳定","稳定（相等不交换）","看实现","从不稳定","相等元素不交换，相对顺序保持，稳定。",[4935],{"id":67,"title":4884,"prompt":4936,"inputFormat":1801,"outputFormat":4937,"samples":4938,"starterPy":4942,"starterCpp":4943,"hint":4944,"referencePy":4945,"referenceCpp":4946},"输入 n（1≤n≤100）和 n 个整数（≤10^9），用冒泡排序将它们从小到大排序并输出。","一行从小到大排序后的 n 个整数，空格分隔。",[4939],{"input":4940,"output":4941},"4\n5 3 8 1","1 3 5 8","a = list(map(int, input().split()))\n# 冒泡排序\n","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    \u002F\u002F 冒泡\n    return 0;\n}","两层循环，内层比较相邻并交换；可加\"某轮无交换则 break\"优化。","a = list(map(int, input().split()))\nn = len(a)\nfor i in range(n):\n    for j in range(n - 1 - i):\n        if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j]\nprint(*a)","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    for(int i=0;i\u003Cn;i++)\n        for(int j=0;j\u003Cn-1-i;j++)\n            if(a[j]>a[j+1]){ int t=a[j]; a[j]=a[j+1]; a[j+1]=t; }\n    for(int i=0;i\u003Cn;i++) cout \u003C\u003C a[i] \u003C\u003C \" \";\n    return 0;\n}","冒泡排序反复比较相邻元素并交换，把最大值逐轮\"冒\"到末端；相等元素不交换，是稳定排序，时间复杂度 O(n²)，可用\"无交换即停止\"提前结束。","05-A1-2","选择排序",{"id":4948,"title":4949,"level":4885,"module":3117,"seq":63,"goal":4951,"keywords":4952,"explanation":4954,"examples":4966,"exercises":4974,"summary":5003,"next":5004,"nextTitle":5005},"理解选择排序：每轮从\"未排序部分\"挑出最小值，放到已排序部分的末尾。",[4949,4953,4929,2339],"选最小",[4955,4960],{"title":4956,"content":4957,"codePy":4958,"codeCpp":4959},"1. 每轮选出未排序区的最小值","第 i 轮，在 a[i..n-1] 里找最小元素的下标 m，然后把 a[m] 与 a[i] 交换。这样前 i+1 个位置就排好了序。重复 n-1 轮即可。","def select(a):\n    n = len(a)\n    for i in range(n):\n        m = i\n        for j in range(i + 1, n):\n            if a[j] \u003C a[m]: m = j\n        a[i], a[m] = a[m], a[i]\n    return a","void select(int a[], int n) {\n    for (int i = 0; i \u003C n; i++) {\n        int m = i;\n        for (int j = i + 1; j \u003C n; j++) if (a[j] \u003C a[m]) m = j;\n        int t = a[i]; a[i] = a[m]; a[m] = t;\n    }\n}",{"title":4961,"content":4962,"codePy":4963,"codeCpp":4964,"tip":4965},"2. 与冒泡的区别：交换次数少","冒泡是\"相邻不断交换\"，选择是\"先找最小、最后换一次\"。选择排序每轮最多交换 1 次，交换次数远少于冒泡，但比较次数仍是 O(n²)，且不稳定（可能把相等元素的相对位置打乱）。","# 冒泡：多次相邻交换；选择：每轮最多一次交换\ndef select(a):\n    n = len(a)\n    for i in range(n):\n        m = min(range(i, n), key=lambda k: a[k])\n        a[i], a[m] = a[m], a[i]\n    return a","\u002F\u002F 选择排序每轮定位最小值后只交换一次\nint m = i; for (int j=i+1;j\u003Cn;j++) if (a[j]\u003Ca[m]) m=j;\nswap(a[i], a[m]);","想要\"稳定\"就用插入排序；选择排序胜在交换少、实现简单。",[4967],{"title":4968,"prompt":4969,"analysis":4970,"codePy":4971,"codeCpp":4972,"run":4973},"例 1：手写一轮选择","数组 [5, 3, 8, 1] 做一轮选择排序（找最小值换到最左）后变成什么？","未排序区 [5,3,8,1] 最小是 1（下标3），与 a[0]=5 交换→[1,3,8,5]。","a = [5, 3, 8, 1]\nm = a.index(min(a))\na[0], a[m] = a[m], a[0]\nprint(a)","int a[]={5,3,8,1}; int m=0;\nfor(int j=1;j\u003C4;j++) if(a[j]\u003Ca[m]) m=j;\nint t=a[0]; a[0]=a[m]; a[m]=t;","[1, 3, 8, 5]",{"choice":4975,"coding":4992},[4976,4982,4986],{"id":38,"stem":4977,"options":4978,"answer":9,"explanation":4981},"选择排序每轮的操作是？",[4888,4979,4980,2509],"找最小并换到前面","随机排","每轮在未排序区选最小放到前端。",{"id":47,"stem":4983,"options":4984,"answer":9,"explanation":4985},"选择排序的时间复杂度约为？",[2338,2339,2564,1245],"两层循环比较，O(n²)。",{"id":56,"stem":4987,"options":4988,"answer":9,"explanation":4991},"关于选择排序稳定性，正确的是？",[4889,4929,4989,4990],"与冒泡相同","绝对稳定","交换可能跨过相等元素，通常不稳定。",[4993],{"id":67,"title":4949,"prompt":4994,"inputFormat":1801,"outputFormat":4995,"samples":4996,"starterPy":4998,"starterCpp":4999,"hint":5000,"referencePy":5001,"referenceCpp":5002},"输入 n（1≤n≤100）和 n 个整数（≤10^9），用选择排序从小到大排序并输出。","一行升序排列的整数，空格分隔。",[4997],{"input":4940,"output":4941},"a = list(map(int, input().split()))\n# 选择排序\n","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    \u002F\u002F 选择\n    return 0;\n}","i 从 0 到 n-1，在 a[i..n-1] 中找最小下标 m，交换 a[i] 与 a[m]。","a = list(map(int, input().split()))\nn = len(a)\nfor i in range(n):\n    m = i\n    for j in range(i + 1, n):\n        if a[j] \u003C a[m]: m = j\n    a[i], a[m] = a[m], a[i]\nprint(*a)","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    for(int i=0;i\u003Cn;i++){\n        int m=i; for(int j=i+1;j\u003Cn;j++) if(a[j]\u003Ca[m]) m=j;\n        int t=a[i]; a[i]=a[m]; a[m]=t;\n    }\n    for(int i=0;i\u003Cn;i++) cout \u003C\u003C a[i] \u003C\u003C \" \";\n    return 0;\n}","选择排序每轮在剩余未排序区挑最小值放到前端，交换次数少（每轮至多 1 次），但比较仍是 O(n²)，且一般不稳定。","05-A1-3","插入排序",{"id":5004,"title":5005,"level":4885,"module":3117,"seq":703,"goal":5007,"keywords":5008,"explanation":5010,"examples":5022,"exercises":5030,"summary":5060,"next":5061,"nextTitle":5062},"理解插入排序：像整理手牌一样，把每个新元素插入到前面已排好序的部分中的正确位置。",[5005,5009,4889,2339],"已排序前缀",[5011,5016],{"title":5012,"content":5013,"codePy":5014,"codeCpp":5015},"1. 把当前元素\"插\"进已排序前缀","假设 a[0..i-1] 已经有序，取出 a[i] 记为 x，从右往左把比 x 大的元素依次右移，最后把 x 放到空出来的位置。重复 i=1..n-1。","def insert(a):\n    for i in range(1, len(a)):\n        x = a[i]; j = i - 1\n        while j >= 0 and a[j] > x:\n            a[j + 1] = a[j]; j -= 1\n        a[j + 1] = x\n    return a","void insert(int a[], int n) {\n    for (int i = 1; i \u003C n; i++) {\n        int x = a[i], j = i - 1;\n        while (j >= 0 && a[j] > x) { a[j + 1] = a[j]; j--; }\n        a[j + 1] = x;\n    }\n}",{"title":5017,"content":5018,"codePy":5019,"codeCpp":5020,"tip":5021},"2. 适合\"基本有序\"的数据","当数据已经接近有序时，插入排序很少需要移动元素，接近 O(n)；很多高级排序（如归并、快排）在小数组时会切换到插入排序。它也是稳定排序。","# 已接近有序时，内层 while 很少执行\nfor i in range(1, len(a)):\n    x = a[i]; j = i - 1\n    while j >= 0 and a[j] > x:\n        a[j + 1] = a[j]; j -= 1\n    a[j + 1] = x","\u002F\u002F 近乎有序时 j 几乎不移动，效率很高\nwhile (j >= 0 && a[j] > x) { a[j + 1] = a[j]; j--; }","插入排序\"边读边插\"，很适合在线（流式）排序。",[5023],{"title":5024,"prompt":5025,"analysis":5026,"codePy":5027,"codeCpp":5028,"run":5029},"例 1：插入一个元素","已排序前缀 [1,3,5,8]，要把 4 插入，结果是什么？","从右往左：8>4 右移→[1,3,5,_,8]；5>4 右移→[1,3,_,5,8]；3\u003C4 停止，把 4 放到空位→[1,3,4,5,8]。","a = [1, 3, 5, 8, None]; x = 4\nj = 3\nwhile j >= 0 and a[j] > x:\n    a[j + 1] = a[j]; j -= 1\na[j + 1] = x\nprint(a[:5])","int a[]={1,3,5,8}; int x=4, j=3;\nwhile(j>=0 && a[j]>x){ a[j+1]=a[j]; j--; }\na[j+1]=x;","[1, 3, 4, 5, 8]",{"choice":5031,"coding":5050},[5032,5038,5042],{"id":38,"stem":5033,"options":5034,"answer":9,"explanation":5037},"插入排序的核心操作是？",[5035,5036,4888,2509],"找最小","把元素插入已排序前缀","逐个取出插入已排序部分。",{"id":47,"stem":5039,"options":5040,"answer":9,"explanation":5041},"插入排序在数据\"基本有序\"时复杂度接近？",[2339,2338,2564,1245],"很少移动，接近线性。",{"id":56,"stem":5043,"options":5044,"answer":607,"explanation":5049},"插入排序是稳定排序吗？",[5045,5046,5047,5048],"是","否","不一定","看数据","相等元素不后移覆盖，稳定。",[5051],{"id":67,"title":5005,"prompt":5052,"inputFormat":1801,"outputFormat":4995,"samples":5053,"starterPy":5055,"starterCpp":5056,"hint":5057,"referencePy":5058,"referenceCpp":5059},"输入 n（1≤n≤100）和 n 个整数（≤10^9），用插入排序从小到大排序并输出。",[5054],{"input":4940,"output":4941},"a = list(map(int, input().split()))\n# 插入排序\n","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    \u002F\u002F 插入\n    return 0;\n}","i 从 1 开始，取出 a[i]，向左把更大的元素右移，最后放回。","a = list(map(int, input().split()))\nfor i in range(1, len(a)):\n    x = a[i]; j = i - 1\n    while j >= 0 and a[j] > x:\n        a[j + 1] = a[j]; j -= 1\n    a[j + 1] = x\nprint(*a)","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    for(int i=1;i\u003Cn;i++){\n        int x=a[i], j=i-1;\n        while(j>=0 && a[j]>x){ a[j+1]=a[j]; j--; }\n        a[j+1]=x;\n    }\n    for(int i=0;i\u003Cn;i++) cout \u003C\u003C a[i] \u003C\u003C \" \";\n    return 0;\n}","插入排序把每个元素插入到前面已排序前缀的正确位置，像理牌；近乎有序时接近 O(n)，稳定，常用于高级排序的小数组收尾。","05-A1-4","排序思想",{"id":5061,"title":5062,"level":4885,"module":3117,"seq":770,"goal":5064,"keywords":5065,"explanation":5069,"examples":5081,"exercises":5089,"summary":5122,"next":5123,"nextTitle":5124},"理解\"为什么需要排序\"以及基于比较的排序的基本思想：通过元素间的比较与交换，把无序变有序。",[5066,5067,5068,4356],"排序目的","比较排序","有序的好处",[5070,5075],{"title":5071,"content":5072,"codePy":5073,"codeCpp":5074},"1. 排序让\"查找\u002F去重\u002F统计\"变简单","排序后数组有序，可以：二分查找（O(log n)）；轻松去重（相邻相等即重复）；求中位数、第 k 小；按名次输出。很多算法都先排序再处理。","# 排序后去重\na = sorted([3, 1, 3, 2, 1])\nres = [a[0]]\nfor x in a[1:]:\n    if x != res[-1]: res.append(x)\nprint(res)","sort(a, a + n);\n\u002F\u002F 相邻相等即重复，可线性去重",{"title":5076,"content":5077,"codePy":5078,"codeCpp":5079,"tip":5080},"2. 基于比较的排序下限","只靠\"比较两个元素大小\"来排序，决策树论证其时间复杂度下界是 O(n log n)。冒泡\u002F选择\u002F插入是 O(n²) 的\"简单排序\"；归并、快排、堆排序能达到 O(n log n)。","# 简单排序 O(n^2)：冒泡\u002F选择\u002F插入\n# 高效排序 O(n log n)：归并\u002F快排（更高阶）","\u002F\u002F O(n^2) 简单排序适合小规模\n\u002F\u002F O(n log n) 高效排序适合大规模","小规模用插入排序足够；大规模要用 O(n log n) 的高效排序。",[5082],{"title":5083,"prompt":5084,"analysis":5085,"codePy":5086,"codeCpp":5087,"run":5088},"例 1：排序后找中位数","给定 5 个数，先排序再输出中位数（第 3 个）。","排序后取中间下标。","a = sorted(list(map(int, input().split())))\nprint(a[2])","sort(a, a + 5); cout \u003C\u003C a[2];","输入 5 1 4 3 2 -> 3",{"choice":5090,"coding":5109},[5091,5098,5101],{"id":38,"stem":5092,"options":5093,"answer":63,"explanation":5097},"排序之后不能直接做的是什么？",[3118,5094,5095,5096],"线性去重","随机打乱","求中位数","排序得到的是有序，不是打乱。",{"id":47,"stem":5099,"options":5100,"answer":63,"explanation":2666},"基于比较的排序，理论最快约为？",[2338,2339,2564,1245],{"id":56,"stem":5102,"options":5103,"answer":9,"explanation":5108},"下列哪组是 O(n²) 的简单排序？",[5104,5105,5106,5107],"快排\u002F归并","冒泡\u002F选择\u002F插入","堆排序","计数排序","冒泡、选择、插入都是 O(n²)。",[5110],{"id":67,"title":5111,"prompt":5112,"inputFormat":1801,"outputFormat":5113,"samples":5114,"starterPy":5117,"starterCpp":5118,"hint":5119,"referencePy":5120,"referenceCpp":5121},"排序去重","输入 n（1≤n≤100）和 n 个整数（≤10^9），从小到大排序并去掉重复值后输出。","一行去重后升序排列的整数，空格分隔。",[5115],{"input":5116,"output":74},"6\n3 1 3 2 1 2","a = list(map(int, input().split()))\n# 排序 + 去重\n","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    \u002F\u002F 排序去重\n    return 0;\n}","先 sort，再线性扫描相邻去重（或借助 unique）。","a = list(map(int, input().split()))\na = sorted(set(a))\nprint(*a)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    sort(a, a + n);\n    int m = unique(a, a + n) - a;\n    for(int i=0;i\u003Cm;i++) cout \u003C\u003C a[i] \u003C\u003C \" \";\n    return 0;\n}","排序把无序变有序，使二分查找、去重、求中位数等变得简单；基于比较的排序下界 O(n log n)，简单排序 O(n²)、高效排序 O(n log n)。","05-A1-5","稳定性",{"id":5123,"title":5124,"level":4885,"module":3117,"seq":838,"goal":5126,"keywords":5127,"explanation":5132,"examples":5144,"exercises":5152,"summary":5186,"next":5187,"nextTitle":5188},"理解排序的\"稳定性\"：相等元素在排序后是否保持原有相对顺序；并记住常见排序是否稳定。",[5128,5129,5130,5131],"稳定排序","相对顺序","相等元素","应用场景",[5133,5138],{"title":5134,"content":5135,"codePy":5136,"codeCpp":5137},"1. 什么是稳定排序","若两个元素值相等，稳定排序保证排序前在前面的那个，排序后仍然在前面（相对顺序不变）。例如按成绩排序后，并列第一的人的原有先后仍保留。","# 稳定：相等不交换，原顺序保留\nrecords = [(\"B\", 90), (\"A\", 90), (\"C\", 80)]\nrecords.sort(key=lambda x: x[1])  # 稳定\nprint(records)  # [(\"B\",90),(\"A\",90),(\"C\",80)]","\u002F\u002F 稳定排序中，相等元素不跨过彼此",{"title":5139,"content":5140,"codePy":5141,"codeCpp":5142,"tip":5143},"2. 常见排序的稳定性","冒泡、插入、归并是稳定的；选择、快排（普通）、堆排序通常不稳定。需要\"多关键字排序\"时（如先按班、再按分），常先排次要关键字，再用稳定排序排主关键字。","# 多关键字：先按次要关键字排（不稳定也行），再用稳定排序按主关键字\nstudents.sort(key=lambda s: s.class_id)        # 次要\nstudents.sort(key=lambda s: s.score, reverse=True)  # 主，稳定","\u002F\u002F 先排次要，再用稳定排序排主关键字","需要保留\"并列顺序\"或做多关键字排序时，优先选稳定排序。",[5145],{"title":5146,"prompt":5147,"analysis":5148,"codePy":5149,"codeCpp":5150,"run":5151},"例 1：稳定与否的差异","对 [(\"B\",2),(\"A\",2),(\"C\",1)] 按数值升序，稳定排序的结果首两个是？","值都是 2 的 B、A 保持原顺序，1 的 C 最后→[(\"B\",2),(\"A\",2),(\"C\",1)]。","a = [(\"B\",2),(\"A\",2),(\"C\",1)]\na.sort(key=lambda x: x[1])\nprint(a)","\u002F\u002F 稳定排序后 B 仍在 A 前","[(\"B\", 2), (\"A\", 2), (\"C\", 1)]",{"choice":5153,"coding":5172},[5154,5161,5166],{"id":38,"stem":5155,"options":5156,"answer":9,"explanation":5160},"稳定排序保证什么？",[1448,5157,5158,5159],"相等元素相对顺序不变","占用更少内存","一定能去重","相等元素保持原先后。",{"id":47,"stem":5162,"options":5163,"answer":63,"explanation":5165},"下列哪种是稳定排序？",[4949,5106,4884,5164],"普通快排","冒泡相等不交换，稳定。",{"id":56,"stem":5167,"options":5168,"answer":9,"explanation":5171},"多关键字排序（先班后分）常用策略是？",[5169,5170,1568,4866],"只排一次","先排次要、再用稳定排序排主","稳定排序保证次要关键字的顺序不被打乱。",[5173],{"id":67,"title":5174,"prompt":5175,"inputFormat":1801,"outputFormat":5176,"samples":5177,"starterPy":5181,"starterCpp":5182,"hint":5183,"referencePy":5184,"referenceCpp":5185},"稳定排序验证","输入 n（1≤n≤100）个整数（≤10^9），用\"稳定\"的方式从小到大排序并输出（相等元素保持输入先后）。","一行升序、相等保序的整数，空格分隔。",[5178],{"input":5179,"output":5180},"5\n2 1 2 1 3","1 1 2 2 3","a = list(map(int, input().split()))\n# 稳定排序\n","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Item { int v, idx; };\nItem a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++){ cin >> a[i].v; a[i].idx = i; }\n    \u002F\u002F 稳定排序\n    return 0;\n}","Python 的 sort 本就稳定；C++ 可附带原始下标，比较时\"值相等按 idx\"。","a = list(map(int, input().split()))\nprint(*sorted(a))","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Item { int v, idx; };\nItem a[105];\nint main(){\n    int n; cin >> n;\n    for(int i=0;i\u003Cn;i++){ cin >> a[i].v; a[i].idx = i; }\n    stable_sort(a, a + n, [](Item x, Item y){\n        if (x.v != y.v) return x.v \u003C y.v;\n        return x.idx \u003C y.idx;\n    });\n    for(int i=0;i\u003Cn;i++) cout \u003C\u003C a[i].v \u003C\u003C \" \";\n    return 0;\n}","稳定排序保持相等元素的原有相对顺序；冒泡\u002F插入\u002F归并稳定，选择\u002F堆\u002F快排通常不稳定。多关键字排序常用\"先排次要、再用稳定排序排主关键字\"。","06-A1-1","顺序查找",{"id":4881,"title":2628,"level":2505,"module":3117,"seq":9,"goal":5190,"keywords":5191,"explanation":5194,"examples":5206,"exercises":5214,"summary":5239,"next":5240,"nextTitle":2627},"掌握快速排序：选轴(pivot)分区，使左边≤轴、右边≥轴，再递归处理左右。",[2628,5192,5193,2564],"pivot","分区",[5195,5200],{"title":5196,"content":5197,"codePy":5198,"codeCpp":5199},"1. 分区与递归","选一个轴，把比轴小的放左、大的放右（双指针或填坑），然后递归排左右。","def qsort(a, l, r):\n    if l >= r: return\n    p = a[l]; i = l; j = r\n    while i \u003C j:\n        while i \u003C j and a[j] >= p: j -= 1\n        a[i] = a[j]\n        while i \u003C j and a[i] \u003C= p: i += 1\n        a[j] = a[i]\n    a[i] = p\n    qsort(a, l, i-1); qsort(a, i+1, r)","\u002F\u002F 双指针分区 + 递归",{"title":5201,"content":5202,"codePy":5203,"codeCpp":5204,"tip":5205},"2. 复杂度与随机化","平均 O(n log n)，最坏（已有序且轴固定）O(n²)；随机选轴可避免最坏。","import random\npiv = a[l + random.randint(0, r-l)]  # 随机轴","\u002F\u002F 随机选轴避免退化","快排是\"原地、平均快\"的排序，工程常用。",[5207],{"title":5208,"prompt":5209,"analysis":5210,"codePy":5211,"codeCpp":5212,"run":5213},"例 1：分区","以首元素为轴，对 [3,1,4,1,5] 分区后，轴应落在哪？","轴=3，分区后 3 左边都≤3、右边都≥3。","print(\"轴就位，递归左右\")","\u002F\u002F 轴就位","轴就位",{"choice":5215,"coding":5229},[5216,5219,5222],{"id":38,"stem":5217,"options":5218,"answer":9,"explanation":3060},"快排平均复杂度？",[2339,2564,2338,1246],{"id":47,"stem":2941,"options":5220,"answer":9,"explanation":5221},[2564,2339,2338,1245],"已有序且轴固定。",{"id":56,"stem":5223,"options":5224,"answer":9,"explanation":5228},"避免最坏常用？",[5225,5226,2949,5227],"增大数组","随机选轴","用循环","随机化。",[5230],{"id":67,"title":2628,"prompt":5231,"inputFormat":1801,"outputFormat":5232,"samples":5233,"starterPy":5236,"starterCpp":5237,"hint":5238,"referencePy":5236,"referenceCpp":5237},"输入 n（1≤n≤100000）和 n 个整数，用快速排序（建议随机选轴）升序输出。","一行升序整数，空格分隔。",[5234],{"input":4280,"output":5235},"1 1 3 4 5","import random\na = list(map(int, input().split()))\ndef qs(l, r):\n    if l >= r: return\n    k = random.randint(l, r); a[l], a[k] = a[k], a[l]\n    p = a[l]; i = l; j = r\n    while i \u003C j:\n        while i \u003C j and a[j] >= p: j -= 1\n        a[i] = a[j]\n        while i \u003C j and a[i] \u003C= p: i += 1\n        a[j] = a[i]\n    a[i] = p\n    qs(l, i-1); qs(i+1, r)\nqs(0, len(a)-1)\nprint(*a)","#include \u003Ciostream>\n#include \u003Ccstdlib>\n#include \u003Cctime>\nusing namespace std;\nint a[100005];\nvoid qs(int l, int r){\n    if(l>=r) return;\n    int p=a[l+(rand()%(r-l+1))]; int i=l,j=r;\n    while(i\u003Cj){\n        while(i\u003Cj && a[j]>=p) j--; a[i]=a[j];\n        while(i\u003Cj && a[i]\u003C=p) i++; a[j]=a[i];\n    } a[i]=p; qs(l,i-1); qs(i+1,r);\n}\nint main(){ srand(time(0)); int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; qs(0,n-1); for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]\u003C\u003C\" \"; }","随机选轴放到 l，双指针分区，递归左右。","快排选轴分区使左小右大再递归，平均 O(n log n)；最坏 O(n²)，随机选轴可避免退化。","05-A2-2",{"id":5240,"title":2627,"level":2505,"module":3117,"seq":63,"goal":5242,"keywords":5243,"explanation":5246,"examples":5258,"exercises":5266,"summary":5289,"next":5290,"nextTitle":5291},"掌握归并排序：分治地把两半分别排好，再合并两个有序序列，稳定且稳定 O(n log n)。",[2627,5244,5245,4889],"分治","合并",[5247,5252],{"title":5248,"content":5249,"codePy":5250,"codeCpp":5251},"1. 分与治","把区间分成两半递归排好，再用双指针合并两个有序段。","def merge(a):\n    if len(a) \u003C= 1: return a\n    m = len(a) \u002F\u002F 2\n    L = merge(a[:m]); R = merge(a[m:])\n    res = []; i = j = 0\n    while i \u003C len(L) and j \u003C len(R):\n        if L[i] \u003C= R[j]: res.append(L[i]); i += 1\n        else: res.append(R[j]); j += 1\n    res.extend(L[i:]); res.extend(R[j:])\n    return res","\u002F\u002F 双指针合并两个有序数组",{"title":5253,"content":5254,"codePy":5255,"codeCpp":5256,"tip":5257},"2. 稳定且下界可达","归并是稳定排序，且严格达到 O(n log n)（不受输入影响）；代价是需要 O(n) 额外空间。","# 稳定；需额外空间 O(n)","\u002F\u002F 稳定 O(n log n)，额外 O(n)","需要稳定排序或外部排序时选归并。",[5259],{"title":5260,"prompt":5261,"analysis":5262,"codePy":5263,"codeCpp":5264,"run":5265},"例 1：合并","合并 [1,4] 与 [2,3] 的结果？","双指针：[1,2,3,4]。","print([1,2,3,4])","\u002F\u002F 1 2 3 4","[1, 2, 3, 4]",{"choice":5267,"coding":5281},[5268,5273,5277],{"id":38,"stem":5269,"options":5270,"answer":607,"explanation":5272},"归并排序稳定吗？",[4889,4929,4931,5271],"从不","相等不颠倒，稳定。",{"id":47,"stem":5274,"options":5275,"answer":9,"explanation":5276},"归并排序额外空间？",[1245,2338,2339,1246],"需辅助数组。",{"id":56,"stem":5278,"options":5279,"answer":9,"explanation":5280},"归并排序复杂度？",[2338,2564,2339,1245],"分治合并。",[5282],{"id":67,"title":2627,"prompt":5283,"inputFormat":1801,"outputFormat":5232,"samples":5284,"starterPy":5286,"starterCpp":5287,"hint":5288,"referencePy":5286,"referenceCpp":5287},"输入 n（1≤n≤100000）和 n 个整数，用归并排序升序输出。",[5285],{"input":4280,"output":5235},"import sys\nsys.setrecursionlimit(200000)\na = list(map(int, input().split()))\ndef ms(x):\n    if len(x) \u003C= 1: return x\n    m = len(x)\u002F\u002F2\n    L = ms(x[:m]); R = ms(x[m:])\n    res = []; i = j = 0\n    while i \u003C len(L) and j \u003C len(R):\n        if L[i] \u003C= R[j]: res.append(L[i]); i += 1\n        else: res.append(R[j]); j += 1\n    res.extend(L[i:]); res.extend(R[j:])\n    return res\nprint(*ms(a))","#include \u003Ciostream>\nusing namespace std;\nint a[100005], t[100005];\nvoid ms(int l, int r){\n    if(l>=r) return;\n    int m=(l+r)\u002F2; ms(l,m); ms(m+1,r);\n    int i=l,j=m+1,k=l;\n    while(i\u003C=m && j\u003C=r){\n        if(a[i]\u003C=a[j]) t[k++]=a[i++]; else t[k++]=a[j++];\n    }\n    while(i\u003C=m) t[k++]=a[i++];\n    while(j\u003C=r) t[k++]=a[j++];\n    for(int p=l;p\u003C=r;p++) a[p]=t[p];\n}\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; ms(0,n-1); for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]\u003C\u003C\" \"; }","分两半递归，再双指针合并到辅助数组。","归并排序分治后合并两个有序段，稳定且恒为 O(n log n)，代价是 O(n) 额外空间。","05-A2-3","sort",{"id":5290,"title":5291,"level":2505,"module":3117,"seq":703,"goal":5293,"keywords":5294,"explanation":5297,"examples":5309,"exercises":5317,"summary":5346,"next":5347,"nextTitle":5348},"掌握语言内置排序（Python sorted \u002F C++ sort）的用法与复杂度，能直接对数组排序。",[5295,5291,5296,2564],"内置排序","sorted",[5298,5303],{"title":5299,"content":5300,"codePy":5301,"codeCpp":5302},"1. Python","sorted(a) 返回新列表（稳定），a.sort() 原地排；默认升序，降序用 reverse=True。","a = [3,1,2]; b = sorted(a); a.sort(); print(a, b)","\u002F\u002F Python 内置 TimSort，稳定 O(n log n)",{"title":5304,"content":5305,"codePy":5306,"codeCpp":5307,"tip":5308},"2. C++","sort(a, a+n) 原地升序（不稳定，平均 O(n log n)）；需要稳定用 stable_sort。","# C++: sort(begin,end); stable_sort 稳定","sort(a, a + n);           \u002F\u002F 不稳定\nstable_sort(a, a + n); \u002F\u002F 稳定","考试\u002F刷题时优先用内置排序，自己写仅用于理解。",[5310],{"title":5311,"prompt":5312,"analysis":5313,"codePy":5314,"codeCpp":5315,"run":5316},"例 1：内置排序","sorted([3,1,2]) 的结果？","[1,2,3]。","print(sorted([3,1,2]))","\u002F\u002F 1 2 3","[1, 2, 3]",{"choice":5318,"coding":5338},[5319,5323,5331],{"id":38,"stem":5320,"options":5321,"answer":9,"explanation":5322},"Python sorted 稳定吗？",[4929,4889,5048,5271],"TimSort 稳定。",{"id":47,"stem":5324,"options":5325,"answer":9,"explanation":5330},"C++ sort 默认？",[5326,5327,5328,5329],"稳定升序","不稳定升序","降序","不稳定降序","不稳定。",{"id":56,"stem":5332,"options":5333,"answer":9,"explanation":5337},"C++ 要稳定排序用？",[5291,5334,5335,5336],"stable_sort","qsort","reverse","stable_sort。",[5339],{"id":67,"title":5295,"prompt":5340,"inputFormat":1801,"outputFormat":5232,"samples":5341,"starterPy":5343,"starterCpp":5344,"hint":5345,"referencePy":5343,"referenceCpp":5344},"输入 n（1≤n≤100000）和 n 个整数，用语言内置排序升序输出。",[5342],{"input":4280,"output":5235},"a = list(map(int, input().split()))\na.sort()\nprint(*a)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; sort(a, a+n); for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]\u003C\u003C\" \"; }","Python 用 list.sort()；C++ 用 sort(a,a+n)。","直接用语言内置排序：Python sorted\u002Fa.sort（稳定，O(n log n)），C++ sort（不稳定）\u002Fstable_sort（稳定）。","05-A2-4","自定义比较",{"id":5347,"title":5348,"level":2505,"module":3117,"seq":770,"goal":5350,"keywords":5351,"explanation":5355,"examples":5367,"exercises":5375,"summary":5412,"next":5413,"nextTitle":5414},"掌握自定义比较函数\u002F键，按\"自己定义的规则\"排序（如按绝对值、按字符串长度）。",[5348,5352,5353,5354],"key","cmp","排序规则",[5356,5361],{"title":5357,"content":5358,"codePy":5359,"codeCpp":5360},"1. Python 用 key","sorted(a, key=lambda x: abs(x)) 按绝对值；key 只算一次，效率高。","a = [-3,1,-2]; print(sorted(a, key=abs))   # [1,-2,-3]","\u002F\u002F key 决定比较依据",{"title":5362,"content":5363,"codePy":5364,"codeCpp":5365,"tip":5366},"2. C++ 用比较函数","sort(a, a+n, [](int x, int y){ return abs(x) \u003C abs(y); })。注意\"严格弱序\"（\u003C 而非 \u003C=）。","# C++: sort(a,a+n,[](int x,int y){return abs(x)\u003Cabs(y);})","sort(a, a + n, [](int x, int y){ return abs(x) \u003C abs(y); });","比较函数必须严格弱序：x==y 时返回 false。",[5368],{"title":5369,"prompt":5370,"analysis":5371,"codePy":5372,"codeCpp":5373,"run":5374},"例 1：按绝对值排序","按绝对值升序排 [-3,1,-2]。","|值| 为 3,1,2 → [1,-2,-3]。","print(sorted([-3,1,-2], key=abs))","\u002F\u002F 1 -2 -3","[1, -2, -3]",{"choice":5376,"coding":5400},[5377,5385,5392],{"id":38,"stem":5378,"options":5379,"answer":9,"explanation":5384},"Python 自定义排序推荐用？",[5380,5381,5382,5383],"循环交换","key 参数","手写快排","多个 sort","key 效率高。",{"id":47,"stem":5386,"options":5387,"answer":9,"explanation":5391},"C++ 比较函数应返回？",[5388,5389,5390,3952],"x\u003C=y","x\u003Cy（严格弱序）","x==y","严格弱序用 \u003C。",{"id":56,"stem":5393,"options":5394,"answer":9,"explanation":5399},"按绝对值排序 [-3,1,-2] 得到？",[5395,5396,5397,5398],"[-3,-2,1]","[1,-2,-3]","[-3,1,-2]","[1,-3,-2]","按 |值| 升序。",[5401],{"id":67,"title":5402,"prompt":5403,"inputFormat":1801,"outputFormat":5404,"samples":5405,"starterPy":5409,"starterCpp":5410,"hint":5411,"referencePy":5409,"referenceCpp":5410},"绝对值排序","输入 n（1≤n≤100000）和 n 个整数，按绝对值升序输出（绝对值相同按原值升序）。","一行整数，空格分隔。",[5406],{"input":5407,"output":5408},"3\n-3 1 -2","1 -2 -3","a = list(map(int, input().split()))\na.sort(key=lambda x: (abs(x), x))\nprint(*a)","#include \u003Ciostream>\n#include \u003Calgorithm>\n#include \u003Ccmath>\nusing namespace std;\nint a[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; sort(a, a+n, [](int x, int y){ if(abs(x)!=abs(y)) return abs(x)\u003Cabs(y); return x\u003Cy; }); for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]\u003C\u003C\" \"; }","Python 用 key=(abs, 值)；C++ 比较函数先比绝对值再比原值。","自定义比较：Python 用 key（推荐，算一次）；C++ 用比较函数（严格弱序 \u003C），按任意规则排序。","05-A2-5","多关键字排序",{"id":5413,"title":5414,"level":2505,"module":3117,"seq":838,"goal":5416,"keywords":5417,"explanation":5420,"examples":5432,"exercises":5440,"summary":5473,"next":5474,"nextTitle":5475},"掌握多关键字排序：先按主关键字，相同再按次关键字（用元组 key 或稳定排序组合）。",[5418,5419,4889,1473],"多关键字","元组",[5421,5426],{"title":5422,"content":5423,"codePy":5424,"codeCpp":5425},"1. Python 元组 key","sorted(a, key=lambda x:(x[0], x[1])) 先比 x[0]，相等再比 x[1]。","a = [(2,3),(1,2),(2,1)]; print(sorted(a, key=lambda x:(x[0],x[1])))","\u002F\u002F 先主后次",{"title":5427,"content":5428,"codePy":5429,"codeCpp":5430,"tip":5431},"2. 或稳定排序组合","先用稳定排序排次关键字，再用稳定排序排主关键字（稳定排序不破坏已排好的次关键字顺序）。","a.sort(key=lambda x:x[1])      # 次\na.sort(key=lambda x:x[0])      # 主，稳定","\u002F\u002F 先排次，再用稳定排序排主","元组 key 最直观；稳定性方法适合不能写 key 的场景。",[5433],{"title":5434,"prompt":5435,"analysis":5436,"codePy":5437,"codeCpp":5438,"run":5439},"例 1：按 (a,b) 排序","按第一维升序、第一维相同按第二维升序排 [(2,3),(1,2),(2,1)]。","[(1,2),(2,1),(2,3)]。","print(sorted([(2,3),(1,2),(2,1)], key=lambda x:(x[0],x[1])))","\u002F\u002F (1,2)(2,1)(2,3)","[(1, 2), (2, 1), (2, 3)]",{"choice":5441,"coding":5461},[5442,5449,5456],{"id":38,"stem":5443,"options":5444,"answer":607,"explanation":5448},"多关键字排序常用？",[5445,5446,1568,5447],"元组 key","多次冒泡","不用排","元组按位比较。",{"id":47,"stem":5450,"options":5451,"answer":9,"explanation":5455},"Python 元组 key 比较顺序？",[5452,5453,3952,5454],"倒序","从左到右（先主后次）","只比第一维","先第一维再第二维。",{"id":56,"stem":5457,"options":5458,"answer":9,"explanation":5460},"\"先排次关键字再用稳定排序排主\"依赖？",[5459,5128,4949,2509],"快排","稳定性保序。",[5462],{"id":67,"title":5414,"prompt":5463,"inputFormat":5464,"outputFormat":5465,"samples":5466,"starterPy":5470,"starterCpp":5471,"hint":5472,"referencePy":5470,"referenceCpp":5471},"输入 n（1≤n≤100000）和 n 对整数 (a,b)，按 a 升序、a 相同按 b 升序输出。","第一行 n；其后 n 行每行 a b。","n 行，每行 a b（按序）。",[5467],{"input":5468,"output":5469},"3\n2 3\n1 2\n2 1","1 2\n2 1\n2 3","n = int(input())\nrows = [tuple(map(int, input().split())) for _ in range(n)]\nrows.sort(key=lambda x:(x[0], x[1]))\nfor a, b in rows: print(a, b)","#include \u003Ciostream>\n#include \u003Calgorithm>\n#include \u003Cvector>\nusing namespace std;\nstruct P{ int a,b; };\nvector\u003CP> v;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ P p; cin>>p.a>>p.b; v.push_back(p); } sort(v.begin(), v.end(), [](P x, P y){ if(x.a!=y.a) return x.a\u003Cy.a; return x.b\u003Cy.b; }); for(auto p:v) cout\u003C\u003Cp.a\u003C\u003C\" \"\u003C\u003Cp.b\u003C\u003Cendl; }","Python 用 key=(a,b)；C++ 比较先比 a 再比 b。","多关键字排序用元组 key（先主后次）或\"先排次关键字再用稳定排序排主关键字\"。","05-A2-6","结构体排序",{"id":5474,"title":5475,"level":2505,"module":3117,"seq":936,"goal":5477,"keywords":5478,"explanation":5481,"examples":5491,"exercises":5499,"summary":5530,"next":5531,"nextTitle":5532},"掌握对\"结构体\u002F对象数组\"按某个字段排序（如学生按成绩）。",[1467,5479,5480,5352],"对象排序","字段",[5482,5486],{"title":5299,"content":5483,"codePy":5484,"codeCpp":5485},"对字典\u002F对象列表按字段排序，用 key=lambda x:x[\"score\"]。","stu = [{'n':'A','s':90},{'n':'B','s':80}]\nprint(sorted(stu, key=lambda x:x['s'], reverse=True))","\u002F\u002F 按结构体字段排序",{"title":5304,"content":5487,"codePy":5488,"codeCpp":5489,"tip":5490},"定义 struct 并写比较函数或重载 \u003C。","# C++: sort(v.begin(),v.end(),[](auto a,auto b){return a.s>b.s;})","sort(v.begin(), v.end(), [](Stu a, Stu b){ return a.s > b.s; });","按字段排序是竞赛最常见需求之一。",[5492],{"title":5493,"prompt":5494,"analysis":5495,"codePy":5496,"codeCpp":5497,"run":5498},"例 1：按成绩降序","学生 [A:90, B:80] 按成绩降序。","[A:90, B:80]。","print(sorted([('A',90),('B',80)], key=lambda x:x[1], reverse=True))","\u002F\u002F A B","[('A', 90), ('B', 80)]",{"choice":5500,"coding":5518},[5501,5506,5512],{"id":38,"stem":5502,"options":5503,"answer":9,"explanation":5505},"Python 对对象列表按字段排序用？",[192,5504,2687,5447],"key=lambda x:字段","key 取字段。",{"id":47,"stem":5507,"options":5508,"answer":9,"explanation":5511},"C++ 结构体排序用？",[5509,5510,1385,1568],"只能用冒泡","比较函数\u002F重载\u003C","写比较函数。",{"id":56,"stem":5513,"options":5514,"answer":9,"explanation":5517},"按成绩降序，成绩相同怎么办需额外规则？",[5515,5516,1568,567],"无所谓","再加次关键字","多关键字。",[5519],{"id":67,"title":5475,"prompt":5520,"inputFormat":5521,"outputFormat":5522,"samples":5523,"starterPy":5527,"starterCpp":5528,"hint":5529,"referencePy":5527,"referenceCpp":5528},"输入 n（1≤n≤100000）和 n 行（姓名 成绩），按成绩降序输出；成绩相同按姓名字典序升序。","第一行 n；其后 n 行每行 字符串 整数。","n 行，每行 姓名 成绩（按序）。",[5524],{"input":5525,"output":5526},"3\nA 90\nB 80\nC 90","A 90\nC 90\nB 80","n = int(input())\nrows = []\nfor _ in range(n):\n    name, s = input().split(); rows.append((name, int(s)))\nrows.sort(key=lambda x:(-x[1], x[0]))\nfor name, s in rows: print(name, s)","#include \u003Ciostream>\n#include \u003Calgorithm>\n#include \u003Cvector>\n#include \u003Cstring>\nusing namespace std;\nstruct Stu{ string n; int s; };\nvector\u003CStu> v;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ Stu t; cin>>t.n>>t.s; v.push_back(t); } sort(v.begin(), v.end(), [](Stu x, Stu y){ if(x.s!=y.s) return x.s>y.s; return x.n\u003Cy.n; }); for(auto t:v) cout\u003C\u003Ct.n\u003C\u003C\" \"\u003C\u003Ct.s\u003C\u003Cendl; }","Python 用 key=(-成绩, 姓名)；C++ 比较先比成绩降序再比姓名升序。","结构体排序按某字段：Python 用 key 取字段；C++ 写比较函数\u002F重载 \u003C 按字段比较（注意次关键字规则）。","05-A2-7","稳定排序应用",{"id":5531,"title":5532,"level":2505,"module":3117,"seq":998,"goal":5534,"keywords":5535,"explanation":5538,"examples":5550,"exercises":5558,"summary":5581,"next":5582,"nextTitle":5583},"掌握\"稳定排序\"的实际应用：保留并列数据的原有顺序，或实现多关键字排序。",[5128,5536,5418,5537],"并列顺序","应用",[5539,5544],{"title":5540,"content":5541,"codePy":5542,"codeCpp":5543},"1. 保留并列顺序","如排行榜中成绩相同的选手保持原报名先后，必须用稳定排序。","ranks = [('A',90),('B',90)]\nranks.sort(key=lambda x:x[1], reverse=True)  # Python 稳定","stable_sort(ranks.begin(), ranks.end(), cmpScore); \u002F\u002F 稳定",{"title":5545,"content":5546,"codePy":5547,"codeCpp":5548,"tip":5549},"2. 实现多关键字","先排次要关键字，再用稳定排序排主关键字（前面已学），避免写复杂比较。","ranks.sort(key=lambda x:x[1])        # 次\nranks.sort(key=lambda x:x[0], reverse=True) # 主","\u002F\u002F 先次后主，利用稳定性","需要\"并列保序\"是稳定排序最典型的用武之地。",[5551],{"title":5552,"prompt":5553,"analysis":5554,"codePy":5555,"codeCpp":5556,"run":5557},"例 1：并列保序","成绩相同的两人 [A:90, B:90] 按成绩降序，稳定排序后顺序？","保持原顺序 A 在 B 前。","print(sorted([('A',90),('B',90)], key=lambda x:x[1], reverse=True))","\u002F\u002F A 90, B 90","[('A', 90), ('B', 90)]",{"choice":5559,"coding":5573},[5560,5564,5569],{"id":38,"stem":5561,"options":5562,"answer":9,"explanation":5563},"排行榜要\"同分保持原报名顺序\"应用了？",[5459,5128,4949,2509],"稳定排序保留并列顺序。",{"id":47,"stem":5565,"options":5566,"answer":9,"explanation":5568},"\"先排次关键字再用稳定排序排主\"利用了？",[5567,5124,1568,1405],"快排快","稳定不破坏次关键字序。",{"id":56,"stem":5570,"options":5571,"answer":9,"explanation":5322},"Python 默认 sort 是？",[4929,4889,1568,5572],"从不排",[5574],{"id":67,"title":5532,"prompt":5575,"inputFormat":5521,"outputFormat":5522,"samples":5576,"starterPy":5578,"starterCpp":5579,"hint":5580,"referencePy":5578,"referenceCpp":5579},"输入 n（1≤n≤100000）和 n 行（姓名 成绩），用稳定排序按成绩降序输出，成绩相同的保持输入先后顺序（即按编号升序）。",[5577],{"input":5525,"output":5526},"n = int(input())\nrows = []\nfor i in range(n):\n    name, s = input().split(); rows.append((i, name, int(s)))\nrows.sort(key=lambda x:-x[2])\nfor _, name, s in rows: print(name, s)","#include \u003Ciostream>\n#include \u003Calgorithm>\n#include \u003Cvector>\n#include \u003Cstring>\nusing namespace std;\nstruct Stu{ int id; string n; int s; };\nvector\u003CStu> v;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ Stu t; cin>>t.n>>t.s; t.id=i; v.push_back(t); } stable_sort(v.begin(), v.end(), [](Stu x, Stu y){ if(x.s!=y.s) return x.s>y.s; return x.id\u003Cy.id; }); for(auto t:v) cout\u003C\u003Ct.n\u003C\u003C\" \"\u003C\u003Ct.s\u003C\u003Cendl; }","Python 用稳定 sort 仅按成绩降序即可保序；C++ 用 stable_sort 并比较成绩、编号。","稳定排序保留相等元素的原有顺序，常用于排行榜并列保序与\"先排次要再用稳定排序排主\"的多关键字方案。","06-A2-1","lower_bound",{"id":5187,"title":5188,"level":4885,"module":5585,"seq":9,"goal":5586,"keywords":5587,"explanation":5590,"examples":5602,"exercises":5609,"summary":5640,"next":5641,"nextTitle":5642},"查找与二分","掌握顺序查找：从第一个元素开始逐个比较，直到找到目标或扫完整个数组。适用于未排序数据。",[5188,5588,2338,5589],"线性查找","未排序",[5591,5596],{"title":5592,"content":5593,"codePy":5594,"codeCpp":5595},"1. 逐个比较，找到即停","从头到尾遍历数组，每步把当前元素与目标比较，相等就返回下标；扫完都没找到返回 -1（表示不存在）。无论数组是否有序都能用。","def linear(a, x):\n    for i in range(len(a)):\n        if a[i] == x: return i\n    return -1","int linear(int a[], int n, int x) {\n    for (int i = 0; i \u003C n; i++) if (a[i] == x) return i;\n    return -1;\n}",{"title":5597,"content":5598,"codePy":5599,"codeCpp":5600,"tip":5601},"2. 时间复杂度 O(n)","最坏要比较 n 次，复杂度 O(n)；平均 n\u002F2 次。当数据无序或规模不大时最方便；数据很大且要多次查找时，应先排序再用二分。","# 无序也能查，但慢\na = [5, 3, 8, 1]\nprint(linear(a, 8))  # 2","\u002F\u002F 顺序查找不需要数组有序","只查一次、数据无序：顺序查找最简单；多次查找：先排序再二分更划算。",[5603],{"title":5604,"prompt":5605,"analysis":5606,"codePy":5607,"codeCpp":5608,"run":759},"例 1：查找是否存在","数组 [5,3,8,1] 中是否存在 8？输出其下标或 -1。","逐个比较，8 在下标 2。","a = [5, 3, 8, 1]\nprint(linear(a, 8))","cout \u003C\u003C linear(a, 4, 8);",{"choice":5610,"coding":5627},[5611,5619,5623],{"id":38,"stem":5612,"options":5613,"answer":9,"explanation":5618},"顺序查找要求数组有序吗？",[5614,5615,5616,5617],"必须有序","不需要","必须倒序","必须去重","顺序查找对数组顺序无要求。",{"id":47,"stem":5620,"options":5621,"answer":63,"explanation":5622},"顺序查找最坏时间复杂度？",[1245,1246,2338,2339],"最坏扫完 n 个。",{"id":56,"stem":5624,"options":5625,"answer":9,"explanation":5626},"没找到目标时通常返回？",[891,1567,3054,567],"-1 常用来表示\"未找到\"。",[5628],{"id":67,"title":5629,"prompt":5630,"inputFormat":2955,"outputFormat":5631,"samples":5632,"starterPy":5635,"starterCpp":5636,"hint":5637,"referencePy":5638,"referenceCpp":5639},"查找元素","输入 n（1≤n≤100）、n 个整数和待查值 x（均 ≤10^9），输出 x 首次出现的下标（从 0 开始），不存在输出 -1。","一行整数下标或 -1。",[5633],{"input":5634,"output":759},"4\n5 3 8 1\n8","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\n# 顺序查找\n","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    \u002F\u002F 查找\n    return 0;\n}","遍历数组，相等就返回下标；结束仍未返回则 -1。","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\nans = -1\nfor i in range(n):\n    if a[i] == x: ans = i; break\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    int ans = -1;\n    for(int i=0;i\u003Cn;i++) if (a[i] == x) { ans = i; break; }\n    cout \u003C\u003C ans;\n    return 0;\n}","顺序查找从头逐个比较，找到返回下标、否则 -1；不要求有序，时间复杂度 O(n)。适合无序数据或一次性查找。","06-A1-2","有序数组查找",{"id":5641,"title":5642,"level":4885,"module":5585,"seq":63,"goal":5644,"keywords":5645,"explanation":5649,"examples":5661,"exercises":5668,"summary":5703,"next":5704,"nextTitle":5705},"掌握在\"已升序数组\"中查找：仍可顺序扫描，但一旦遇到比目标大的元素即可提前停止。",[5646,5647,5648,3844],"有序查找","提前停止","升序",[5650,5655],{"title":5651,"content":5652,"codePy":5653,"codeCpp":5654},"1. 利用有序性提前停止","数组已升序时，从左到右扫描：若当前元素等于目标就找到；若当前元素已经大于目标，说明后面的更大，不可能再有目标，直接结束返回 -1。这比无序顺序查找平均更快。","def find_sorted(a, x):\n    for i in range(len(a)):\n        if a[i] == x: return i\n        if a[i] > x: return -1   # 已越过，后面更大\n    return -1","int find_sorted(int a[], int n, int x) {\n    for (int i = 0; i \u003C n; i++) {\n        if (a[i] == x) return i;\n        if (a[i] > x) return -1;\n    }\n    return -1;\n}",{"title":5656,"content":5657,"codePy":5658,"codeCpp":5659,"tip":5660},"2. 仍是 O(n)，但为二分打基础","有序数组的顺序查找最坏仍是 O(n)，但它体现了\"利用有序性剪枝\"的思想——这正是二分查找能降到 O(log n) 的关键前提：数据必须有序。","# 有序才能\"越过即停\"\na = [1, 3, 5, 8]\nprint(find_sorted(a, 4))  # -1（3\u003C4 继续，5>4 停止）","\u002F\u002F 有序是二分查找的前提","记住：二分查找的第一步，永远是确认数组\"已经有序\"。",[5662],{"title":5663,"prompt":5664,"analysis":5665,"codePy":5666,"codeCpp":5667,"run":1567},"例 1：有序中查找","升序数组 [1,3,5,8] 查找 4，过程如何？","1\u003C4、3\u003C4 继续；5>4 停止返回 -1。","print(find_sorted([1,3,5,8], 4))","cout \u003C\u003C find_sorted(a, 4, 4);",{"choice":5669,"coding":5690},[5670,5678,5682],{"id":38,"stem":5671,"options":5672,"answer":9,"explanation":5677},"升序数组中遇到 a[i] > x 时应？",[5673,5674,5675,5676],"继续找","立即结束（返回-1）","从头再找","输出 x","后面更大，不可能再有 x。",{"id":47,"stem":5679,"options":5680,"answer":9,"explanation":5681},"有序数组顺序查找最坏复杂度？",[1246,2338,1245,2339],"最坏仍扫完。",{"id":56,"stem":5683,"options":5684,"answer":9,"explanation":5689},"二分查找能用的前提是？",[5685,5686,5687,5688],"数组无序","数组有序","数组为空","数组有重复","必须有序才能二分。",[5691],{"id":67,"title":5646,"prompt":5692,"inputFormat":5693,"outputFormat":5694,"samples":5695,"starterPy":5698,"starterCpp":5699,"hint":5700,"referencePy":5701,"referenceCpp":5702},"输入 n（1≤n≤100）、已升序的 n 个整数和 x（均 ≤10^9），在数组中查找 x：相等输出下标，否则一旦当前元素大于 x 就输出 -1。","第一行 n；第二行升序 n 个整数；第三行 x。","一行下标或 -1。",[5696],{"input":5697,"output":1567},"4\n1 3 5 8\n4","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\n# 有序查找\n","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    \u002F\u002F 有序查找\n    return 0;\n}","相等返回下标；遇到第一个 >x 的元素立即返回 -1。","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\nans = -1\nfor i in range(n):\n    if a[i] == x: ans = i; break\n    if a[i] > x: break\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nint a[105];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    int ans = -1;\n    for(int i=0;i\u003Cn;i++){\n        if (a[i]==x){ ans=i; break; }\n        if (a[i]>x) break;\n    }\n    cout \u003C\u003C ans;\n    return 0;\n}","在升序数组中查找，遇到等于目标返回下标；遇到首个大于目标的即可提前停止返回 -1。这利用了有序性剪枝，是二分查找的思想基础。","06-A1-3","二分查找基础",{"id":5704,"title":5705,"level":4885,"module":5585,"seq":703,"goal":5707,"keywords":5708,"explanation":5711,"examples":5723,"exercises":5730,"summary":5765,"next":5766,"nextTitle":5767},"掌握二分查找：在已升序数组中，每次取中点比较，把搜索区间缩小一半，复杂度降到 O(log n)。",[3118,5709,5710,1246],"中点","区间缩小",[5712,5717],{"title":5713,"content":5714,"codePy":5715,"codeCpp":5716},"1. 每次砍掉一半","维护区间 [l, r]。取中点 m=(l+r)\u002F\u002F2：若 a[m]==x 找到；若 a[m]\u003Cx，目标在右半，令 l=m+1；否则目标在左半，令 r=m-1。直到 l>r 仍未找到。","def bisect(a, x):\n    l, r = 0, len(a) - 1\n    while l \u003C= r:\n        m = (l + r) \u002F\u002F 2\n        if a[m] == x: return m\n        if a[m] \u003C x: l = m + 1\n        else: r = m - 1\n    return -1","int bisect(int a[], int n, int x) {\n    int l = 0, r = n - 1;\n    while (l \u003C= r) {\n        int m = (l + r) \u002F 2;\n        if (a[m] == x) return m;\n        if (a[m] \u003C x) l = m + 1; else r = m - 1;\n    }\n    return -1;\n}",{"title":5718,"content":5719,"codePy":5720,"codeCpp":5721,"tip":5722},"2. 循环条件是 l\u003C=r，中点防溢出","用 l\u003C=r 因为当区间只剩一个元素时仍需检查。计算中点用 m = l + (r - l) \u002F 2 可避免 (l+r) 在大数时溢出（C++ 中 l+r 可能超 int 范围）。","m = l + (r - l) \u002F\u002F 2   # 防溢出写法","int m = l + (r - l) \u002F 2;  \u002F\u002F 避免 l+r 溢出","二分必须\"有序\"；写完后用 [1,2,3] 找 1、找 3、找 0 各测一遍验证边界。",[5724],{"title":5725,"prompt":5726,"analysis":5727,"codePy":5728,"codeCpp":5729,"run":890},"例 1：二分找元素","升序数组 [1,3,5,8,9] 查找 8，经过几次中点比较？","m=2(值5)\u003C8→l=3；m=3(值8)==8 找到，下标 3。","print(bisect([1,3,5,8,9], 8))","cout \u003C\u003C bisect(a, 5, 8);",{"choice":5731,"coding":5752},[5732,5740,5744],{"id":38,"stem":5733,"options":5734,"answer":9,"explanation":5739},"二分查找的前提是？",[5735,5736,5737,5738],"无序","升序（有序）","全相等","长度=2","必须有序。",{"id":47,"stem":5741,"options":5742,"answer":9,"explanation":5743},"二分查找时间复杂度？",[2338,1246,2339,1245],"每次减半区间。",{"id":56,"stem":5745,"options":5746,"answer":9,"explanation":5751},"当 a[m] \u003C x 时应？",[5747,5748,5749,5750],"r=m-1","l=m+1","结束","m+=1","目标在右半，左界右移。",[5753],{"id":67,"title":3118,"prompt":5754,"inputFormat":5693,"outputFormat":5694,"samples":5755,"starterPy":5760,"starterCpp":5761,"hint":5762,"referencePy":5763,"referenceCpp":5764},"输入 n（1≤n≤100000）、已升序的 n 个整数和 x（均 ≤10^9），用二分查找输出 x 的下标，不存在输出 -1。",[5756,5758],{"input":5757,"output":890},"5\n1 3 5 8 9\n8",{"input":5759,"output":1567},"5\n1 3 5 8 9\n4","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\n# 二分查找\n","#include \u003Ciostream>\nusing namespace std;\nint a[100005];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    \u002F\u002F 二分\n    return 0;\n}","维护 l,r；m=l+(r-l)\u002F2；按 a[m] 与 x 大小移动边界。","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\nl, r = 0, n - 1\nans = -1\nwhile l \u003C= r:\n    m = (l + r) \u002F\u002F 2\n    if a[m] == x: ans = m; break\n    elif a[m] \u003C x: l = m + 1\n    else: r = m - 1\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nint a[100005];\nint main(){\n    int n, x; cin >> n;\n    for(int i=0;i\u003Cn;i++) cin >> a[i];\n    cin >> x;\n    int l=0, r=n-1, ans=-1;\n    while (l \u003C= r) {\n        int m = l + (r - l) \u002F 2;\n        if (a[m] == x) { ans = m; break; }\n        if (a[m] \u003C x) l = m + 1; else r = m - 1;\n    }\n    cout \u003C\u003C ans;\n    return 0;\n}","二分查找在有序数组上每次取中点把搜索区间减半，复杂度 O(log n)。循环用 l\u003C=r，a[m]\u003Cx 则 l=m+1 否则 r=m-1；中点用 l+(r-l)\u002F2 防溢出。","08-A1-1","递归思想",{"id":5582,"title":5583,"level":2505,"module":5585,"seq":9,"goal":5769,"keywords":5770,"explanation":5773,"examples":5784,"exercises":5791,"summary":5826,"next":5827,"nextTitle":5828},"掌握 lower_bound：在有序数组中找\"第一个 ≥ x\"的位置（C++ lower_bound \u002F 手写二分）。",[5583,5771,2509,5772],"第一个>=","有序",[5774,5779],{"title":5775,"content":5776,"codePy":5777,"codeCpp":5778},"1. 语义","返回第一个满足 a[i] ≥ x 的下标；都小则返回 n（越界）。可用 C++ lower_bound 或手写二分。","def lower_bound(a, x):\n    l, r = 0, len(a)\n    while l \u003C r:\n        m = (l + r) \u002F\u002F 2\n        if a[m] \u003C x: l = m + 1\n        else: r = m\n    return l","auto it = lower_bound(a, a + n, x);  \u002F\u002F 第一个 >= x",{"title":4654,"content":5780,"codePy":5781,"codeCpp":5782,"tip":5783},"判断存在性（若返回处 ==x 则存在）、求≥x 的个数（n - lb）。","lb = lower_bound(a, x)\nprint(\"存在\" if lb \u003C n and a[lb] == x else \"无\")","\u002F\u002F 存在性：*it==x 且 it!=end","lower_bound 是二分查找最常用的变体。",[5785],{"title":5786,"prompt":5787,"analysis":5788,"codePy":5789,"codeCpp":5790,"run":1942},"例 1：查找","a=[1,3,3,5]，lower_bound(3) 与 lower_bound(4)？","第一个 ≥3 是下标1；≥4 是下标3（值5）。","print(lower_bound(a,3), lower_bound(a,4))","\u002F\u002F 1, 3",{"choice":5792,"coding":5813},[5793,5801,5805],{"id":38,"stem":5794,"options":5795,"answer":9,"explanation":5800},"lower_bound(x) 返回？",[5796,5797,5798,5799],"最后一个\u003Cx","第一个>=x","最后一个\u003C=x","第一个>x","第一个不小于 x。",{"id":47,"stem":5802,"options":5803,"answer":9,"explanation":5804},"a=[1,3,3,5]，lower_bound(3)=？",[891,892,890,1663],"下标1处值3≥3。",{"id":56,"stem":5806,"options":5807,"answer":607,"explanation":5812},"用 lower_bound 判断 x 是否存在？",[5808,5809,5810,5811],"看返回值\u003Clen 且 a[lb]==x","直接返回 bool","不能","看 a[0]","需检查边界与相等。",[5814],{"id":67,"title":5815,"prompt":5816,"inputFormat":5693,"outputFormat":5817,"samples":5818,"starterPy":5823,"starterCpp":5824,"hint":5825,"referencePy":5823,"referenceCpp":5824},"第一个 >=x","输入 n（1≤n≤100000）、升序数组和 x，输出第一个 ≥x 的下标（不存在输出 n）。","一行下标或 n。",[5819,5821],{"input":5820,"output":890},"4\n1 3 3 5\n4",{"input":5822,"output":1663},"4\n1 3 3 5\n6","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\nl, r = 0, n\nwhile l \u003C r:\n    m = (l + r) \u002F\u002F 2\n    if a[m] \u003C x: l = m + 1\n    else: r = m\nprint(l)","#include \u003Ciostream>\nusing namespace std;\nint a[100005];\nint main(){ int n,x; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; cin>>x; int l=0,r=n; while(l\u003Cr){ int m=(l+r)\u002F2; if(a[m]\u003Cx) l=m+1; else r=m; } cout\u003C\u003Cl; }","二分：a[m]\u003Cx 则 l=m+1，否则 r=m；最终 l 即答案。","lower_bound 找有序数组第一个 ≥x 的位置；用于判断存在性与统计≥x 的个数（n-lb）。","06-A2-2","upper_bound",{"id":5827,"title":5828,"level":2505,"module":5585,"seq":63,"goal":5830,"keywords":5831,"explanation":5834,"examples":5845,"exercises":5852,"summary":5881,"next":5882,"nextTitle":5883},"掌握 upper_bound：找\"第一个 > x\"的位置，与 lower_bound 配合统计某值的个数。",[5828,5832,4166,5833],"第一个>","区间",[5835,5839],{"title":5775,"content":5836,"codePy":5837,"codeCpp":5838},"返回第一个 a[i] > x 的下标；都 ≤x 则返回 n。手写时条件是 a[m] ≤ x 则 l=m+1。","def upper_bound(a, x):\n    l, r = 0, len(a)\n    while l \u003C r:\n        m = (l + r) \u002F\u002F 2\n        if a[m] \u003C= x: l = m + 1\n        else: r = m\n    return l","auto it = upper_bound(a, a + n, x); \u002F\u002F 第一个 > x",{"title":5840,"content":5841,"codePy":5842,"codeCpp":5843,"tip":5844},"2. 计数","值等于 x 的个数 = upper_bound(x) - lower_bound(x)。","cnt = upper_bound(a, x) - lower_bound(a, x)","\u002F\u002F 个数 = ub - lb","lower\u002Fupper 配对可求任意值的出现次数与范围。",[5846],{"title":5847,"prompt":5848,"analysis":5849,"codePy":5850,"codeCpp":5851,"run":759},"例 1：计数","a=[1,3,3,5]，x=3 的出现次数？","ub(3)=3, lb(3)=1，个数=2。","print(upper_bound(a,3) - lower_bound(a,3))","\u002F\u002F 2",{"choice":5853,"coding":5870},[5854,5858,5862],{"id":38,"stem":5855,"options":5856,"answer":9,"explanation":5857},"upper_bound(x) 返回？",[5797,5799,5798,5796],"第一个大于 x。",{"id":47,"stem":5859,"options":5860,"answer":63,"explanation":5861},"a=[1,3,3,5]，upper_bound(3)=？",[892,759,890,1663],"下标3值5>3。",{"id":56,"stem":5863,"options":5864,"answer":9,"explanation":5869},"值 x 的出现次数 = ？",[5865,5866,5867,5868],"lb(x)","ub(x)-lb(x)","n-ub(x)","ub(x)+lb(x)","右边界减左边界。",[5871],{"id":67,"title":5872,"prompt":5873,"inputFormat":5693,"outputFormat":5874,"samples":5875,"starterPy":5878,"starterCpp":5879,"hint":5880,"referencePy":5878,"referenceCpp":5879},"值计数","输入 n（1≤n≤100000）、升序数组和 x，输出 x 出现的次数。","一行整数次数。",[5876],{"input":5877,"output":759},"4\n1 3 3 5\n3","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\ndef lb():\n    l,r=0,n\n    while l\u003Cr:\n        m=(l+r)\u002F\u002F2\n        if a[m]\u003Cx: l=m+1\n        else: r=m\n    return l\ndef ub():\n    l,r=0,n\n    while l\u003Cr:\n        m=(l+r)\u002F\u002F2\n        if a[m]\u003C=x: l=m+1\n        else: r=m\n    return l\nprint(ub()-lb())","#include \u003Ciostream>\nusing namespace std;\nint a[100005];\nint main(){ int n,x; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; cin>>x; int L=0,R=n; while(L\u003CR){int m=(L+R)\u002F2; if(a[m]\u003Cx)L=m+1; else R=m;} int lb=L; L=0,R=n; while(L\u003CR){int m=(L+R)\u002F2; if(a[m]\u003C=x)L=m+1; else R=m;} cout\u003C\u003CL-lb; }","分别求 lb（第一个≥x）和 ub（第一个>x），相减。","upper_bound 找第一个 >x 的位置；某值出现次数 = upper_bound(x)-lower_bound(x)。","06-A2-3","二分边界",{"id":5882,"title":5883,"level":2505,"module":5585,"seq":703,"goal":5885,"keywords":5886,"explanation":5890,"examples":5902,"exercises":5908,"summary":5941,"next":5942,"nextTitle":5943},"掌握二分求\"边界\"：第一个满足 \u002F 最后一个满足某条件的位置（答案落在有序判定函数上）。",[5883,5887,5888,5889],"第一个满足","最后一个满足","判定",[5891,5896],{"title":5892,"content":5893,"codePy":5894,"codeCpp":5895},"1. 第一个满足","条件 f(mid) 在右侧为真、左侧为假，找最左真值（同 lower_bound 套路：a[m] 满足条件则 r=m，否则 l=m+1）。","def first_true(f, n):\n    l, r = 0, n\n    while l \u003C r:\n        m = (l + r) \u002F\u002F 2\n        if f(m): r = m\n        else: l = m + 1\n    return l","\u002F\u002F 找第一个使 f 为真的位置",{"title":5897,"content":5898,"codePy":5899,"codeCpp":5900,"tip":5901},"2. 最后一个满足","f 左侧真右侧假，找最右真值（mid 向下取整用 (l+r+1)\u002F\u002F2，f(mid) 真则 l=m 否则 r=m-1）。","def last_true(f, n):\n    l, r = -1, n - 1\n    while l \u003C r:\n        m = (l + r + 1) \u002F\u002F 2\n        if f(m): l = m\n        else: r = m - 1\n    return l","\u002F\u002F 找最后一个使 f 为真的位置","二分边界死循环多因 mid 取整方向错，记住\"求右端点用上取整\"。",[5903],{"title":5904,"prompt":5905,"analysis":5906,"codePy":5907,"codeCpp":5851,"run":759},"例 1：最后一个 ≤x","a=[1,3,3,5]，最后一个 ≤3 的下标？","下标2（值3）。可用 last_true(a[mid]\u003C=3)。","print(last_true(lambda i: a[i] \u003C= 3, n))",{"choice":5909,"coding":5930},[5910,5918,5925],{"id":38,"stem":5911,"options":5912,"answer":63,"explanation":5917},"找第一个使 f 为真的位置，条件真在右侧时用？",[5913,5914,5915,5916],"l=m+1 当假","r=m 当真","两者都对","不用二分","真则 r=m，假则 l=m+1。",{"id":47,"stem":5919,"options":5920,"answer":9,"explanation":5924},"找最后一个真值，mid 应取？",[5921,5922,3952,5923],"下取整 (l+r)\u002F\u002F2","上取整 (l+r+1)\u002F\u002F2","l","上取整防死循环。",{"id":56,"stem":5926,"options":5927,"answer":9,"explanation":5929},"二分边界常用于？",[3117,5928,4165,4166],"求满足条件的端点","边界定位。",[5931],{"id":67,"title":5932,"prompt":5933,"inputFormat":5693,"outputFormat":5694,"samples":5934,"starterPy":5938,"starterCpp":5939,"hint":5940,"referencePy":5938,"referenceCpp":5939},"最后一个 ≤x","输入 n（1≤n≤100000）、升序数组和 x，输出最后一个 ≤x 的下标（不存在输出 -1）。",[5935,5936],{"input":5877,"output":759},{"input":5937,"output":1567},"4\n1 3 3 5\n0","n = int(input())\na = list(map(int, input().split()))\nx = int(input())\nl, r = -1, n - 1\nwhile l \u003C r:\n    m = (l + r + 1) \u002F\u002F 2\n    if a[m] \u003C= x: l = m\n    else: r = m - 1\nprint(l)","#include \u003Ciostream>\nusing namespace std;\nint a[100005];\nint main(){ int n,x; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; cin>>x; int l=-1,r=n-1; while(l\u003Cr){ int m=(l+r+1)\u002F2; if(a[m]\u003C=x) l=m; else r=m-1; } if(l>=0 && a[l]\u003C=x) cout\u003C\u003Cl; else cout\u003C\u003C-1; }","用上取整 mid，a[m]\u003C=x 则 l=m 否则 r=m-1。","二分边界用于求\"第一个\u002F最后一个满足条件\"的位置；靠调整 l、r 的移动方向与中点取整（求右端点用上取整）避免死循环。","06-A2-4","二分最大值",{"id":5942,"title":5943,"level":2505,"module":5585,"seq":770,"goal":5945,"keywords":5946,"explanation":5950,"examples":5961,"exercises":5967,"summary":5999,"next":6000,"nextTitle":6001},"掌握\"二分答案求最大\"：当答案越大越难满足时，二分找满足条件的最大值。",[5947,4202,5948,5949],"二分答案","判定函数","可行性",[5951,5956],{"title":5952,"content":5953,"codePy":5954,"codeCpp":5955},"1. 套路","若\"mid 可行则更大的也可能可行（或反之）\"，就在 [L,R] 上二分，用判定函数 check(mid)。","def max_ok(L, R):\n    ans = L\n    while L \u003C= R:\n        m = (L + R) \u002F\u002F 2\n        if check(m): ans = m; L = m + 1\n        else: R = m - 1\n    return ans","\u002F\u002F 可行则向右找更大",{"title":2920,"content":5957,"codePy":5958,"codeCpp":5959,"tip":5960},"在数组中最晚能在时间 T 内完成（T 越大越容易），二分最大可行 T。","# check(T) 判断能否在时间 T 完成","\u002F\u002F 二分最大可行值","二分答案把\"最优化\"变\"判定\"，核心是写对 check。",[5962],{"title":5963,"prompt":5964,"analysis":5965,"codePy":5966,"codeCpp":4490,"run":1663},"例 1：最大 k","数组 [1,2,3,4,5]，二分最大的 k 使前 k 个元素之和 ≤ 10。","前4项和=10≤10，前5项=15>10，答案 k=4。","print(4)",{"choice":5968,"coding":5987},[5969,5975,5980],{"id":38,"stem":5970,"options":5971,"answer":9,"explanation":5974},"二分答案求最大，可行时？",[5747,5972,5749,5973],"L=m+1 向右","L=m","可行则尝试更大。",{"id":47,"stem":5976,"options":5977,"answer":9,"explanation":5979},"二分答案的核心是？",[3117,5978,1405,3259],"写判定函数 check","用 check 替代枚举。",{"id":56,"stem":5981,"options":5982,"answer":9,"explanation":5986},"\"越大越容易满足\"应求？",[5983,5984,4916,5985],"最小","最大","和","二分最大可行值。",[5988],{"id":67,"title":5989,"prompt":5990,"inputFormat":5991,"outputFormat":5992,"samples":5993,"starterPy":5996,"starterCpp":5997,"hint":5998,"referencePy":5996,"referenceCpp":5997},"最大前缀和 k","输入 n（1≤n≤100000）和 n 个正整数、S（1≤S≤10^18），二分最大的 k（1≤k≤n）使前 k 个元素之和 ≤ S；若不存在（即 a[0]>S）输出 0。","第一行 n S；第二行 n 个正整数。","一行整数 k。",[5994],{"input":5995,"output":1663},"5 10\n1 2 3 4 5","n, S = map(int, input().split())\na = list(map(int, input().split()))\npref = [0]*(n+1)\nfor i in range(n): pref[i+1] = pref[i] + a[i]\nL, R, ans = 1, n, 0\nwhile L \u003C= R:\n    m = (L + R)\u002F\u002F2\n    if pref[m] \u003C= S: ans = m; L = m + 1\n    else: R = m - 1\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nlong long pre[100005];\nint main(){ int n; long long S; cin>>n>>S; for(int i=1;i\u003C=n;i++){ long long x; cin>>x; pre[i]=pre[i-1]+x; } int L=1,R=n,ans=0; while(L\u003C=R){ int m=(L+R)\u002F2; if(pre[m]\u003C=S){ ans=m; L=m+1; } else R=m-1; } cout\u003C\u003Cans; }","先求前缀和，在 [1,n] 上二分找最大的满足 pre[k]\u003C=S 的 k。","二分最大值：当\"越大越容易满足\"时用判定函数 check(mid)，可行就向右找更大；典型如二分最大可行资源。","06-A2-5","二分最小值",{"id":6000,"title":6001,"level":2505,"module":5585,"seq":838,"goal":6003,"keywords":6004,"explanation":6006,"examples":6016,"exercises":6023,"summary":6054,"next":6055,"nextTitle":5947},"掌握\"二分答案求最小\"：当答案越小越难满足时，二分找满足条件的最小值。",[6001,5983,5889,6005],"答案",[6007,6011],{"title":5952,"content":6008,"codePy":6009,"codeCpp":6010},"若\"mid 可行则更小的也可能可行\"，可行就向左找更小（r=mid），否则 l=mid+1。","def min_ok(L, R):\n    ans = R\n    while L \u003C= R:\n        m = (L + R) \u002F\u002F 2\n        if check(m): ans = m; R = m - 1\n        else: L = m + 1\n    return ans","\u002F\u002F 可行则向左找更小",{"title":2920,"content":6012,"codePy":6013,"codeCpp":6014,"tip":6015},"把 n 本书分给 k 人，求\"单人最多页数\"的最小值（越大越容易分，越小越难），二分最小可行值。","# check(x): 能否让每人页数\u003C=x","\u002F\u002F 二分最小上界","与\"求最大\"的区别只在移动方向。",[6017],{"title":6018,"prompt":6019,"analysis":6020,"codePy":6021,"codeCpp":6022,"run":885},"例 1：分书最小最大页","页数 [3,5,2,4] 分给 2 人，最小化\"单人最多页数\"。","最优可 7（如 5+2=7 与 3+4=7），最小最大=7。","print(7)","\u002F\u002F 7",{"choice":6024,"coding":6042},[6025,6032,6036],{"id":38,"stem":6026,"options":6027,"answer":9,"explanation":6031},"二分答案求最小，可行时？",[6028,6029,5749,6030],"L=m+1","r=m-1 向左","R=m+1","可行则尝试更小。",{"id":47,"stem":6033,"options":6034,"answer":9,"explanation":6035},"\"越小越难满足\"应求？",[5984,5983,5985,3000],"二分最小可行值。",{"id":56,"stem":6037,"options":6038,"answer":9,"explanation":6041},"最小最大分配问题（如分书）常用？",[6039,5947,6040,3117],"贪心","DP","答案单调，二分。",[6043],{"id":67,"title":6044,"prompt":6045,"inputFormat":6046,"outputFormat":6047,"samples":6048,"starterPy":6051,"starterCpp":6052,"hint":6053,"referencePy":6051,"referenceCpp":6052},"最小最大页数","输入 n（1≤n≤100000）和 n 个正整数页数、k（1≤k≤n）个人，二分输出\"单人最多页数\"的最小值（即把书分成 k 段，使各段和的最大值尽量小）。","第一行 n k；第二行 n 个正整数。","一行整数最小最大段和。",[6049],{"input":6050,"output":885},"4 2\n3 5 2 4","n, k = map(int, input().split())\na = list(map(int, input().split()))\ndef check(x):\n    cnt = 1; s = 0\n    for v in a:\n        if s + v \u003C= x: s += v\n        else:\n            cnt += 1; s = v\n    return cnt \u003C= k\nL, R, ans = max(a), sum(a), R\nwhile L \u003C= R:\n    m = (L + R)\u002F\u002F2\n    if check(m): ans = m; R = m - 1\n    else: L = m + 1\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nlong long a[100005];\nint n,k; bool check(long long x){ int c=1; long long s=0; for(int i=0;i\u003Cn;i++){ if(s+a[i]\u003C=x) s+=a[i]; else { c++; s=a[i]; } } return c\u003C=k; }\nint main(){ cin>>n>>k; long long mx=0,sm=0; for(int i=0;i\u003Cn;i++){ cin>>a[i]; mx=max(mx,a[i]); sm+=a[i]; } long long L=mx,R=sm,ans=R; while(L\u003C=R){ long long m=(L+R)\u002F2; if(check(m)){ ans=m; R=m-1; } else L=m+1; } cout\u003C\u003Cans; }","判定 check(x)：贪心尽量往当前段塞，能分≤k 段则 x 可行。在 [max,sum] 上二分最小 x。","二分最小值：当\"越小越难满足\"时可行就向左（r=mid）找最小；典型如\"最小最大\"分配问题（如分书）。","06-A2-6",{"id":6055,"title":5947,"level":2505,"module":5585,"seq":936,"goal":6057,"keywords":6058,"explanation":6061,"examples":6073,"exercises":6081,"summary":6115,"next":6116,"nextTitle":3846},"综合理解\"二分答案\"：把最优化问题转为\"判定 mid 是否可行\"的二分搜索。",[5947,6059,5889,6060],"最优化","转化",[6062,6067],{"title":6063,"content":6064,"codePy":6065,"codeCpp":6066},"1. 为什么能二分","若答案具有单调性（可行区间连续），就可在答案域上二分，用 check 替代枚举。","# 最大\u002F最小都靠 check(mid) 单调性","\u002F\u002F 答案单调 => 二分",{"title":6068,"content":6069,"codePy":6070,"codeCpp":6071,"tip":6072},"2. 步骤","确定答案上下界 [L,R]；写 check(mid)；按\"求最大\u002F最小\"选择移动方向；最后取 ans。","L, R = lo, hi\nwhile L \u003C= R:\n    m = (L + R) \u002F\u002F 2\n    if check(m): ans = m; R = m - 1   # 求最小\n    else: L = m + 1","\u002F\u002F 三步：上下界、check、移动","写不出 check 的单调性，就不能二分答案。",[6074],{"title":6075,"prompt":6076,"analysis":6077,"codePy":6078,"codeCpp":6079,"run":6080},"例 1：答案转化","把\"求满足条件的最小 x\"转为二分：需要什么？","需要 check(x) 随 x 单调（x 越大越易满足）。","print(\"check 单调\")","\u002F\u002F 单调","单调",{"choice":6082,"coding":6103},[6083,6090,6095],{"id":38,"stem":6084,"options":6085,"answer":9,"explanation":6089},"二分答案的前提是？",[5686,6086,6087,6088],"答案单调（check 单调）","数据随机","n 很小","单调性保证可二分。",{"id":47,"stem":6091,"options":6092,"answer":9,"explanation":6094},"二分答案把最优化问题转为？",[3117,6093,1542,1405],"判定问题","判定 mid 是否可行。",{"id":56,"stem":6096,"options":6097,"answer":9,"explanation":6102},"二分答案三步是？",[6098,6099,6100,6101],"排序、查找、输出","上下界、check、移动方向","递归、合并、输出","读入、计算、输出","上下界 + check + 移动。",[6104],{"id":67,"title":6105,"prompt":6106,"inputFormat":6107,"outputFormat":6108,"samples":6109,"starterPy":6112,"starterCpp":6113,"hint":6114,"referencePy":6112,"referenceCpp":6113},"最少箱子数","输入 n（1≤n≤100000）个正整数和容量 cap（1≤cap≤10^9），二分输出\"最少需要的箱子数\"使得每箱物品之和不超过 cap（每个物品放入一个箱子，贪心装箱：能装就装，不能则新箱）。","第一行 n cap；第二行 n 个正整数。","一行整数最少箱子数。",[6110],{"input":6111,"output":890},"5 10\n4 5 6 3 2","n, cap = map(int, input().split())\na = list(map(int, input().split()))\ndef check(k):\n    cnt = 1; s = 0\n    for v in a:\n        if s + v \u003C= cap: s += v\n        else:\n            cnt += 1; s = v\n    return cnt \u003C= k\nL, R, ans = 1, n, n\nwhile L \u003C= R:\n    m = (L + R)\u002F\u002F2\n    if check(m): ans = m; R = m - 1\n    else: L = m + 1\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nlong long a[100005];\nint n; long long cap; bool check(int k){ int c=1; long long s=0; for(int i=0;i\u003Cn;i++){ if(s+a[i]\u003C=cap) s+=a[i]; else { c++; s=a[i]; } } return c\u003C=k; }\nint main(){ cin>>n>>cap; for(int i=0;i\u003Cn;i++) cin>>a[i]; int L=1,R=n,ans=n; while(L\u003C=R){ int m=(L+R)\u002F2; if(check(m)){ ans=m; R=m-1; } else L=m+1; } cout\u003C\u003Cans; }","判定 check(k)：贪心装箱能否用≤k 个箱子；在 [1,n] 上二分最小 k。","二分答案把最优化转为判定：答案单调时，在上下界上二分，用 check(mid) 决定移动方向，得到最小\u002F最大可行值。","06-A2-7",{"id":6116,"title":3846,"level":2505,"module":5585,"seq":998,"goal":6118,"keywords":6119,"explanation":6121,"examples":6133,"exercises":6139,"summary":6173,"next":6174,"nextTitle":6175},"理解单调性是二分的前提：只有\"判定结果随 mid 单调变化\"才能用二分。",[3846,6120,5889,5772],"二分前提",[6122,6127],{"title":6123,"content":6124,"codePy":6125,"codeCpp":6126},"1. 单调含义","存在一个分界点，分界一侧 check 全为真、另一侧全为假（如\"时间够不够\"随时间单调）。","# check(t): 时间 t 能否完成 -> t 越大越可能 True","\u002F\u002F 判定随答案单调",{"title":6128,"content":6129,"codePy":6130,"codeCpp":6131,"tip":6132},"2. 没有单调性不能二分","若 check 结果忽真忽假（如随机），二分会错；需先证明\u002F构造单调性。","# 先确认单调，再二分","\u002F\u002F 非单调问题不能用简单二分","面试\u002F考试常考\"能否二分\"，先证单调性。",[6134],{"title":6135,"prompt":6136,"analysis":6137,"codePy":6138,"codeCpp":6079,"run":6080},"例 1：单调判定","\"能否在 t 秒内完成\"随 t 单调吗？","t 越大越容易完成，单调。","print(\"单调\")",{"choice":6140,"coding":6162},[6141,6147,6155],{"id":38,"stem":6142,"options":6143,"answer":9,"explanation":6146},"能用二分答案的前提？",[6087,6144,6088,6145],"判定随答案单调","数组升序","单调才有分界点。",{"id":47,"stem":6148,"options":6149,"answer":9,"explanation":6154},"check 结果忽真忽假时？",[6150,6151,6152,6153],"仍可二分","不能简单二分","一定能","用快排","无分界点。",{"id":56,"stem":6156,"options":6157,"answer":9,"explanation":6161},"\"所需时间 ≤ T\" 随 T 的变化？",[1568,6158,6159,6160],"单调（T 大更易）","相反","不相关","T 越大越易满足。",[6163],{"id":67,"title":6164,"prompt":6165,"inputFormat":6166,"outputFormat":6167,"samples":6168,"starterPy":6170,"starterCpp":6171,"hint":6172,"referencePy":6170,"referenceCpp":6171},"最早完成时间","输入 n（1≤n≤100000）个任务耗时（正整数）和机器数 k（1≤k≤n），二分输出\"最早完成时间\"T（判定=能否用 k 台机器在 T 内完成：贪心把任务依次放进当前最早结束的机器）。请输出使所有任务能在 T 内完成的最小 T。","第一行 n k；第二行 n 个正整数耗时。","一行整数最小 T。",[6169],{"input":6050,"output":885},"n, k = map(int, input().split())\na = list(map(int, input().split()))\ndef check(T):\n    load = [0]*k\n    for v in a:\n        idx = min(range(k), key=lambda i: load[i])\n        if load[idx] + v > T: return False\n        load[idx] += v\n    return True\nL, R, ans = max(a), sum(a), R\nwhile L \u003C= R:\n    m = (L + R)\u002F\u002F2\n    if check(m): ans = m; R = m - 1\n    else: L = m + 1\nprint(ans)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nlong long a[100005];\nint n,k; bool check(long long T){ long long load[20]={0}; for(int i=0;i\u003Cn;i++){ int idx=0; for(int j=1;j\u003Ck;j++) if(load[j]\u003Cload[idx]) idx=j; if(load[idx]+a[i]>T) return false; load[idx]+=a[i]; } return true; }\nint main(){ cin>>n>>k; long long mx=0,sm=0; for(int i=0;i\u003Cn;i++){ cin>>a[i]; mx=max(mx,a[i]); sm+=a[i]; } long long L=mx,R=sm,ans=R; while(L\u003C=R){ long long m=(L+R)\u002F2; if(check(m)){ ans=m; R=m-1; } else L=m+1; } cout\u003C\u003Cans; }","判定：把任务贪心放入当前最早空闲机器；在 [max,sum] 上二分最小 T。","单调性（判定结果随答案一侧真一侧假）是二分的前提；不单调不能用简单二分，需先构造\u002F证明。","08-A2-1","分治思想",{"id":6177,"title":2627,"level":6178,"module":6179,"seq":9,"goal":6180,"keywords":6181,"explanation":6183,"examples":6195,"exercises":6203,"summary":6228,"next":6229,"nextTitle":6230},"07-A1-1","Lv.6","排序进阶","掌握归并排序：用分治把数组分成两半分别排序，再合并两个有序序列，整体达到 O(n log n)。",[2627,5244,6182,2564],"合并有序",[6184,6190],{"title":6185,"content":6186,"codePy":6187,"codeCpp":6188,"tip":6189},"1. 分治与合并","先把区间 [l, r] 从中点分成两半，递归排好左右，再用双指针把两个有序段合并成整体有序。","def merge_sort(a):\n    if len(a) \u003C= 1: return a\n    m = len(a)\u002F\u002F2\n    L, R = merge_sort(a[:m]), merge_sort(a[m:])\n    i=j=k=0; res=[]\n    while i\u003Clen(L) and j\u003Clen(R):\n        if L[i]\u003C=R[j]: res.append(L[i]); i+=1\n        else: res.append(R[j]); j+=1\n    res+=L[i:]; res+=R[j:]; return res","\u002F\u002F 分治：[l,r] 取中点 m，递归排 [l,m] 与 [m+1,r]，再合并","tip: 归并的核心是“合并两个有序序列”，复杂度稳定在 O(n log n)。",{"title":6191,"content":6192,"codePy":6193,"codeCpp":6194},"2. 复杂度","递归树高 log n 层，每层合并总工作量 O(n)，故 T(n)=2T(n\u002F2)+O(n)=O(n log n)，且是稳定排序。","import math\nn=8\nprint(math.ceil(math.log2(n)))  # 层数 3","\u002F\u002F 稳定；最好\u002F最坏\u002F平均都是 O(n log n)",[6196],{"title":6197,"prompt":6198,"analysis":6199,"codePy":6200,"codeCpp":6201,"run":6202},"例 1：合并两个有序数组","[1,3,5] 与 [2,4,6] 合并结果？","双指针比较：1,2,3,4,5,6。","print([1,2,3,4,5,6])","\u002F\u002F 1 2 3 4 5 6","1 2 3 4 5 6",{"choice":6204,"coding":6218},[6205,6212,6214],{"id":38,"stem":6206,"options":6207,"answer":9,"explanation":6211},"归并排序的核心思想是？",[6208,6209,4166,6210],"选主元划分","分治+合并有序","堆","先分后合并有序段。",{"id":47,"stem":2652,"options":6213,"answer":9,"explanation":2654},[2338,2564,2339,1246],{"id":56,"stem":6215,"options":6216,"answer":607,"explanation":6217},"归并排序是否稳定？",[4889,4929,4931,5047],"相等元素不改变相对顺序。",[6219],{"id":67,"title":2627,"prompt":6220,"inputFormat":1801,"outputFormat":5232,"samples":6221,"starterPy":6223,"starterCpp":6224,"hint":6225,"referencePy":6226,"referenceCpp":6227},"输入 n 个整数，输出升序排列后的结果。",[6222],{"input":4280,"output":5235},"def merge_sort(a):\n    # 实现归并排序\n    pass\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvoid merge_sort(vector\u003Cint>& a, int l, int r) {\n    \u002F\u002F 实现\n}\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; merge_sort(a,0,n-1); for(int x:a) cout\u003C\u003Cx\u003C\u003C' '; }","分治到长度为 1，再写合并双指针。","def merge_sort(a):\n    if len(a)\u003C=1: return a\n    m=len(a)\u002F\u002F2; L=merge_sort(a[:m]); R=merge_sort(a[m:])\n    i=j=0; res=[]\n    while i\u003Clen(L) and j\u003Clen(R):\n        if L[i]\u003C=R[j]: res.append(L[i]); i+=1\n        else: res.append(R[j]); j+=1\n    return res+L[i:]+R[j:]","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvoid merge(vector\u003Cint>& a,int l,int m,int r){\n    vector\u003Cint> t; int i=l,j=m+1;\n    while(i\u003C=m&&j\u003C=r){ if(a[i]\u003C=a[j]) t.push_back(a[i++]); else t.push_back(a[j++]); }\n    while(i\u003C=m) t.push_back(a[i++]); while(j\u003C=r) t.push_back(a[j++]);\n    for(int k=0;k\u003Ct.size();k++) a[l+k]=t[k];\n}\nvoid merge_sort(vector\u003Cint>& a,int l,int r){\n    if(l>=r) return; int m=(l+r)\u002F2; merge_sort(a,l,m); merge_sort(a,m+1,r); merge(a,l,m,r);\n}\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; merge_sort(a,0,n-1); for(int x:a) cout\u003C\u003Cx\u003C\u003C' '; }","归并排序用分治把数组二分、递归排序后再合并两个有序段，稳定且复杂度为 O(n log n)，适合大规模数据。","07-A1-2","归并排序与逆序对",{"id":6229,"title":6230,"level":6178,"module":6179,"seq":63,"goal":6232,"keywords":6233,"explanation":6237,"examples":6248,"exercises":6254,"summary":6284,"next":6285,"nextTitle":6286},"理解归并过程可顺带统计逆序对数量，把“计数”问题转化为分治合并时的跨段计数。",[6234,6235,6236],"逆序对","归序计数","分治统计",[6238,6243],{"title":6239,"content":6240,"codePy":6241,"codeCpp":6242},"1. 什么是逆序对","若 i\u003Cj 且 a[i]>a[j]，则 (i,j) 是一个逆序对。暴力 O(n²)；归并时，当右段元素 a[j] 被选中，说明它比左段剩余所有元素都小，贡献 “左段剩余个数” 个逆序对。","cnt = 0\ndef ms(a):\n    global cnt\n    if len(a)\u003C=1: return a\n    m=len(a)\u002F\u002F2; L=ms(a[:m]); R=ms(a[m:])\n    i=j=0; res=[]\n    while i\u003Clen(L) and j\u003Clen(R):\n        if L[i]\u003C=R[j]: res.append(L[i]); i+=1\n        else: res.append(R[j]); cnt+=len(L)-i; j+=1\n    res+=L[i:]; res+=R[j:]; return res","\u002F\u002F 合并时若取 R[j]，逆序对 += (左段剩余长度)",{"title":6244,"content":6245,"codePy":6246,"codeCpp":6247},"2. 复杂度与意义","归并统计仍是 O(n log n)；逆序对衡量“无序程度”，常用于求最少交换次数使序列有序。","print(\"cnt 即逆序对总数\")","\u002F\u002F 冒泡交换次数 = 逆序对总数",[6249],{"title":6250,"prompt":6251,"analysis":6252,"codePy":6253,"codeCpp":5851,"run":759},"例 1：逆序对","[3,1,2] 的逆序对？","(3,1),(3,2) 共 2 个。","print(2)",{"choice":6255,"coding":6272},[6256,6260,6266],{"id":38,"stem":6257,"options":6258,"answer":9,"explanation":6259},"归并统计逆序对的复杂度？",[2338,2564,2339,1245],"复用归并过程。",{"id":47,"stem":6261,"options":6262,"answer":9,"explanation":6265},"右段元素被选中时贡献几个逆序对？",[891,6263,892,6264],"左段剩余个数","右段个数","它比左段剩余都小。",{"id":56,"stem":6267,"options":6268,"answer":9,"explanation":6271},"使序列有序的最少相邻交换次数等于？",[6269,6270,891,3053],"长度","逆序对数","每次相邻交换减 1 逆序对。",[6273],{"id":67,"title":6234,"prompt":6274,"inputFormat":1801,"outputFormat":6275,"samples":6276,"starterPy":6279,"starterCpp":6280,"hint":6281,"referencePy":6282,"referenceCpp":6283},"输入 n 个整数，输出逆序对总数（对 10^9+7 取模无需，直接输出）。","一行整数。",[6277],{"input":6278,"output":759},"3\n3 1 2","cnt=0\ndef ms(a):\n    global cnt\n    # 实现\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nlong long cnt=0;\nvoid merge_sort(vector\u003Cint>& a,int l,int r){\n    \u002F\u002F 合并时累加 cnt\n}\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; merge_sort(a,0,n-1); cout\u003C\u003Ccnt; }","合并时取右段元素则 cnt += 左段剩余长度。","cnt=0\ndef ms(a):\n    global cnt\n    if len(a)\u003C=1: return a\n    m=len(a)\u002F\u002F2; L=ms(a[:m]); R=ms(a[m:])\n    i=j=0; res=[]\n    while i\u003Clen(L) and j\u003Clen(R):\n        if L[i]\u003C=R[j]: res.append(L[i]); i+=1\n        else: res.append(R[j]); cnt+=len(L)-i; j+=1\n    return res+L[i:]+R[j:]\nms([3,1,2]); print(cnt)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nlong long cnt=0;\nvoid merge(vector\u003Cint>& a,int l,int m,int r){\n    vector\u003Cint> t; int i=l,j=m+1;\n    while(i\u003C=m&&j\u003C=r){ if(a[i]\u003C=a[j]) t.push_back(a[i++]); else { t.push_back(a[j++]); cnt+=m-i+1; } }\n    while(i\u003C=m) t.push_back(a[i++]); while(j\u003C=r) t.push_back(a[j++]);\n    for(int k=0;k\u003Ct.size();k++) a[l+k]=t[k];\n}\nvoid ms(vector\u003Cint>& a,int l,int r){ if(l>=r) return; int m=(l+r)\u002F2; ms(a,l,m); ms(a,m+1,r); merge(a,l,m,r); }\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; ms(a,0,n-1); cout\u003C\u003Ccnt; }","在归并合并时，右段元素被选中即贡献“左段剩余长度”个逆序对，从而在 O(n log n) 内统计逆序对，它也等于使序列有序的最少相邻交换次数。","07-A1-3","计数排序与桶排序",{"id":6285,"title":6286,"level":6178,"module":6179,"seq":703,"goal":6288,"keywords":6289,"explanation":6293,"examples":6305,"exercises":6313,"summary":6344,"next":6345,"nextTitle":6346},"掌握非比较排序：计数排序用“值域桶”统计出现次数，桶排序把值域分桶后桶内再排，突破 O(n log n) 下界。",[5107,6290,6291,6292],"桶排序","非比较","基数思想",[6294,6299],{"title":6295,"content":6296,"codePy":6297,"codeCpp":6298},"1. 计数排序","当元素值域较小（如 0..k）时，统计每个值出现次数，再按次数展开即可，复杂度 O(n+k)。","def count_sort(a, k):\n    c=[0]*(k+1)\n    for x in a: c[x]+=1\n    res=[]\n    for v in range(k+1):\n        res+= [v]*c[v]\n    return res","\u002F\u002F 值域 [0,k]：cnt 统计，再按 cnt 展开",{"title":6300,"content":6301,"codePy":6302,"codeCpp":6303,"tip":6304},"2. 桶排序","把 [0,1) 的值域均分若干桶，元素落入对应桶，桶内各自排序后依次拼接；数据均匀时约 O(n)。","def bucket_sort(a, b=10):\n    buckets=[[] for _ in range(b)]\n    for x in a: buckets[min(b-1,int(x*b))].append(x)\n    res=[]\n    for bk in buckets:\n        res+= sorted(bk)\n    return res","\u002F\u002F 分桶→桶内排→拼接","tip: 非比较排序依赖值域范围，值域过大时不适用。",[6306],{"title":6307,"prompt":6308,"analysis":6309,"codePy":6310,"codeCpp":6311,"run":6312},"例 1：计数排序","值域 0..5，序列 [3,1,3,5] 排序结果？","统计 1:1,3:2,5:1 → 1 3 3 5。","print([1,3,3,5])","\u002F\u002F 1 3 3 5","1 3 3 5",{"choice":6314,"coding":6334},[6315,6321,6327],{"id":38,"stem":6316,"options":6317,"answer":607,"explanation":6320},"计数排序的前提是？",[6318,6087,6319,6088],"值域较小","已部分有序","需要值域范围可控。",{"id":47,"stem":6322,"options":6323,"answer":9,"explanation":6326},"计数排序复杂度（值域 k）？",[2564,6324,2339,6325],"O(n+k)","O(k log k)","统计+展开。",{"id":56,"stem":6328,"options":6329,"answer":607,"explanation":6333},"桶排序的思想是？",[6330,6331,6332,6210],"分桶再桶内排","选主元","归并","值域分桶。",[6335],{"id":67,"title":5107,"prompt":6336,"inputFormat":1801,"outputFormat":5404,"samples":6337,"starterPy":6340,"starterCpp":6341,"hint":6342,"referencePy":6343,"referenceCpp":6341},"输入 n 个 0..100 的整数，输出升序结果。",[6338],{"input":6339,"output":6312},"4\n3 1 3 5","def count_sort(a, k=100):\n    # 实现\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; int c[101]={0}; for(int x:a) c[x]++; for(int v=0;v\u003C=100;v++) while(c[v]--) cout\u003C\u003Cv\u003C\u003C' '; }","开 cnt[101] 统计，再展开。","def count_sort(a,k=100):\n    c=[0]*(k+1)\n    for x in a: c[x]+=1\n    res=[]\n    for v in range(k+1): res+=[v]*c[v]\n    return res","非比较排序利用值域信息：计数排序统计出现次数后展开（O(n+k)），桶排序分桶后桶内排序再拼接，能在特定条件下突破 O(n log n)。","09-A2-1","贪心正确性：贪心选择性质",{"id":6348,"title":6349,"level":6350,"module":6351,"seq":9,"goal":6352,"keywords":6353,"explanation":6357,"examples":6368,"exercises":6373,"summary":6404,"next":6405,"nextTitle":6406},"07-A2-1","双指针求区间（尺取法）","Lv.7","双指针与滑动窗口","掌握同向双指针（尺取法）：用左右指针维护一个区间，根据条件移动右\u002F左指针，高效求出满足某约束的区间。",[6354,6355,5833,6356],"尺取法","同向双指针","滑动窗口基础",[6358,6363],{"title":6359,"content":6360,"codePy":6361,"codeCpp":6362},"1. 尺取思想","右指针不断右移扩大区间；当区间不满足条件时移动左指针收缩。常用于“和≥S 的最短子数组”“最多含 k 种元素的最长区间”等。","def shortest_ge(a, S):\n    n=len(a); l=0; s=0; ans=n+1\n    for r in range(n):\n        s+=a[r]\n        while s>=S:\n            ans=min(ans, r-l+1); s-=a[l]; l+=1\n    return ans if ans\u003C=n else -1","\u002F\u002F l,r 同向；r 扩、l 缩，保持条件",{"title":6364,"content":6365,"codePy":6366,"codeCpp":6367},"2. 何时用尺取","区间具有“单调性”：右扩更易满足、左缩更易破坏（或反之），使得指针不回退，复杂度 O(n)。","print(\"具有单调性时才可尺取\")","\u002F\u002F 否则退化为枚举",[6369],{"title":6370,"prompt":6371,"analysis":6372,"codePy":6253,"codeCpp":5851,"run":759},"例 1：最短子数组和≥S","a=[2,3,1,2,4,3]，S=7，最短长度？","2+3+1+2=8 长4；3+1+2+4=10 长4；4+3=7 长2 => 2。",{"choice":6374,"coding":6391},[6375,6382,6387],{"id":38,"stem":6376,"options":6377,"answer":9,"explanation":6381},"尺取法两指针移动方向？",[6378,6379,3952,6380],"反向","同向不回退","交替","同向且只进不退。",{"id":47,"stem":6383,"options":6384,"answer":9,"explanation":6386},"尺取前提条件？",[1568,6385,2971,6088],"区间具单调性","否则指针会回退。",{"id":56,"stem":6388,"options":6389,"answer":9,"explanation":6390},"最短子数组和≥S 复杂度？",[2339,2338,2564,1245],"每元素进出各一次。",[6392],{"id":67,"title":6393,"prompt":6394,"inputFormat":6395,"outputFormat":6275,"samples":6396,"starterPy":6399,"starterCpp":6400,"hint":6401,"referencePy":6402,"referenceCpp":6403},"最短子数组","给定 n 个正整数与 S，求元素和≥S 的最短子数组长度；无解输出 0。","第一行 n S；第二行 n 个整数。",[6397],{"input":6398,"output":759},"6 7\n2 3 1 2 4 3","n,S=map(int,input().split()); a=list(map(int,input().split()))\nl=0; s=0; ans=n+1\nfor r in range(n):\n    s+=a[r]\n    while s>=S:\n        ans=min(ans,r-l+1); s-=a[l]; l+=1\nprint(ans if ans\u003C=n else 0)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,S; cin>>n>>S; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; int l=0,s=0,ans=n+1; for(int r=0;r\u003Cn;r++){ s+=a[r]; while(s>=S){ ans=min(ans,r-l+1); s-=a[l]; l++; } } cout\u003C\u003C(ans\u003C=n?ans:0); }","右扩左缩，维护区间和。","n,S=map(int,input().split()); a=list(map(int,input().split()))\nl=0; s=0; ans=n+1\nfor r in range(n):\n    s+=a[r]\n    while s>=S:\n        ans=min(ans,r-l+1); s-=a[l]; l+=1\nprint(ans if ans\u003C=n else 0)","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,S; cin>>n>>S; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; int l=0,s=0,ans=n+1; for(int r=0;r\u003Cn;r++){ s+=a[r]; while(s>=S){ ans=min(ans,r-l+1); s-=a[l]; l++; } } cout\u003C\u003C(ans\u003C=n?ans:0); }","尺取法用同向双指针维护区间：右指针扩张、左指针在满足条件时收缩，利用区间单调性做到 O(n)；适用于求最短\u002F最长满足条件的子数组。","07-A2-2","双指针求和（两数\u002F三数之和）",{"id":6405,"title":6406,"level":6350,"module":6351,"seq":63,"goal":6408,"keywords":6409,"explanation":6412,"examples":6424,"exercises":6432,"summary":6463,"next":6464,"nextTitle":6465},"掌握对有序数组用双指针求“和为定值”的组合：两数之和直接左右夹逼，三数之和排序后枚举首元素再双指针。",[6410,2133,6411,4356],"双指针求和","三数之和",[6413,6418],{"title":6414,"content":6415,"codePy":6416,"codeCpp":6417},"1. 两数之和（有序）","对升序数组，左指针 l、右指针 r，若 a[l]+a[r]==T 命中，\u003CT 则 l++，>T 则 r--；O(n)。","def two_sum_sorted(a, T):\n    l,r=0,len(a)-1\n    while l\u003Cr:\n        s=a[l]+a[r]\n        if s==T: return l,r\n        if s\u003CT: l+=1\n        else: r-=1\n    return None","\u002F\u002F 升序：小了左移，大了右移",{"title":6419,"content":6420,"codePy":6421,"codeCpp":6422,"tip":6423},"2. 三数之和","先排序，枚举第一个数 i，再对 i+1..n-1 用双指针求另两数之和为 -a[i]；注意跳过重复值去重。","def three_sum(a):\n    a.sort(); res=[]; n=len(a)\n    for i in range(n-2):\n        if i>0 and a[i]==a[i-1]: continue\n        l,r=i+1,n-1\n        while l\u003Cr:\n            s=a[i]+a[l]+a[r]\n            if s==0: res.append([a[i],a[l],a[r]]); l+=1; r-=1\n            elif s\u003C0: l+=1\n            else: r-=1\n    return res","\u002F\u002F 枚举首元 + 双指针","tip: 去重是关键，否则重复三元组。",[6425],{"title":6426,"prompt":6427,"analysis":6428,"codePy":6429,"codeCpp":6430,"run":6431},"例 1：两数之和","a=[1,3,5,7]，T=8，命中？","1+7=8，下标 0,3。","print((0,3))","\u002F\u002F 0 3","0 3",{"choice":6433,"coding":6451},[6434,6439,6443],{"id":38,"stem":6435,"options":6436,"answer":9,"explanation":6438},"有序数组两数之和用？",[3259,6437,2797,6210],"排序+双指针","左右夹逼 O(n)。",{"id":47,"stem":6440,"options":6441,"answer":9,"explanation":6442},"三数之和复杂度？",[2338,2339,2345,2564],"排序 O(n log n)+枚举 O(n²)。",{"id":56,"stem":6444,"options":6445,"answer":9,"explanation":6450},"三数之和为何要跳过重复？",[6446,6447,6448,6449],"提速","避免重复三元组","排序需要","无必要","否则同值组合重复。",[6452],{"id":67,"title":2133,"prompt":6453,"inputFormat":6454,"outputFormat":6455,"samples":6456,"starterPy":6459,"starterCpp":6460,"hint":6461,"referencePy":6462,"referenceCpp":6460},"升序数组 a 与定值 T，输出任意一对下标 l\u003Cr 使 a[l]+a[r]==T；无解输出 -1 -1。","第一行 n T；第二行 n 个整数。","一行两个整数。",[6457],{"input":6458,"output":6431},"4 8\n1 3 5 7","n,T=map(int,input().split()); a=list(map(int,input().split()))\nl,r=0,n-1\nwhile l\u003Cr:\n    s=a[l]+a[r]\n    if s==T: print(l,r); break\n    elif s\u003CT: l+=1\n    else: r-=1\nelse: print(-1,-1)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,T; cin>>n>>T; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; int l=0,r=n-1; while(l\u003Cr){ int s=a[l]+a[r]; if(s==T){ cout\u003C\u003Cl\u003C\u003C\" \"\u003C\u003Cr; return 0;} if(s\u003CT) l++; else r--; } cout\u003C\u003C\"-1 -1\"; }","左小右大，夹逼。","n,T=map(int,input().split()); a=list(map(int,input().split()))\nl,r=0,n-1\nwhile l\u003Cr:\n    s=a[l]+a[r]\n    if s==T: print(l,r); break\n    elif s\u003CT: l+=1\n    else: r-=1\nelse: print(-1,-1)","有序数组的两数之和用左右指针夹逼（O(n)）；三数之和先排序再枚举首元+双指针（O(n²)），注意跳过重复值去重。","07-A2-3","滑动窗口",{"id":6464,"title":6465,"level":6350,"module":6351,"seq":703,"goal":6467,"keywords":6468,"explanation":6471,"examples":6483,"exercises":6488,"summary":6517,"next":6518,"nextTitle":6519},"掌握滑动窗口技巧：在数组\u002F字符串上维护一个随指针滑动的窗口，用哈希或计数实时更新窗口内统计量（和、最值、种类数）。",[6465,4166,6469,6470],"窗口统计量","子串",[6472,6477],{"title":6473,"content":6474,"codePy":6475,"codeCpp":6476},"1. 窗口与统计量","右指针进窗口时更新统计量，左指针出窗口时撤销；保持窗口满足约束即可 O(n) 遍历。","from collections import Counter\ndef max_vowel(s, k):\n    t=set(\"aeiou\"); win=0; best=0; l=0\n    for r in range(len(s)):\n        if s[r] in t: win+=1\n        while r-l+1>k:\n            if s[l] in t: win-=1\n            l+=1\n        best=max(best,win)\n    return best","\u002F\u002F 进窗加、出窗减，维护计数",{"title":6478,"content":6479,"codePy":6480,"codeCpp":6481,"tip":6482},"2. 固定 vs 可变窗口","固定长度 k 的窗口只关心大小；可变窗口以“满足条件”为边界，常用于最长子串\u002F子数组。","print(\"窗口大小由条件决定\")","\u002F\u002F 可变窗口即尺取的推广","tip: 进出窗口都更新统计量，避免重算。",[6484],{"title":6485,"prompt":6486,"analysis":6487,"codePy":6253,"codeCpp":5851,"run":759},"例 1：最多元音","s=\"abcii\"，k=3，窗口内最多元音数？","子串 \"abc\" 含 a(1)；\"bci\"含 i,i(2)；\"cii\"含 i,i(2) => 2。",{"choice":6489,"coding":6505},[6490,6496,6502],{"id":38,"stem":6491,"options":6492,"answer":9,"explanation":6495},"滑动窗口进出元素时要？",[6493,6494,3117,3560],"重算全部","增量更新统计量","进加出减，O(1) 更新。",{"id":47,"stem":6497,"options":6498,"answer":9,"explanation":6501},"可变窗口的边界由？",[6499,6500,1568,6269],"k","满足条件","以约束为界。",{"id":56,"stem":6503,"options":6504,"answer":9,"explanation":6390},"滑动窗口复杂度？",[2339,2338,2564,1245],[6506],{"id":67,"title":6507,"prompt":6508,"inputFormat":6509,"outputFormat":6275,"samples":6510,"starterPy":6513,"starterCpp":6514,"hint":6515,"referencePy":6516,"referenceCpp":6514},"最长无重复字符子串","给定字符串 s，求不含重复字符的最长子串长度（仅小写字母）。","一行字符串 s。",[6511],{"input":6512,"output":890},"abcabcbb","s=input().strip()\nseen={}; l=0; best=0\nfor r,ch in enumerate(s):\n    if ch in seen and seen[ch]>=l:\n        l=seen[ch]+1\n    seen[ch]=r; best=max(best, r-l+1)\nprint(best)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nint main(){ string s; cin>>s; vector\u003Cint> last(26,-1); int l=0,best=0; for(int r=0;r\u003Cs.size();r++){ int c=s[r]-'a'; if(last[c]>=l) l=last[c]+1; last[c]=r; best=max(best,r-l+1); } cout\u003C\u003Cbest; }","记录每个字符上次位置，重复则移动左边界。","s=input().strip()\nseen={}; l=0; best=0\nfor r,ch in enumerate(s):\n    if ch in seen and seen[ch]>=l:\n        l=seen[ch]+1\n    seen[ch]=r; best=max(best, r-l+1)\nprint(best)","滑动窗口用左右指针维护一段区间，元素进\u002F出窗口时增量更新统计量（计数、和等），从而 O(n) 求最长\u002F最大满足条件的子串或子数组；可变窗口以条件为边界。","07-A2-4","最长满足条件区间",{"id":6518,"title":6519,"level":6350,"module":6351,"seq":770,"goal":6521,"keywords":6522,"explanation":6527,"examples":6539,"exercises":6546,"summary":6579,"next":6580,"nextTitle":6581},"用可变滑动窗口求“最长满足某约束的区间”，如最多含 k 个不同字符、或子数组和≤K，掌握“超界即收缩”的模板。",[6523,6524,6525,6526],"最长区间","可变窗口","约束收缩","种类数",[6528,6533],{"title":6529,"content":6530,"codePy":6531,"codeCpp":6532},"1. 最多 k 种字符","右指针进窗口加种类计数；若种类>k，左指针出窗口直到种类≤k；期间更新最长长度。","from collections import Counter\ndef longest_k(s, k):\n    c=Counter(); l=0; best=0\n    for r in range(len(s)):\n        c[s[r]]+=1\n        while len(c)>k:\n            c[s[l]]-=1\n            if c[s[l]]==0: del c[s[l]]\n            l+=1\n        best=max(best, r-l+1)\n    return best","\u002F\u002F 种类>k 就左缩",{"title":6534,"content":6535,"codePy":6536,"codeCpp":6537,"tip":6538},"2. 子数组和≤K","前缀和+窗口：右扩累加，超 K 则左缩；天然满足“越短越易满足”的单调性。","print(\"约束触发收缩\")","\u002F\u002F 窗口越大越易违例则可用","tip: 约束与窗口大小同向变化才能用滑动窗口。",[6540],{"title":6541,"prompt":6542,"analysis":6543,"codePy":6544,"codeCpp":6545,"run":890},"例 1：最多2种","s=\"eceba\"，k=2，最长？","\"ece\"含 e,c 两种长3；\"eceb\"含3种需缩 => 3。","print(3)","\u002F\u002F 3",{"choice":6547,"coding":6567},[6548,6556,6561],{"id":38,"stem":6549,"options":6550,"answer":9,"explanation":6555},"最长含 k 种字符，超 k 时？",[6551,6552,6553,6554],"右移","左缩直到\u003C=k","重置","停止","收缩左边界。",{"id":47,"stem":6557,"options":6558,"answer":9,"explanation":6560},"滑动窗口求最长依赖？",[1568,6559,2971,3259],"约束与窗口大小同向单调","越大越易违例才可用。",{"id":56,"stem":6562,"options":6563,"answer":9,"explanation":6566},"统计窗口种类用？",[1434,6564,3117,6565],"哈希\u002F计数","栈","计数各字符出现。",[6568],{"id":67,"title":6569,"prompt":6570,"inputFormat":6571,"outputFormat":6275,"samples":6572,"starterPy":6575,"starterCpp":6576,"hint":6577,"referencePy":6578,"referenceCpp":6576},"最长含 k 种字符","字符串 s 与整数 k，求最多含 k 种不同字符的最长子串长度（仅小写字母）。","第一行 s；第二行 k。",[6573],{"input":6574,"output":890},"eceba\n2","s=input().strip(); k=int(input())\nlast={}; l=0; best=0\nfor r,ch in enumerate(s):\n    last[ch]=r+1  # 记录下次应跳过的位置\n    while len(last)>k:\n        # 移出最左出现的字符\n        lc=min(last, key=last.get); del last[lc]\n        l=max(l, 0)\n    best=max(best, r-l+1)\nprint(best)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nint main(){ string s; int k; cin>>s>>k; vector\u003Cint> cnt(26,0); int l=0,best=0,types=0; for(int r=0;r\u003Cs.size();r++){ int c=s[r]-'a'; if(cnt[c]==0) types++; cnt[c]++; while(types>k){ int d=s[l]-'a'; cnt[d]--; if(cnt[d]==0) types--; l++; } best=max(best,r-l+1); } cout\u003C\u003Cbest; }","用计数维护窗口内字符种数，超 k 收缩。","s=input().strip(); k=int(input())\nlast={}; l=0; best=0\nfor r,ch in enumerate(s):\n    last[ch]=r+1\n    while len(last)>k:\n        lc=min(last, key=last.get); del last[lc]\n    best=max(best, r-l+1)\nprint(best)","最长满足条件区间用可变窗口：右扩更新统计量，一旦违反约束（如种类>k、和>K）就收缩左边界，全程维护最长长度；关键保证约束与窗口大小同向单调。","07-A2-5","最短满足条件区间",{"id":6580,"title":6581,"level":6350,"module":6351,"seq":838,"goal":6583,"keywords":6584,"explanation":6589,"examples":6600,"exercises":6605,"summary":6639,"next":6640,"nextTitle":6641},"用可变窗口求“最短覆盖某条件的区间”，如包含全部目标字符、或子数组和≥S，掌握“满足条件才尝试收缩”。",[6585,6586,6587,6588],"最短区间","覆盖","最小窗口","收缩",[6590,6595],{"title":6591,"content":6592,"codePy":6593,"codeCpp":6594},"1. 包含全部字符","右扩直到窗口覆盖所有目标字符，再尽量左缩（仍满足）以最小化长度；记录最小。","from collections import Counter\ndef min_cover(s, t):\n    need=Counter(t); have=0; l=0; best=len(s)+1\n    for r in range(len(s)):\n        if s[r] in need and need[s[r]]>0: have+=1\n        if s[r] in need: need[s[r]]-=1\n        while have==len(t):\n            best=min(best, r-l+1)\n            if s[l] in need: need[s[l]]+=1\n            if s[l] in need and need[s[l]]>0: have-=1\n            l+=1\n    return best if best\u003C=len(s) else 0","\u002F\u002F 满足后才收缩",{"title":6596,"content":6597,"codePy":6598,"codeCpp":6599},"2. 与最长区间区别","最长区间是“违反才收缩”，最短区间是“满足才收缩”方向相反，但都靠窗口单调性 O(n)。","print(\"满足才缩，求最小\")","\u002F\u002F 反过来即可得最小",[6601],{"title":6602,"prompt":6603,"analysis":6604,"codePy":5966,"codeCpp":4490,"run":1663},"例 1：最小覆盖","s=\"ADOBECODEBANC\"，t=\"ABC\"，最短？","\"BANC\" 覆盖 A,B,C 长 4。",{"choice":6606,"coding":6626},[6607,6615,6621],{"id":38,"stem":6608,"options":6609,"answer":9,"explanation":6614},"最短覆盖窗口何时收缩？",[6610,6611,6612,6613],"随时","满足条件后","超长时","不收缩","满足后再尽量缩。",{"id":47,"stem":6616,"options":6617,"answer":9,"explanation":6620},"求最小窗口用？",[6618,6619,3117,3259],"固定窗口","满足才缩的可变窗口","右扩满足、左缩求最小。",{"id":56,"stem":6622,"options":6623,"answer":9,"explanation":6625},"最短\u002F最长窗口都依赖？",[1568,6624,1405,6565],"窗口单调性","指针只进不退。",[6627],{"id":67,"title":6628,"prompt":6629,"inputFormat":6630,"outputFormat":2004,"samples":6631,"starterPy":6635,"starterCpp":6636,"hint":6637,"referencePy":6638,"referenceCpp":6636},"最小覆盖子串","字符串 s 与目标 t，求包含 t 中所有字符的最短子串；不存在输出空串。","第一行 s；第二行 t。",[6632],{"input":6633,"output":6634},"ADOBECODEBANC\nABC","BANC","s=input().strip(); t=input().strip()\nfrom collections import Counter\nneed=Counter(t); have=0; l=0; best=len(s)+1; res=\"\"\nfor r in range(len(s)):\n    if s[r] in need:\n        if need[s[r]]>0: have+=1\n        need[s[r]]-=1\n    while have==len(t):\n        if r-l+1\u003Cbest: best=r-l+1; res=s[l:r+1]\n        if s[l] in need: need[s[l]]+=1\n        if s[l] in need and need[s[l]]>0: have-=1\n        l+=1\nprint(res)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nint main(){ string s,t; cin>>s>>t; vector\u003Cint> need(256,0),cnt(256,0); int tot=0; for(char c:t){ if(need[c]==0) tot++; need[c]++; } int l=0,have=0,best=s.size()+1,bl=0; for(int r=0;r\u003Cs.size();r++){ char c=s[r]; if(need[c]>0){ if(cnt[c]==0) have++; cnt[c]++; } while(have==tot){ if(r-l+1\u003Cbest){ best=r-l+1; bl=l; } char d=s[l]; if(need[d]>0){ cnt[d]--; if(cnt[d]==0) have--; } l++; } } if(best>s.size()) cout\u003C\u003C\"\"; else cout\u003C\u003Cs.substr(bl,best); }","右扩累计需求字符，满足后左缩记录最小。","s=input().strip(); t=input().strip()\nfrom collections import Counter\nneed=Counter(t); have=0; l=0; best=len(s)+1; res=\"\"\nfor r in range(len(s)):\n    if s[r] in need:\n        if need[s[r]]>0: have+=1\n        need[s[r]]-=1\n    while have==len(t):\n        if r-l+1\u003Cbest: best=r-l+1; res=s[l:r+1]\n        if s[l] in need: need[s[l]]+=1\n        if s[l] in need and need[s[l]]>0: have-=1\n        l+=1\nprint(res)","最短满足条件区间与最长相反：右指针扩张直到窗口“满足条件”，随后再尽量左缩以最小化长度；核心是窗口单调性，复杂度 O(n)。","10DFS-A2-1","连通块（图的连通分量）",{"id":5766,"title":5767,"level":4885,"module":6643,"seq":9,"goal":6644,"keywords":6645,"explanation":6649,"examples":6661,"exercises":6669,"summary":6703,"next":6704,"nextTitle":6705},"递归、分治","理解递归思想：把\"规模为 n 的问题\"转化为\"一个或多个规模更小的同类子问题\"，直到小到能直接解决。",[5767,6646,6647,6648],"自相似","子问题","分而治之",[6650,6655],{"title":6651,"content":6652,"codePy":6653,"codeCpp":6654},"1. 大问题 = 小问题 + 一点工作","递归的核心是\"相信函数已经能解决更小的同类问题\"。例如求 n!，可以想成\"n! = n × (n-1)!\"，把求 n! 交给求 (n-1)! 这个更小的同类问题。","# 递归思路：n! = n * (n-1)!\ndef fact(n):\n    return n * fact(n - 1)   # 交给更小的子问题","\u002F\u002F fact(n) = n * fact(n-1)",{"title":6656,"content":6657,"codePy":6658,"codeCpp":6659,"tip":6660},"2. 必须有\"收敛\"到终止条件","递归必须一步步向\"最小可直接解决的情况\"收敛，否则会无限调用。写递归先想清楚：①最小情况怎么直接返回（终止条件）；②怎么把问题缩小（递推关系）。","# 递归三问：终止？缩小？收敛？\ndef fact(n):\n    if n \u003C= 1: return 1      # 终止\n    return n * fact(n - 1)   # 缩小并收敛","\u002F\u002F 终止：n\u003C=1 返回 1；否则向 n-1 收敛","画\"调用树\"理解递归：每个节点是一个子问题，叶子是终止条件。",[6662],{"title":6663,"prompt":6664,"analysis":6665,"codePy":6666,"codeCpp":6667,"run":6668},"例 1：递归求 1~n 和","用递归思想表达 sum(n)=1+2+...+n。","sum(n)=n+sum(n-1)，终止于 sum(0)=0 或 sum(1)=1。","def s(n):\n    if n \u003C= 0: return 0\n    return n + s(n - 1)","int s(int n){ return n\u003C=0 ? 0 : n + s(n-1); }","s(5) = 15",{"choice":6670,"coding":6691},[6671,6677,6683],{"id":38,"stem":6672,"options":6673,"answer":9,"explanation":6676},"递归思想的关键是？",[192,6674,2838,6675],"把问题化为更小的同类子问题","用指针","自相似地缩小问题。",{"id":47,"stem":6678,"options":6679,"answer":9,"explanation":6682},"没有终止条件的递归会？",[6680,6681,1450,3205],"变快","无限递归直至栈溢出","无法收敛会耗尽调用栈。",{"id":56,"stem":6684,"options":6685,"answer":607,"explanation":6690},"写递归先想清楚什么？",[6686,6687,6688,6689],"终止条件与递推关系","输出格式","变量名","循环次数","终止条件 + 缩小方式最重要。",[6692],{"id":67,"title":6693,"prompt":6694,"inputFormat":1456,"outputFormat":6695,"samples":6696,"starterPy":6698,"starterCpp":6699,"hint":6700,"referencePy":6701,"referenceCpp":6702},"递归求和","用递归函数 sum(n) 返回 1+2+...+n（1≤n≤1000）。","一行结果。",[6697],{"input":290,"output":291},"def s(n):\n    # 递归\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nlong long s(int n){\n    \u002F\u002F 递归\n}\nint main(){ int n; cin>>n; cout\u003C\u003Cs(n); }","s(n)=n+s(n-1)，n\u003C=0 返回 0（或 n\u003C=1 返回 1）。","def s(n):\n    if n \u003C= 0: return 0\n    return n + s(n - 1)\nprint(s(int(input())))","#include \u003Ciostream>\nusing namespace std;\nlong long s(int n){ if (n\u003C=0) return 0; return (long long)n + s(n-1); }\nint main(){ int n; cin>>n; cout\u003C\u003Cs(n); }","递归思想把大问题转化为更小的同类子问题（自相似），相信函数能解子问题并叠加一点工作；必须向终止条件收敛，否则无限递归栈溢出。","08-A1-2","递归函数",{"id":6704,"title":6705,"level":4885,"module":6643,"seq":63,"goal":6707,"keywords":6708,"explanation":6711,"examples":6723,"exercises":6731,"summary":6768,"next":6769,"nextTitle":6770},"能把递归思想写成真正的递归函数：函数体内调用自身，并正确传递缩小后的参数。",[6705,1407,6709,6710],"参数缩小","返回",[6712,6717],{"title":6713,"content":6714,"codePy":6715,"codeCpp":6716},"1. 函数体里调用自己","递归函数与普通函数的区别仅在于：函数体内出现了对自身名字的调用。关键是参数要\"变小\"，并且最终结果要通过 return 一层层传回来。","def fact(n):\n    if n \u003C= 1: return 1\n    return n * fact(n - 1)   # 调用自身，参数 n-1 更小","long long fact(int n) {\n    if (n \u003C= 1) return 1;\n    return (long long)n * fact(n - 1);\n}",{"title":6718,"content":6719,"codePy":6720,"codeCpp":6721,"tip":6722},"2. 每一层的结果如何汇合","递归调用会\"压栈\"：先调用的外层要等内层返回。内层把结果 return 给外层，外层再用它计算自己的结果。理解\"调用栈\"有助于调试递归。","# fact(3): 3 * fact(2)\n#   fact(2): 2 * fact(1)\n#     fact(1): 返回 1\n#   -> 2*1=2 -> 3*2=6","\u002F\u002F 每层等下一层返回后再计算","不确定时，手推 2~3 层调用栈，能看清递归怎么收敛和回传。",[6724],{"title":6725,"prompt":6726,"analysis":6727,"codePy":6728,"codeCpp":6729,"run":6730},"例 1：递归打印 1~n","用递归函数打印 1 到 n（每行一个）。","先递归打印 1~n-1，再打印 n；终止于 n\u003C=0 不输出。","def show(n):\n    if n \u003C= 0: return\n    show(n - 1)\n    print(n)\nshow(3)","void show(int n){\n    if (n\u003C=0) return;\n    show(n-1);\n    cout \u003C\u003C n \u003C\u003C endl;\n}","1\n2\n3",{"choice":6732,"coding":6754},[6733,6740,6747],{"id":38,"stem":6734,"options":6735,"answer":9,"explanation":6739},"递归函数与普通函数的区别是？",[6736,6737,2411,6738],"语法不同","函数体内调用自身","必须返回 void","递归函数自调用。",{"id":47,"stem":6741,"options":6742,"answer":9,"explanation":6746},"递归调用时参数应？",[6743,6744,6745,1568],"保持不变","逐步缩小","增大","缩小才能收敛。",{"id":56,"stem":6748,"options":6749,"answer":9,"explanation":6753},"递归结果如何回到最外层？",[1340,6750,6751,6752],"通过 return 逐层回传","打印","不回传","每层 return 给上层。",[6755],{"id":67,"title":6756,"prompt":6757,"inputFormat":6758,"outputFormat":6759,"samples":6760,"starterPy":6763,"starterCpp":6764,"hint":6765,"referencePy":6766,"referenceCpp":6767},"递归倒序打印","用递归函数把 1 到 n 倒序打印（每行一个）。","一行整数 n（1≤n≤1000）。","n 行，从 n 到 1。",[6761],{"input":890,"output":6762},"3\n2\n1","def rev(n):\n    # 递归倒序\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nvoid rev(int n){\n    \u002F\u002F 递归倒序\n}\nint main(){ int n; cin>>n; rev(n); }","先打印 n，再递归 rev(n-1)；n\u003C=0 返回。","def rev(n):\n    if n \u003C= 0: return\n    print(n)\n    rev(n - 1)\nrev(int(input()))","#include \u003Ciostream>\nusing namespace std;\nvoid rev(int n){\n    if (n\u003C=0) return;\n    cout \u003C\u003C n \u003C\u003C endl;\n    rev(n-1);\n}\nint main(){ int n; cin>>n; rev(n); }","递归函数在函数体内调用自身，参数必须逐步缩小并通过 return 把结果逐层回传；调用过程形成\"调用栈\"，理解它能帮助调试与收敛分析。","08-A1-3","递归终止条件",{"id":6769,"title":6770,"level":4885,"module":6643,"seq":703,"goal":6772,"keywords":6773,"explanation":6776,"examples":6788,"exercises":6796,"summary":6832,"next":6833,"nextTitle":6834},"掌握写好递归的\"第一要素\"：明确且正确的终止条件（base case），保证递归能停下来。",[1406,6774,6775,3907],"base case","收敛",[6777,6782],{"title":6778,"content":6779,"codePy":6780,"codeCpp":6781},"1. 终止条件是递归的\"出口\"","没有终止条件，递归会一直调用直到栈溢出崩溃。终止条件应覆盖\"最小\u002F最平凡\"的情况，直接返回结果，不再调用自身。","def fib(n):\n    if n \u003C= 1: return n      # 终止：fib(0)=0, fib(1)=1\n    return fib(n - 1) + fib(n - 2)","int fib(int n){\n    if (n \u003C= 1) return n;    \u002F\u002F 终止\n    return fib(n-1) + fib(n-2);\n}",{"title":6783,"content":6784,"codePy":6785,"codeCpp":6786,"tip":6787},"2. 终止条件要\"收得拢\"","不仅要有一个终止条件，还要保证每次递归都在向它靠近。例如 fact(n) 用 n-1 向 n\u003C=1 收敛；若写成 fact(n-2) 且 n 为偶数会跳过终止值而越界。","# 错误示范：n 每次减 2，n=2 会变 0 跳过 1，可能死循环\u002F越界\ndef bad(n):\n    if n == 1: return 1\n    return n * bad(n - 2)","\u002F\u002F 确保每步都向终止条件收敛","写完递归，用最小几个输入（0、1、2）手算验证能终止且结果正确。",[6789],{"title":6790,"prompt":6791,"analysis":6792,"codePy":6793,"codeCpp":6794,"run":6795},"例 1：斐波那契的终止","斐波那契 fib(n)=fib(n-1)+fib(n-2)，终止条件是什么？","fib(0)=0、fib(1)=1 作为两个终止条件。","def fib(n):\n    if n \u003C= 1: return n\n    return fib(n-1) + fib(n-2)","int fib(int n){ if (n\u003C=1) return n; return fib(n-1)+fib(n-2); }","fib(6) = 8",{"choice":6797,"coding":6820},[6798,6805,6813],{"id":38,"stem":6799,"options":6800,"answer":9,"explanation":6804},"递归缺少终止条件会？",[1448,6801,6802,6803],"栈溢出崩溃","返回随机值","自动补终止","无限递归耗尽调用栈。",{"id":47,"stem":6806,"options":6807,"answer":607,"explanation":6812},"斐波那契的终止条件通常是？",[6808,6809,6810,6811],"fib(0)=0,fib(1)=1","fib(1)=1","无","fib(n)=n","两个最小项作为 base case。",{"id":56,"stem":6814,"options":6815,"answer":9,"explanation":6819},"好的终止条件还要保证什么？",[6816,6817,5227,6818],"代码短","每次递归都向它收敛","返回字符串","必须能真正\"收得拢\"。",[6821],{"id":67,"title":6822,"prompt":6823,"inputFormat":1456,"outputFormat":6824,"samples":6825,"starterPy":6827,"starterCpp":6828,"hint":6829,"referencePy":6830,"referenceCpp":6831},"递归斐波那契","用递归实现 fib(n)（fib(0)=0, fib(1)=1, fib(n)=fib(n-1)+fib(n-2)），输入 n（0≤n≤30）输出结果。","一行 fib(n)。",[6826],{"input":624,"output":148},"def fib(n):\n    # 递归 + 终止\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nint fib(int n){\n    \u002F\u002F 递归 + 终止\n}\nint main(){ int n; cin>>n; cout\u003C\u003Cfib(n); }","n\u003C=1 返回 n；否则返回 fib(n-1)+fib(n-2)。","def fib(n):\n    if n \u003C= 1: return n\n    return fib(n - 1) + fib(n - 2)\nprint(fib(int(input())))","#include \u003Ciostream>\nusing namespace std;\nint fib(int n){ if (n\u003C=1) return n; return fib(n-1)+fib(n-2); }\nint main(){ int n; cin>>n; cout\u003C\u003Cfib(n); }","终止条件（base case）是递归的出口，必须覆盖最平凡情况并直接返回；同时要保证每次递归都向终止条件收敛（如 n-1 而非跳过），否则栈溢出。","08-A1-4","简单递归",{"id":6833,"title":6834,"level":4885,"module":6643,"seq":770,"goal":6836,"keywords":6837,"explanation":6840,"examples":6852,"exercises":6859,"summary":6896,"next":6897,"nextTitle":6898},"通过几个经典小例子（求和、数字各位和、幂、打印）巩固递归写法，体会\"相信子问题已解决\"。",[6834,6838,6839,6751],"数字和","幂",[6841,6846],{"title":6842,"content":6843,"codePy":6844,"codeCpp":6845},"1. 数字各位之和","digit_sum(n) = 末位 n%10 + digit_sum(n\u002F10)，终止于 n==0 返回 0。这是\"拆最后一位 + 交给去掉末位的子问题\"的典型模式。","def digit_sum(n):\n    if n == 0: return 0\n    return n % 10 + digit_sum(n \u002F\u002F 10)","int digit_sum(int n){\n    if (n == 0) return 0;\n    return n % 10 + digit_sum(n \u002F 10);\n}",{"title":6847,"content":6848,"codePy":6849,"codeCpp":6850,"tip":6851},"2. 幂与打印序列","pow(a,b)=a*pow(a,b-1)，终止 b==0 返回 1；打印 1~n 则\"先递归打印前 n-1，再打印 n\"。同一套\"缩小参数 + 终止条件\"套路适用于很多小问题。","def pow(a, b):\n    if b == 0: return 1\n    return a * pow(a, b - 1)","long long pow(int a, int b){ return b==0 ? 1 : (long long)a * pow(a, b-1); }","遇到\"处理 n 个东西\"，常可写成\"处理前 n-1 个 + 处理第 n 个\"的递归。",[6853],{"title":6854,"prompt":6855,"analysis":6856,"codePy":6857,"codeCpp":6858,"run":624},"例 1：各位数字之和","用递归求 123 的各位数字之和。","123%10=3 + digit_sum(12)；12%10=2 + digit_sum(1)；1%10=1 + digit_sum(0)=0 → 3+2+1=6。","print(digit_sum(123))","cout \u003C\u003C digit_sum(123);",{"choice":6860,"coding":6882},[6861,6869,6876],{"id":38,"stem":6862,"options":6863,"answer":9,"explanation":6868},"digit_sum(123) 的递归第一步是？",[6864,6865,6866,6867],"123 + digit_sum(12)","3 + digit_sum(12)","digit_sum(123)","1 + digit_sum(23)","拆末位 3，剩下 12。",{"id":47,"stem":6870,"options":6871,"answer":607,"explanation":6875},"pow(a,b) 的终止条件应是？",[6872,6873,6874,6810],"b==0 返回 1","a==0","b==1","a^0=1。",{"id":56,"stem":6877,"options":6878,"answer":9,"explanation":6881},"“处理前 n-1 个再处理第 n 个”属于？",[6879,6880,2509,6039],"迭代","递归典型套路","常见递归分解方式。",[6883],{"id":67,"title":6884,"prompt":6885,"inputFormat":1456,"outputFormat":6886,"samples":6887,"starterPy":6891,"starterCpp":6892,"hint":6893,"referencePy":6894,"referenceCpp":6895},"递归数字和","用递归函数 digit_sum(n) 返回非负整数 n（0≤n≤10^9）的各位数字之和。","一行数字和。",[6888,6890],{"input":6889,"output":624},"123",{"input":891,"output":891},"def digit_sum(n):\n    # 递归\n    pass\n","#include \u003Ciostream>\nusing namespace std;\nint digit_sum(int n){\n    \u002F\u002F 递归\n}\nint main(){ int n; cin>>n; cout\u003C\u003Cdigit_sum(n); }","n==0 返回 0；否则返回 n%10 + digit_sum(n\u002F\u002F10)。","def digit_sum(n):\n    if n == 0: return 0\n    return n % 10 + digit_sum(n \u002F\u002F 10)\nprint(digit_sum(int(input())))","#include \u003Ciostream>\nusing namespace std;\nint digit_sum(int n){ if (n==0) return 0; return n%10 + digit_sum(n\u002F10); }\nint main(){ int n; cin>>n; cout\u003C\u003Cdigit_sum(n); }","用\"拆最后一位 \u002F 处理前 n-1 个 + 第 n 个 \u002F 幂降次\"等套路写简单递归：数字和 digit_sum(n)=n%10+digit_sum(n\u002F10)，pow(a,b)=a*pow(a,b-1)，关键是终止条件与参数缩小。","24基础数论-A1-1","奇偶性",{"id":6174,"title":6175,"level":2505,"module":6643,"seq":9,"goal":6900,"keywords":6901,"explanation":6903,"examples":6915,"exercises":6922,"summary":6956,"next":6957,"nextTitle":6958},"掌握分治：把问题分成独立子问题，各自解决后合并结果（与单纯的递归区别在于\"合\"）。",[5244,6902,5245,1405],"分解",[6904,6909],{"title":6905,"content":6906,"codePy":6907,"codeCpp":6908},"1. 三步","分（拆成小问题）、治（递归解决）、合（合并子结果）。归并排序是典型：分两半、排序、合并。","def divide(a):\n    if len(a) \u003C= 1: return a\n    m = len(a) \u002F\u002F 2\n    L = divide(a[:m]); R = divide(a[m:])   # 分+治\n    return merge(L, R)                       # 合","\u002F\u002F 分 -> 治 -> 合",{"title":6910,"content":6911,"codePy":6912,"codeCpp":6913,"tip":6914},"2. 与递归区别","递归强调\"自调用\"，分治强调\"分解+合并\"且子问题互不重叠（与 DP 的重叠子问题相对）。","# 分治子问题独立；DP 子问题重叠","\u002F\u002F 分治 vs 递归 vs DP","看到\"大问题化为同类小问题且需合并\"就考虑分治。",[6916],{"title":6917,"prompt":6918,"analysis":6919,"codePy":6920,"codeCpp":6921,"run":5244},"例 1：分治求最大值","用分治求数组中最大值：分别求左右半最大值再取较大。","规模对半，深度 O(log n)，每层 O(1) 合并。","def mx(a, l, r):\n    if l == r: return a[l]\n    m = (l+r)\u002F\u002F2\n    return max(mx(a,l,m), mx(a,m+1,r))","\u002F\u002F 分治求最大",{"choice":6923,"coding":6946},[6924,6931,6939],{"id":38,"stem":6925,"options":6926,"answer":9,"explanation":6930},"分治的三步是？",[6101,6927,6928,6929],"分、治、合","循环、递归、排序","排序、查找、合并","分解、解决、合并。",{"id":47,"stem":6932,"options":6933,"answer":9,"explanation":6938},"分治与 DP 的区别？",[6934,6935,6936,6937],"没有区别","分治子问题独立，DP 子问题重叠","分治更慢","DP 不用递归","关键在子问题是否重叠。",{"id":56,"stem":6940,"options":6941,"answer":9,"explanation":6945},"归并排序体现分治的哪一步\"合\"？",[6902,6942,6943,6944],"合并两个有序段","递归调用","没有合","合并是核心\"合\"。",[6947],{"id":67,"title":6948,"prompt":6949,"inputFormat":1801,"outputFormat":6950,"samples":6951,"starterPy":6953,"starterCpp":6954,"hint":6955,"referencePy":6953,"referenceCpp":6954},"分治求最大值","输入 n（1≤n≤100000）和 n 个整数，用分治（对半递归）求最大值。","一行整数最大值。",[6952],{"input":4280,"output":290},"import sys\nsys.setrecursionlimit(200000)\na = list(map(int, input().split()))\ndef mx(l, r):\n    if l == r: return a[l]\n    m = (l + r)\u002F\u002F2\n    return max(mx(l, m), mx(m+1, r))\nprint(mx(0, len(a)-1))","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[100005];\nint mx(int l, int r){ if(l==r) return a[l]; int m=(l+r)\u002F2; return max(mx(l,m), mx(m+1,r)); }\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; cout\u003C\u003Cmx(0,n-1); }","对半递归，返回左右半最大值的较大者。","分治=分解+解决+合并，子问题独立；与递归（只强调自调用）、DP（子问题重叠）不同。归并排序、快排是典型。","08-A2-2","归并排序（分治）",{"id":6957,"title":6958,"level":2505,"module":6643,"seq":63,"goal":6960,"keywords":6961,"explanation":6962,"examples":6973,"exercises":6977,"summary":7002,"next":7003,"nextTitle":7004},"用\"分治\"视角重写并理解归并排序的分解与合并过程。",[6332,5244,5245,4889],[6963,6968],{"title":6964,"content":6965,"codePy":6966,"codeCpp":6967},"1. 分解","不断把区间对半分直到长度为 1；合并：把两个有序子区间合成一个有序区间。","def ms(a):\n    if len(a) \u003C= 1: return a\n    m = len(a) \u002F\u002F 2\n    L = ms(a[:m]); R = ms(a[m:])\n    i = j = 0; res = []\n    while i \u003C len(L) and j \u003C len(R):\n        res.append(L[i] if L[i] \u003C= R[j] else R[j]); \n        if L[i] \u003C= R[j]: i += 1\n        else: j += 1\n    res.extend(L[i:]); res.extend(R[j:])\n    return res","\u002F\u002F 对半分 + 双指针合并",{"title":6191,"content":6969,"codePy":6970,"codeCpp":6971,"tip":6972},"深度 log n，每层合并 O(n)，总 O(n log n)，稳定，需 O(n) 辅助空间。","# 稳定 O(n log n)，辅助 O(n)","\u002F\u002F 稳定，额外 O(n)","分治版归并强调\"先分后合\"。",[6974],{"title":5260,"prompt":6975,"analysis":6976,"codePy":5263,"codeCpp":5264,"run":5265},"合并 [1,4] 和 [2,3]。","双指针取小：[1,2,3,4]。",{"choice":6978,"coding":6996},[6979,6987,6989],{"id":38,"stem":6980,"options":6981,"answer":607,"explanation":6986},"归并排序分治的\"分\"是？",[6982,6983,6984,6985],"对半分","随机分","按值分","不分","对半分解。",{"id":47,"stem":5278,"options":6988,"answer":9,"explanation":5280},[2338,2564,2339,1245],{"id":56,"stem":6990,"options":6991,"answer":9,"explanation":6995},"归并排序稳定且需要？",[6992,6993,6994,5615],"O(1) 空间","O(n) 辅助空间","O(log n) 空间","辅助数组 O(n)。",[6997],{"id":67,"title":2627,"prompt":6998,"inputFormat":1801,"outputFormat":5232,"samples":6999,"starterPy":5286,"starterCpp":5287,"hint":7001,"referencePy":5286,"referenceCpp":5287},"输入 n（1≤n≤100000）和 n 个整数，用分治版（对半递归+双指针合并）归并排序升序输出。",[7000],{"input":4280,"output":5235},"对半递归，再双指针合并到辅助数组。","分治版归并排序：对半分解到长度 1，再用双指针合并两个有序段；稳定 O(n log n)，需 O(n) 辅助空间。","08-A2-3","快速排序（分治）",{"id":7003,"title":7004,"level":2505,"module":6643,"seq":703,"goal":7006,"keywords":7007,"explanation":7009,"examples":7021,"exercises":7027,"summary":7048,"next":7049,"nextTitle":7050},"用分治视角理解快排：选轴分区使问题独立，再递归处理左右子区间。",[5459,5244,7008,5193],"轴",[7010,7015],{"title":7011,"content":7012,"codePy":7013,"codeCpp":7014},"1. 分治结构","选轴→把区间分成\"≤轴\"和\"≥轴\"两部分（治：两部分独立递归）→无需显式\"合\"（轴已在正确位）。","def qs(a, l, r):\n    if l >= r: return\n    p = a[l]; i = l; j = r\n    while i \u003C j:\n        while i \u003C j and a[j] >= p: j -= 1\n        a[i] = a[j]\n        while i \u003C j and a[i] \u003C= p: i += 1\n        a[j] = a[i]\n    a[i] = p\n    qs(a, l, i-1); qs(a, i+1, r)","\u002F\u002F 分区后递归左右",{"title":7016,"content":7017,"codePy":7018,"codeCpp":7019,"tip":7020},"2. 分治 vs 归并","快排\"先分后治\"且轴就位无需合并；归并\"先治后合\"。平均 O(n log n)。","# 快排：轴就位，不用合","\u002F\u002F 快排省去合并步骤","快排是\"原地分治\"的代表。",[7022],{"title":7023,"prompt":7024,"analysis":7025,"codePy":7026,"codeCpp":5212,"run":5213},"例 1：单次分区","以首为轴的单次分区对 [3,1,4,1,5]。","轴=3，分区后左≤3、右≥3。","print(\"轴就位\")",{"choice":7028,"coding":7042},[7029,7037,7039],{"id":38,"stem":7030,"options":7031,"answer":9,"explanation":7036},"快排分治后是否需要\"合\"？",[7032,7033,7034,7035],"需要合并","不需要，轴已就位","需要排序","需要反转","轴分区后即就位。",{"id":47,"stem":5217,"options":7038,"answer":9,"explanation":3060},[2338,2564,2339,1245],{"id":56,"stem":2941,"options":7040,"answer":9,"explanation":7041},[2564,2339,2338,1246],"已有序且轴固定退化。",[7043],{"id":67,"title":2628,"prompt":7044,"inputFormat":1801,"outputFormat":5232,"samples":7045,"starterPy":5236,"starterCpp":5237,"hint":7047,"referencePy":5236,"referenceCpp":5237},"输入 n（1≤n≤100000）和 n 个整数，用分治版（随机选轴分区+递归左右）快排升序输出。",[7046],{"input":4280,"output":5235},"随机选轴，双指针分区，递归左右。","分治版快排：选轴分区使左右独立、轴就位，递归处理左右；平均 O(n log n)，无需显式合并。","08-A2-4","二分递归",{"id":7049,"title":7050,"level":2505,"module":6643,"seq":770,"goal":7052,"keywords":7053,"explanation":7056,"examples":7068,"exercises":7075,"summary":7105,"next":7106,"nextTitle":7107},"掌握二分递归：在递归中把规模对半（而非减一），复杂度降为 O(log n) 或 O(n log n)。",[7050,7054,1405,7055],"对半","log",[7057,7062],{"title":7058,"content":7059,"codePy":7060,"codeCpp":7061},"1. 对半递归","如\"幂 a^n 用 a^(n\u002F2) 平方\"，规模每次减半，深度 O(log n)。","def pow2(a, n):\n    if n == 0: return 1\n    h = pow2(a, n \u002F\u002F 2)\n    return h * h if n % 2 == 0 else h * h * a","\u002F\u002F 规模对半，深度 log n",{"title":7063,"content":7064,"codePy":7065,"codeCpp":7066,"tip":7067},"2. 与线性递归对比","线性递归（减一）深度 O(n)；二分递归（对半）深度 O(log n)，常用于快速幂、二分查找递归版。","# 线性递归 O(n) vs 二分递归 O(log n)","\u002F\u002F 二分递归更快","把\"减一递归\"改写成\"对半递归\"常是优化的关键。",[7069],{"title":7070,"prompt":7071,"analysis":7072,"codePy":7073,"codeCpp":7074,"run":3055},"例 1：快速幂","a^n 的二分递归深度约为？","规模对半，深度 log₂n。","print(\"log n 深度\")","\u002F\u002F 深度 log n",{"choice":7076,"coding":7092},[7077,7081,7085],{"id":38,"stem":7078,"options":7079,"answer":9,"explanation":7080},"二分递归（对半）的深度？",[2338,1246,2339,1245],"规模每次减半。",{"id":47,"stem":7082,"options":7083,"answer":9,"explanation":7084},"线性递归（减一）的深度？",[1246,2338,1245,2339],"每次减一。",{"id":56,"stem":7086,"options":7087,"answer":9,"explanation":7091},"快速幂用哪种递归？",[7088,7089,7090,1568],"线性","二分（对半）","不用递归","对半提升效率。",[7093],{"id":67,"title":7094,"prompt":7095,"inputFormat":7096,"outputFormat":7097,"samples":7098,"starterPy":7101,"starterCpp":7102,"hint":7103,"referencePy":7104,"referenceCpp":7102},"二分递归快速幂","输入 a（1≤a≤10^9）和 n（0≤n≤60），用二分（对半）递归求 a^n。","一行 a n。","一行整数 a^n。",[7099],{"input":7100,"output":2548},"2 10","a, n = map(int, input().split())\ndef pow2(a, n):\n    if n == 0: return 1\n    h = pow2(a, n \u002F\u002F 2) % (10**18)\n    return (h * h % (10**18) if n % 2 == 0 else h * h % (10**18) * a % (10**18))\nprint(pow2(a, n))","#include \u003Ciostream>\nusing namespace std;\nlong long pow2(long long a, long long n){ if(n==0) return 1; long long h=pow2(a,n\u002F2); long long r=h*h; if(n%2) r=r*a; return r; }\nint main(){ long long a,n; cin>>a>>n; cout\u003C\u003Cpow2(a,n); }","递归规模对半：h=pow(a,n\u002F2)，返回 h*h（奇乘 a）。","a, n = map(int, input().split())\ndef pow2(a, n):\n    if n == 0: return 1\n    h = pow2(a, n \u002F\u002F 2)\n    return h * h if n % 2 == 0 else h * h * a\nprint(pow2(a, n))","二分递归每次把规模对半，深度 O(log n)（如快速幂）；比线性递归（减一，O(n)）快得多。","08-A2-5","递归搜索",{"id":7106,"title":7107,"level":2505,"module":6643,"seq":838,"goal":7109,"keywords":7110,"explanation":7112,"examples":7124,"exercises":7131,"summary":7160,"next":7161,"nextTitle":7162},"掌握用递归进行搜索\u002F枚举（如 DFS 遍历、子集\u002F排列生成），理解搜索树与回溯。",[7107,7111,2796,1542],"DFS",[7113,7118],{"title":7114,"content":7115,"codePy":7116,"codeCpp":7117},"1. 递归遍历","DFS 用递归访问子节点，先处理当前再递归子节点，形成搜索树。","def dfs(u):\n    vis[u] = True\n    for v in g[u]:\n        if not vis[v]: dfs(v)","void dfs(int u){ vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs(v); }",{"title":7119,"content":7120,"codePy":7121,"codeCpp":7122,"tip":7123},"2. 回溯枚举","在递归中\"做选择→递归→撤销选择\"，如生成所有排列\u002F子集。","def dfs(i):\n    if i == n: ans.append(path[:]); return\n    path.append(a[i]); dfs(i+1); path.pop()  # 选\n    dfs(i+1)                                 # 不选","\u002F\u002F 选\u002F不选 两种递归分支","回溯 = 递归 + 撤销选择；注意搜索树规模（指数）。",[7125],{"title":7126,"prompt":7127,"analysis":2824,"codePy":7128,"codeCpp":7129,"run":7130},"例 1：子集生成","用递归生成 n 个元素的全子集（选\u002F不选两种分支）。","print(\"2^n 个子集\")","\u002F\u002F 2^n","2^n",{"choice":7132,"coding":7148},[7133,7137,7142],{"id":38,"stem":7134,"options":7135,"answer":9,"explanation":7136},"DFS 遍历通常用？",[192,1405,3117,2509],"递归自然形成搜索树。",{"id":47,"stem":7138,"options":7139,"answer":9,"explanation":7141},"回溯的关键是？",[5459,7140,6039,3117],"撤销选择","做选择后撤销以枚举其他分支。",{"id":56,"stem":7143,"options":7144,"answer":63,"explanation":7147},"生成 n 元素全子集的递归分支数？",[3054,7145,7130,7146],"n!","n^2","每元素选\u002F不选。",[7149],{"id":67,"title":1202,"prompt":7150,"inputFormat":7151,"outputFormat":7152,"samples":7153,"starterPy":7157,"starterCpp":7158,"hint":7159,"referencePy":7157,"referenceCpp":7158},"输入 n（1≤n≤15）和 k（0≤k≤n），用递归输出从 1..n 中选 k 个的所有组合（每行一个组合，元素升序空格分隔）。","一行 n k。","若干行，每行一个组合。",[7154],{"input":7155,"output":7156},"4 2","1 2\n1 3\n1 4\n2 3\n2 4\n3 4","n, k = map(int, input().split())\nres = []\npath = []\ndef dfs(start):\n    if len(path) == k:\n        res.append(path[:]); return\n    for i in range(start, n+1):\n        path.append(i); dfs(i+1); path.pop()\ndfs(1)\nfor p in res: print(*p)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n,k; vector\u003Cint> path;\nvoid dfs(int s){ if((int)path.size()==k){ for(int x:path) cout\u003C\u003Cx\u003C\u003C\" \"; cout\u003C\u003Cendl; return; } for(int i=s;i\u003C=n;i++){ path.push_back(i); dfs(i+1); path.pop_back(); } }\nint main(){ cin>>n>>k; dfs(1); }","递归从 start 起选数，凑满 k 个即记录，注意升序（i 递增）。","递归搜索用 DFS\u002F回溯枚举：DFS 遍历图、回溯\"选择-递归-撤销\"生成排列\u002F子集；理解搜索树与规模（指数）。","09-A1-1","贪心思想",{"id":7161,"title":7162,"level":2505,"module":6039,"seq":9,"goal":7164,"keywords":7165,"explanation":7169,"examples":7181,"exercises":7186,"summary":7218,"next":7219,"nextTitle":7166},"理解贪心：每一步都做\"当前看起来最优\"的选择，期望得到全局最优。",[6039,7166,7167,7168],"局部最优","全局最优","策略",[7170,7175],{"title":7171,"content":7172,"codePy":7173,"codeCpp":7174},"1. 贪心是什么","在分步决策中，每步取当前最优（不回退），希望累积成全局最优。","# 每步选当前最好的，不回溯\nfor x in items:\n    if can_take(x): take(x)","\u002F\u002F 局部最优，不回溯",{"title":7176,"content":7177,"codePy":7178,"codeCpp":7179,"tip":7180},"2. 何时可用","当问题具有\"贪心选择性质\"（局部最优能导向全局最优）和\"最优子结构\"。不是所有问题都能贪心。","# 需证明贪心正确性；否则用 DP","\u002F\u002F 贪心需证明","写贪心前先问：局部最优能导出全局最优吗？",[7182],{"title":7183,"prompt":7184,"analysis":7185,"codePy":4786,"codeCpp":4432,"run":624},"例 1：找零","用面额 1\u002F5\u002F10\u002F20\u002F50\u002F100 凑 68，最少纸币数（贪心：先用大面额）。","50+10+5+1+1+1=6 张。",{"choice":7187,"coding":7206},[7188,7193,7201],{"id":38,"stem":7189,"options":7190,"answer":9,"explanation":7192},"贪心每步选择？",[7167,7191,1568,5983],"当前局部最优","局部最优累积。",{"id":47,"stem":7194,"options":7195,"answer":9,"explanation":7200},"贪心能用的前提？",[7196,7197,7198,7199],"任意问题","有贪心选择性质","必须排序","必须 DP","局部最优导全局最优。",{"id":56,"stem":7202,"options":7203,"answer":9,"explanation":7205},"不能用贪心时通常用？",[6039,7204,3117,2509],"DP\u002F回溯","如 0-1 背包用 DP。",[7207],{"id":67,"title":7208,"prompt":7209,"inputFormat":7210,"outputFormat":7211,"samples":7212,"starterPy":7215,"starterCpp":7216,"hint":7217,"referencePy":7215,"referenceCpp":7216},"贪心找零","输入金额 amt（1≤amt≤10^9）和 m 种面额（从大到小且能贪心，如 1,5,10,20,50,100），用贪心求最少纸币数。","第一行 amt m；第二行 m 个面额（从大到小）。","一行整数最少张数。",[7213],{"input":7214,"output":624},"68 6\n100 50 20 10 5 1","amt, m = map(int, input().split())\nfaces = list(map(int, input().split()))\nans = 0\nfor f in faces:\n    ans += amt \u002F\u002F f\n    amt %= f\nprint(ans)","#include \u003Ciostream>\nusing namespace std;\nlong long amt; int m; long long f[20];\nint main(){ cin>>amt>>m; for(int i=0;i\u003Cm;i++) cin>>f[i]; long long ans=0; for(int i=0;i\u003Cm;i++){ ans+=amt\u002Ff[i]; amt%=f[i]; } cout\u003C\u003Cans; }","面额从大到小，每次取尽可能多张并求余。","贪心每步取当前最优且不回退，期望得全局最优；仅在具\"贪心选择性质\"时正确，否则需 DP。","09-A1-2",{"id":7219,"title":7166,"level":2505,"module":6039,"seq":63,"goal":7221,"keywords":7222,"explanation":7225,"examples":7237,"exercises":7244,"summary":7278,"next":7279,"nextTitle":7280},"理解\"局部最优\"与\"全局最优\"的关系，学会判断一个问题能否贪心。",[7166,7167,7223,7224],"证明","反例",[7226,7231],{"title":7227,"content":7228,"codePy":7229,"codeCpp":7230},"1. 局部 vs 全局","局部最优指\"当前这一步最好\"；贪心假设它累加成全局最优。需要证明（或举反例否定）。","# 反例：某些问题局部最优≠全局最优\n# 如背包(非整数)不能简单贪心","\u002F\u002F 局部最优不总导向全局",{"title":7232,"content":7233,"codePy":7234,"codeCpp":7235,"tip":7236},"2. 验证方法","交换论证（最优解可通过交换成贪心解而不变差）、或举反例。","# 交换论证：证明贪心解不劣于任意最优解","\u002F\u002F 用反例排除错误贪心","考试常给反例让你判断某贪心是否正确。",[7238],{"title":7239,"prompt":7240,"analysis":7241,"codePy":7242,"codeCpp":7243,"run":5810},"例 1：反例","0-1 背包能否按性价比贪心？","不能：受容量整数限制，可能剩空间浪费，需 DP。","print(\"不能，需用 DP\")","\u002F\u002F 反例存在",{"choice":7245,"coding":7266},[7246,7253,7258],{"id":38,"stem":7247,"options":7248,"answer":9,"explanation":7252},"局部最优是？",[7249,7250,1568,7251],"全局最好","当前步最好","最差","每一步的最好选择。",{"id":47,"stem":7254,"options":7255,"answer":9,"explanation":7257},"验证贪心正确性常用？",[1568,7256,3117,2509],"交换论证\u002F反例","证明或举反例。",{"id":56,"stem":7259,"options":7260,"answer":9,"explanation":7265},"0-1 背包用贪心（按性价比）？",[7261,7262,7263,7264],"可以","不行（需 DP）","总是最优","排序即可","整数容量限制下反例存在。",[7267],{"id":67,"title":7268,"prompt":7269,"inputFormat":7270,"outputFormat":7271,"samples":7272,"starterPy":7275,"starterCpp":7276,"hint":7277,"referencePy":7275,"referenceCpp":7276},"区间调度（贪心）","输入 n（1≤n≤100000）个区间（开始,结束），用贪心（按结束时间排序，依次选不冲突且最早结束的）求最多不重叠区间数。","第一行 n；其后 n 行每行 s e。","一行整数最多不重叠区间数。",[7273],{"input":7274,"output":759},"3\n1 3\n2 4\n3 5","n = int(input())\niv = [tuple(map(int, input().split())) for _ in range(n)]\niv.sort(key=lambda x: x[1])\nlast = -1; cnt = 0\nfor s, e in iv:\n    if s >= last: cnt += 1; last = e\nprint(cnt)","#include \u003Ciostream>\n#include \u003Calgorithm>\n#include \u003Cvector>\nusing namespace std;\nstruct I{ int s,e; };\nvector\u003CI> v;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ I t; cin>>t.s>>t.e; v.push_back(t); } sort(v.begin(), v.end(), [](I x,I y){ return x.e\u003Cy.e; }); int last=-1,cnt=0; for(auto t:v){ if(t.s>=last){ cnt++; last=t.e; } } cout\u003C\u003Ccnt; }","按结束时间排序，贪心选最早结束且不与已选冲突的。","局部最优是\"当前步最好\"，贪心假设它导向全局；需用交换论证证明或举反例否定（如 0-1 背包）。","09-A1-3","简单贪心",{"id":7279,"title":7280,"level":2505,"module":6039,"seq":703,"goal":7282,"keywords":7283,"explanation":7285,"examples":7297,"exercises":7302,"summary":7332,"next":7333,"nextTitle":7334},"通过经典简单贪心题（区间选点、拼接等）巩固贪心套路。",[7280,5833,3117,7284],"选择",[7286,7291],{"title":7287,"content":7288,"codePy":7289,"codeCpp":7290},"1. 区间调度","按结束时间升序，依次选\"与已选不冲突且结束最早\"的区间，可得最多不重叠区间。","a.sort(key=lambda x:x[1])\nlast=-1; cnt=0\nfor s,e in a:\n    if s>=last: cnt+=1; last=e","\u002F\u002F 按结束时间排序贪心",{"title":7292,"content":7293,"codePy":7294,"codeCpp":7295,"tip":7296},"2. 关键","先排序（给贪心一个\"顺序\"），再线性扫描做局部最优选择。","# 排序 + 扫描 = 常见贪心模板","\u002F\u002F 排序后贪心","多数贪心题都要先排序再扫描。",[7298],{"title":7299,"prompt":7300,"analysis":7301,"codePy":6253,"codeCpp":5851,"run":759},"例 1：区间调度","区间 [(1,3),(2,4),(3,5)] 最多不重叠数？","选 (1,3) 与 (3,5) = 2 个。",{"choice":7303,"coding":7324},[7304,7310,7316],{"id":38,"stem":7305,"options":7306,"answer":9,"explanation":7309},"区间调度（最多不重叠）贪心按？",[7307,7308,6269,1568],"开始时间","结束时间升序","最早结束优先。",{"id":47,"stem":7311,"options":7312,"answer":607,"explanation":7315},"简单贪心常是？",[7313,7314,1542,2509],"先排序再扫描","随机选","排序给顺序。",{"id":56,"stem":7317,"options":7318,"answer":9,"explanation":7323},"贪心选区间时要求？",[7319,7320,7321,7322],"选最长","不冲突且最早结束","选最短","选第一个","不冲突+最早结束。",[7325],{"id":67,"title":7326,"prompt":7327,"inputFormat":7270,"outputFormat":6275,"samples":7328,"starterPy":7275,"starterCpp":7276,"hint":7331,"referencePy":7275,"referenceCpp":7276},"区间调度再练","输入 n（1≤n≤100000）个区间，求最多不重叠区间数（同上一题，巩固）。",[7329],{"input":7330,"output":890},"4\n1 2\n2 3\n3 4\n1 5","按结束时间排序后贪心计数。","简单贪心常是\"先按某关键字排序，再线性扫描做局部最优\"；如区间调度按结束时间排序选最早结束。","09-A1-4","直接选择",{"id":7333,"title":7334,"level":2505,"module":6039,"seq":770,"goal":7336,"keywords":7337,"explanation":7339,"examples":7351,"exercises":7358,"summary":7385,"next":7386,"nextTitle":1434},"掌握\"直接选择\"型贪心：每步直接挑当前最优元素（如选择排序、选最大可用）。",[7334,7338,4949,6039],"每步最优",[7340,7345],{"title":7341,"content":7342,"codePy":7343,"codeCpp":7344},"1. 选择排序即贪心","每轮从未排序区选最小放到前面，正是\"每步选当前最优\"。","for i in range(n):\n    m = min(range(i, n), key=lambda k: a[k])\n    a[i], a[m] = a[m], a[i]","\u002F\u002F 每轮选最小，贪心",{"title":7346,"content":7347,"codePy":7348,"codeCpp":7349,"tip":7350},"2. 一般化","很多贪心可描述为\"重复：从可选集合中选一个当前最优的加入解\"。","# 重复选当前最优\nwhile pool:\n    x = best(pool); take(x); pool.remove(x)","\u002F\u002F 当前最优加入解","直接选择是最直观的贪心形式。",[7352],{"title":7353,"prompt":7354,"analysis":7355,"codePy":7356,"codeCpp":7357,"run":4953},"例 1：每轮选最小","选择排序每轮的操作？","从未排序区选最小值放前端。","print(\"每轮选最小\")","\u002F\u002F 选最小",{"choice":7359,"coding":7374},[7360,7365,7370],{"id":38,"stem":7361,"options":7362,"answer":9,"explanation":7364},"选择排序属于？",[6040,7363,5244,2509],"贪心（直接选择）","每轮选最优。",{"id":47,"stem":7366,"options":7367,"answer":9,"explanation":7369},"直接选择型贪心每步？",[1568,7368,3117,5245],"选当前最优加入解","当前最优。",{"id":56,"stem":7371,"options":7372,"answer":9,"explanation":7373},"选择排序复杂度？",[2338,2339,2564,1245],"每轮扫描未排序区。",[7375],{"id":67,"title":7376,"prompt":7377,"inputFormat":1801,"outputFormat":7378,"samples":7379,"starterPy":7382,"starterCpp":7383,"hint":7384,"referencePy":7382,"referenceCpp":7383},"降序选择排序","输入 n（1≤n≤100000）和 n 个整数，用\"每步选最大\"降序输出（即选择排序降序）。","一行降序整数，空格分隔。",[7380],{"input":4280,"output":7381},"5 4 3 1 1","a = list(map(int, input().split()))\nfor i in range(len(a)):\n    m = max(range(i, len(a)), key=lambda k: a[k])\n    a[i], a[m] = a[m], a[i]\nprint(*a)","#include \u003Ciostream>\n#include \u003Calgorithm>\nusing namespace std;\nint a[100005];\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int i=0;i\u003Cn;i++){ int m=i; for(int j=i+1;j\u003Cn;j++) if(a[j]>a[m]) m=j; int t=a[i]; a[i]=a[m]; a[m]=t; } for(int i=0;i\u003Cn;i++) cout\u003C\u003Ca[i]\u003C\u003C\" \"; }","每轮从未排序区选最大值放前端。","直接选择型贪心每步从候选中选当前最优加入解（如选择排序每轮选最小）；是贪心最直观的形式。","11线性结构-A1-1",{"id":6345,"title":6346,"level":6178,"module":7388,"seq":9,"goal":7389,"keywords":7390,"explanation":7394,"examples":7406,"exercises":7413,"summary":7448,"next":7449,"nextTitle":7450},"贪心算法进阶","理解贪心算法的两个支柱：贪心选择性质（局部最优能导向全局最优）与最优子结构；并掌握“交换论证”证明。",[7391,7392,7393],"贪心选择","最优子结构","交换论证",[7395,7400],{"title":7396,"content":7397,"codePy":7398,"codeCpp":7399},"1. 两个性质","最优子结构：问题最优解包含子问题最优解。贪心选择性质：可先做一个局部最优选择，剩下子问题独立于之前选择。","def greedy(items):\n    items.sort(key=lambda x: x.w\u002Fx.v, reverse=True)  # 例：按性价比\n    res=[]\n    for it in items: res.append(it)  # 逐步取局部最优\n    return res","\u002F\u002F 先排序\u002F选局部最优，再解剩余子问题",{"title":7401,"content":7402,"codePy":7403,"codeCpp":7404,"tip":7405},"2. 交换论证","证明：假设某最优解的第一步不是贪心选的，把它换成贪心选的不会变差，从而贪心解也是最优。反例：找零用 1\u002F3\u002F4 凑 6，贪心选 4 得 4+1+1（3 张）非最优（3+3 仅 2 张）。","print(\"能贪心的前提：局部最优可交换为全局最优\")","\u002F\u002F 找零 1\u002F3\u002F4 凑 6：贪心失败 => 需 DP","tip: 不确定能否贪心时，用 DP 兜底验证。",[7407],{"title":7408,"prompt":7409,"analysis":7410,"codePy":7411,"codeCpp":7412,"run":5046},"例 1：能否贪心","硬币 1\u002F3\u002F4 凑 6，贪心给 4 再 1+1 是最优吗？","不是，3+3 只用 2 张。","print(\"否，3+3 更优\")","\u002F\u002F 3+3",{"choice":7414,"coding":7435},[7415,7421,7427],{"id":38,"stem":7416,"options":7417,"answer":9,"explanation":7420},"贪心正确需要哪个性质？",[7418,7419,1405,3117],"随机性","贪心选择+最优子结构","二者兼具才可贪心。",{"id":47,"stem":7422,"options":7423,"answer":9,"explanation":7426},"证明贪心常用的论证是？",[7424,7393,7425,1542],"归纳","反证即可","把最优解第一步换成贪心选。",{"id":56,"stem":7428,"options":7429,"answer":9,"explanation":7434},"硬币 1\u002F3\u002F4 凑 6，贪心为什么失败？",[7430,7431,7432,7433],"复杂度高","局部最优不导向全局最优","n 小","无排序","4+1+1 不如 3+3。",[7436],{"id":67,"title":7437,"prompt":7438,"inputFormat":7439,"outputFormat":7440,"samples":7441,"starterPy":7444,"starterCpp":7445,"hint":7446,"referencePy":7447,"referenceCpp":7445},"活动安排（贪心）","给定 n 个区间 [l,r]，选出最多的互不重叠区间。","第一行 n；接下来 n 行每行 l r。","一行整数，最多区间数。",[7442],{"input":7443,"output":759},"3\n1 2\n2 3\n1 3","n=int(input())\nseg=[tuple(map(int,input().split())) for _ in range(n)]\n# 按右端点排序，贪心选\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cpair\u003Cint,int>> s(n); for(int i=0;i\u003Cn;i++) cin>>s[i].first>>s[i].second; sort(s.begin(),s.end(),[](auto&a,auto&b){return a.second\u003Cb.second;}); int cnt=0,end=-1; for(auto&p:s){ if(p.first>=end){ cnt++; end=p.second; } } cout\u003C\u003Ccnt; }","按结束时间升序，能选就选。","n=int(input())\nseg=[tuple(map(int,input().split())) for _ in range(n)]\nseg.sort(key=lambda x:x[1])\nend=-1; cnt=0\nfor l,r in seg:\n    if l>=end: cnt+=1; end=r\nprint(cnt)","贪心成立需贪心选择性质与最优子结构；用“按结束时间排序+能选就选”等活动安排是经典模板，拿不准时用 DP 验证。","09-A2-2","区间调度（最多不重叠区间）",{"id":7449,"title":7450,"level":6178,"module":7388,"seq":63,"goal":7452,"keywords":7453,"explanation":7457,"examples":7468,"exercises":7472,"summary":7502,"next":7503,"nextTitle":7504},"熟练区间调度：按结束时间升序，依次选取与已选不冲突的区间，得到最多互不重叠区间。",[7454,7455,7456],"区间调度","最早结束","不重叠",[7458,7463],{"title":7459,"content":7460,"codePy":7461,"codeCpp":7462},"1. 为什么按结束时间","越早结束，留给后面的时间越多，越可能选更多。这是贪心选择性质的直接体现。","seg.sort(key=lambda x: x[1])\nend=-1; cnt=0\nfor l, r in seg:\n    if l >= end:\n        cnt += 1; end = r","\u002F\u002F sort by r; if l>=end: take it, end=r",{"title":7464,"content":7465,"codePy":7466,"codeCpp":7467},"2. 变体","若要求覆盖整个 [0,T]，则按开始时间排序、在可选项中选结束最晚的；若求最少点打掉所有区间，则按结束时间、在交集内放点。","print(\"按目标不同换排序键\")","\u002F\u002F 覆盖\u002F选点属于不同贪心策略",[7469],{"title":7299,"prompt":7470,"analysis":7471,"codePy":6253,"codeCpp":5851,"run":759},"[1,2],[2,3],[1,3] 最多选几个？","选 [1,2] 与 [2,3] 共 2 个。",{"choice":7473,"coding":7491},[7474,7479,7483],{"id":38,"stem":7475,"options":7476,"answer":9,"explanation":7478},"区间调度按什么排序最优？",[7307,7477,6269,1568],"结束时间","最早结束留更多空间。",{"id":47,"stem":7480,"options":7481,"answer":9,"explanation":7482},"[1,4],[2,3],[3,5] 最多选几个？",[892,759,890,891],"[2,3] 与 [1,4]\u002F[3,5] 冲突，只能选 2 个如 [2,3]+[3,5] 不可(3=3 相接可)=2。",{"id":56,"stem":7484,"options":7485,"answer":607,"explanation":7490},"冲突判定用？",[7486,7487,7488,7489],"l>=end","l>end","r\u003Cend","l\u003Cr","端点相接不算重叠。",[7492],{"id":67,"title":7493,"prompt":7494,"inputFormat":7495,"outputFormat":6275,"samples":7496,"starterPy":7499,"starterCpp":7500,"hint":7501,"referencePy":7447,"referenceCpp":7500},"最多区间","n 个区间，求最多互不重叠区间数（端点相接不冲突）。","第一行 n；接下来 n 行 l r。",[7497],{"input":7498,"output":890},"4\n1 2\n2 3\n3 4\n1 3","n=int(input())\nseg=[tuple(map(int,input().split())) for _ in range(n)]\nseg.sort(key=lambda x:x[1])\nend=-1; cnt=0\n# 贪心\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cpair\u003Cint,int>> s(n); for(int i=0;i\u003Cn;i++) cin>>s[i].first>>s[i].second; sort(s.begin(),s.end(),[](auto&a,auto&b){return a.second\u003Cb.second;}); int cnt=0,end=-1; for(auto&p:s) if(p.first>=end){cnt++; end=p.second;} cout\u003C\u003Ccnt; }","按结束时间排序，l>=end 则选。","区间调度按结束时间升序、能选就选，可得到最多互不重叠区间；端点相接不算冲突。不同目标（覆盖\u002F选点）需换对应贪心策略。","09-A2-3","区间选点与区间覆盖",{"id":7503,"title":7504,"level":6178,"module":7388,"seq":703,"goal":7506,"keywords":7507,"explanation":7511,"examples":7522,"exercises":7527,"summary":7555,"next":7556,"nextTitle":7557},"掌握两类区间贪心：用最少点“刺穿”所有区间（按结束时间，在交集末放点）；用最少区间覆盖 [0,T]（按开始时间，选能接上的结束最晚者）。",[7508,7509,7510],"区间选点","区间覆盖","最少",[7512,7517],{"title":7513,"content":7514,"codePy":7515,"codeCpp":7516},"1. 最少选点","按结束时间排序，维护当前点；若某区间未被当前点覆盖，就在它的右端点放一个新点。","seg.sort(key=lambda x: x[1])\npts=[]; cur=-1\nfor l, r in seg:\n    if l > cur:\n        pts.append(r); cur = r","\u002F\u002F 按 r 排序；l>cur 才放新点于 r",{"title":7518,"content":7519,"codePy":7520,"codeCpp":7521},"2. 最少覆盖","按开始时间排序，在能覆盖当前位置的区间里选结束最晚的，跳到该结束处继续。","print(\"覆盖 [0,T]：选开始\u003C=pos 中 r 最大者\")","\u002F\u002F 线段覆盖经典贪心",[7523],{"title":7524,"prompt":7525,"analysis":7526,"codePy":6253,"codeCpp":5851,"run":759},"例 1：最少选点","[1,3],[2,4],[4,5] 最少几个点？","放点于 3 覆盖前两，点 5 覆盖第三，共 2 个。",{"choice":7528,"coding":7544},[7529,7534,7540],{"id":38,"stem":7530,"options":7531,"answer":9,"explanation":7533},"最少区间选点按什么排序？",[7532,5749,6269,5709],"开始","按结束时间。",{"id":47,"stem":7535,"options":7536,"answer":9,"explanation":7539},"选点放在区间的？",[7537,7538,5709,3952],"左端","右端","右端覆盖最多剩余。",{"id":56,"stem":7541,"options":7542,"answer":9,"explanation":7543},"最少覆盖 [0,T] 排序键？",[5749,7532,6269,1568],"按开始时间，选接得上且结束最晚。",[7545],{"id":67,"title":7546,"prompt":7547,"inputFormat":7495,"outputFormat":6275,"samples":7548,"starterPy":7551,"starterCpp":7552,"hint":7553,"referencePy":7554,"referenceCpp":7552},"最少选点","n 个闭区间，求最少需多少个点使每个区间至少含一个点。",[7549],{"input":7550,"output":759},"3\n1 3\n2 4\n4 5","n=int(input())\nseg=[tuple(map(int,input().split())) for _ in range(n)]\nseg.sort(key=lambda x:x[1])\npts=0; cur=-1\n# 贪心放点\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cpair\u003Cint,int>> s(n); for(int i=0;i\u003Cn;i++) cin>>s[i].first>>s[i].second; sort(s.begin(),s.end(),[](auto&a,auto&b){return a.second\u003Cb.second;}); int cnt=0,cur=-1e9; for(auto&p:s){ if(p.first>cur){ cnt++; cur=p.second; } } cout\u003C\u003Ccnt; }","按结束时间排序，l>cur 时放点于 r。","n=int(input())\nseg=[tuple(map(int,input().split())) for _ in range(n)]\nseg.sort(key=lambda x:x[1])\npts=0; cur=-1\nfor l,r in seg:\n    if l>cur: pts+=1; cur=r\nprint(pts)","最少选点：按结束时间排序，区间未被当前点覆盖就在其右端点放新点；最少覆盖：按开始时间排序，在能接上的区间中选结束最晚者。","09-A2-4","哈夫曼思想与最优合并",{"id":7556,"title":7557,"level":6178,"module":7388,"seq":770,"goal":7559,"keywords":7560,"explanation":7565,"examples":7576,"exercises":7583,"summary":7615,"next":7616,"nextTitle":7617},"理解哈夫曼贪心：每次合并两个最小权值，使总代价最小；用于最优前缀码与合并果子等问题。",[7561,7562,7563,7564],"哈夫曼","最小堆","合并代价","前缀码",[7566,7571],{"title":7567,"content":7568,"codePy":7569,"codeCpp":7570},"1. 合并果子","n 堆果子，每次合并两堆，代价为两堆重量和；要使总代价最小，应每次合并当前最轻的两堆（用最小堆）。","import heapq\ndef cost(a):\n    h=list(a); heapq.heapify(h)\n    s=0\n    while len(h)>1:\n        x=heapq.heappop(h); y=heapq.heappop(h)\n        s+=x+y; heapq.heappush(h,x+y)\n    return s","\u002F\u002F 小根堆，反复取最小两个合并",{"title":7572,"content":7573,"codePy":7574,"codeCpp":7575},"2. 哈夫曼编码","出现频率越低编码越长、越高越短，使带权路径长度最小；本质同“合并最小两个”。","print(\"频率小的码长，总比特最小\")","\u002F\u002F 前缀码：任一码不是另一码前缀",[7577],{"title":7578,"prompt":7579,"analysis":7580,"codePy":7581,"codeCpp":7582,"run":291},"例 1：合并代价","果子重量 [1,2,9]，最小总合并代价？","1+2=3，再 3+9=12，总 3+12=15。","print(15)","\u002F\u002F 15",{"choice":7584,"coding":7605},[7585,7591,7597],{"id":38,"stem":7586,"options":7587,"answer":9,"explanation":7590},"哈夫曼每次合并哪两个？",[5984,7588,1568,7589],"最小两个","首尾","最小两个代价最低。",{"id":47,"stem":7592,"options":7593,"answer":9,"explanation":7596},"[1,2,9] 总代价？",[884,291,7594,7595],"11","21","(1+2)=3, (3+9)=12, 共15。",{"id":56,"stem":7598,"options":7599,"answer":9,"explanation":7604},"哈夫曼用哪种堆？",[7600,7601,7602,7603],"大根堆","小根堆","不用堆","二叉堆","取最小用最小堆。",[7606],{"id":67,"title":7563,"prompt":7607,"inputFormat":1801,"outputFormat":6275,"samples":7608,"starterPy":7611,"starterCpp":7612,"hint":7613,"referencePy":7614,"referenceCpp":7612},"n 个正整数，每次合并两个并把和加进总代价，求最小总代价。",[7609],{"input":7610,"output":291},"3\n1 2 9","import heapq\nn=int(input()); a=list(map(int,input().split()))\nheapq.heapify(a)\n# 合并\n","#include \u003Ciostream>\n#include \u003Cqueue>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; priority_queue\u003Cint,vector\u003Cint>,greater\u003Cint>> pq; for(int i=0,x;i\u003Cn;i++){cin>>x; pq.push(x);} long long s=0; while(pq.size()>1){ int a=pq.top(); pq.pop(); int b=pq.top(); pq.pop(); s+=a+b; pq.push(a+b);} cout\u003C\u003Cs; }","最小堆，取两个合并、和入总代价。","import heapq\nn=int(input()); a=list(map(int,input().split()))\nheapq.heapify(a)\ns=0\nwhile len(a)>1:\n    x=heapq.heappop(a); y=heapq.heappop(a); s+=x+y; heapq.heappush(a,x+y)\nprint(s)","哈夫曼贪心每次合并当前最轻的两堆，使总合并代价最小；对应最优前缀码（频率高的码短），用最小堆实现 O(n log n)。","09-A2-5","贪心与排序结合（比值\u002F字典序）",{"id":7616,"title":7617,"level":6178,"module":7388,"seq":838,"goal":7619,"keywords":7620,"explanation":7625,"examples":7637,"exercises":7645,"summary":7678,"next":7679,"nextTitle":7680},"掌握“先排序再贪心”的模式：按性价比、按比值、按字典序等排序后顺序选取，常见于任务安排与拼接问题。",[7621,7622,7623,7624],"排序贪心","性价比","拼接","字典序",[7626,7631],{"title":7627,"content":7628,"codePy":7629,"codeCpp":7630},"1. 性价比排序","如“用有限体力搬最多分数物品”，按 分数\u002F重量 降序取。本质是给物品定一个贪心序。","items.sort(key=lambda x: x[1]\u002Fx[0], reverse=True)\nres=0\nfor w, v in items:\n    res += v  # 简化：按序取","\u002F\u002F 先定序：value\u002Fweight 降序",{"title":7632,"content":7633,"codePy":7634,"codeCpp":7635,"tip":7636},"2. 字典序拼接","给定数字字符串，拼成最大数：比较 a+b 与 b+a，按 a+b>b+a 排序（如 3 与 30：330>303 故 3 在前）。","def largest(nums):\n    s=list(map(str,nums))\n    s.sort(key=lambda x: x*10, reverse=True)  # 简化示意\n    return ''.join(s)","\u002F\u002F 比较 x+y 与 y+x 决定先后","tip: 拼接类问题用“比较函数”而非直接按数值。",[7638],{"title":7639,"prompt":7640,"analysis":7641,"codePy":7642,"codeCpp":7643,"run":7644},"例 1：拼最大数","数字 3 与 30 拼最大？","330 > 303，故 3 在前得 330。","print(330)","\u002F\u002F 330","330",{"choice":7646,"coding":7666},[7647,7654,7660],{"id":38,"stem":7648,"options":7649,"answer":9,"explanation":7653},"搬分数按什么排序？",[7650,7651,7652,1568],"重量","分数\u002F重量","分数","性价比降序。",{"id":47,"stem":7655,"options":7656,"answer":9,"explanation":7659},"拼最大数比较的是？",[7657,7658,6269,5985],"a 与 b","a+b 与 b+a","拼接后字典序。",{"id":56,"stem":7661,"options":7662,"answer":9,"explanation":7665},"3 与 30 哪个在前拼更大？",[7663,890,2661,7664],"30","看长度","330>303。",[7667],{"id":67,"title":7668,"prompt":7669,"inputFormat":1801,"outputFormat":2004,"samples":7670,"starterPy":7674,"starterCpp":7675,"hint":7676,"referencePy":7677,"referenceCpp":7675},"拼最大数","给定 n 个非负整数，把它们拼接成最大的整数（可很大，输出字符串）。",[7671],{"input":7672,"output":7673},"3\n3 30 34","343303","n=int(input()); a=list(map(str,input().split()))\na.sort(key=lambda x: x*10, reverse=True)\nprint(''.join(a))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cstring> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; sort(a.begin(),a.end(),[](string& x,string& y){return x+y>y+x;}); for(auto&s:a) cout\u003C\u003Cs; }","用 x+y>y+x 作为比较规则。","n=int(input()); a=list(map(str,input().split()))\na.sort(key=lambda x: x*2, reverse=True)\nprint(''.join(a))","“先排序后贪心”是常见套路：按性价比、按比值或按拼接字典序（比较 x+y 与 y+x）定序，再顺序选取。","09-A2-6","多机调度与任务分配",{"id":7679,"title":7680,"level":6178,"module":7388,"seq":936,"goal":7682,"keywords":7683,"explanation":7688,"examples":7700,"exercises":7707,"summary":7738,"next":7739,"nextTitle":7740},"掌握两类调度贪心：任务有截止时间求最多完成数（按截止时间贪心+可反悔）；负载均衡用“最长处理时间优先”近似。",[7684,7685,7686,7687],"调度","截止时间","可反悔","LPT",[7689,7694],{"title":7690,"content":7691,"codePy":7692,"codeCpp":7693},"1. 带截止时间","每个任务需 1 单位时间、有截止 d；按 d 升序，能完成就做，否则若当前任务比已做的最长还短可替换（反悔）。","print(\"按截止排序；冲突时替换已选中最长者\")","\u002F\u002F 用堆维护已选任务时长，可反悔",{"title":7695,"content":7696,"codePy":7697,"codeCpp":7698,"tip":7699},"2. LPT 负载均衡","m 台机器分配任务使最晚完成时间尽量小：把任务按耗时降序，依次放当前负载最小的机器（近似最优）。","def lpt(tasks, m):\n    load=[0]*m\n    for t in sorted(tasks, reverse=True):\n        i=load.index(min(load)); load[i]+=t\n    return max(load)","\u002F\u002F 最长任务优先，放最空机器","tip: 精确最优是 NP-hard，LPT 是良好近似。",[7701],{"title":7702,"prompt":7703,"analysis":7704,"codePy":7705,"codeCpp":7706,"run":290},"例 1：LPT","任务 [5,3,2]，2 台机器，LPT 后最晚完成？","5→机1，3→机2，2→机2(现5)，最晚 5。","print(5)","\u002F\u002F 5",{"choice":7708,"coding":7726},[7709,7714,7721],{"id":38,"stem":7710,"options":7711,"answer":9,"explanation":7713},"带截止调度按什么排序？",[7712,7685,1568,7532],"时长","按截止升序。",{"id":47,"stem":7715,"options":7716,"answer":9,"explanation":7720},"LPT 指？",[7717,7718,3000,7719],"随机分配","最长任务优先放最空机","最短优先","均衡负载。",{"id":56,"stem":7722,"options":7723,"answer":9,"explanation":7725},"可反悔调度用什么维护已选？",[6565,7724,4617,1434],"最大堆","用堆取最长已选任务。",[7727],{"id":67,"title":7728,"prompt":7729,"inputFormat":7730,"outputFormat":6275,"samples":7731,"starterPy":7734,"starterCpp":7735,"hint":7736,"referencePy":7737,"referenceCpp":7735},"最长任务优先","n 个任务时长，m 台相同机器，按 LPT 分配，输出最晚完成时间。","第一行 n m；第二行 n 个整数。",[7732],{"input":7733,"output":290},"3 2\n5 3 2","n,m=map(int,input().split()); a=list(map(int,input().split()))\nload=[0]*m\nfor t in sorted(a,reverse=True):\n    i=load.index(min(load)); load[i]+=t\nprint(max(load))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; sort(a.rbegin(),a.rend()); vector\u003Cint> load(m,0); for(int t:a){ int bi=0; for(int i=1;i\u003Cm;i++) if(load[i]\u003Cload[bi]) bi=i; load[bi]+=t; } int ans=0; for(int x:load) ans=max(ans,x); cout\u003C\u003Cans; }","任务降序，放当前负载最小的机器。","n,m=map(int,input().split()); a=list(map(int,input().split()))\nload=[0]*m\nfor t in sorted(a,reverse=True):\n    i=load.index(min(load)); load[i]+=t\nprint(max(load))","带截止调度按截止时间贪心、冲突时可反悔替换已选最长任务；负载均衡用 LPT（最长任务优先放最空机器）获得良好近似。","10DFS-A1-1","DFS 基本框架",{"id":7742,"title":7743,"level":6178,"module":7744,"seq":9,"goal":7745,"keywords":7746,"explanation":7750,"examples":7762,"exercises":7767,"summary":7799,"next":7800,"nextTitle":7801},"10BFS-A1-1","BFS 基本框架","广度优先搜索（BFS）","掌握广度优先搜索：用队列逐层扩展，先访问离起点近的节点，适合求最短路与层序遍历。",[7747,4617,7748,7749],"BFS","逐层","最短路",[7751,7756],{"title":7752,"content":7753,"codePy":7754,"codeCpp":7755},"1. 队列框架","入队起点并标记；不断出队、把未访问邻居入队并标记，保证按距离分层扩展。","from collections import deque\ndef bfs(s):\n    q=deque([s]); vis[s]=True\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if not vis[v]:\n                vis[v]=True; q.append(v)","\u002F\u002F queue\u003Cint> q; q.push(s); while(!q.empty()){int u=q.front();q.pop(); ...}",{"title":7757,"content":7758,"codePy":7759,"codeCpp":7760,"tip":7761},"2. 与 DFS 的区别","BFS 用队列（FIFO）逐层，首次到达即为最短距离；DFS 用栈\u002F递归深入。","print(\"BFS 首达=最短路\")","\u002F\u002F 边权为 1 时 BFS 即最短路","tip: 求最少步数优先 BFS。",[7763],{"title":7764,"prompt":7765,"analysis":7766,"codePy":6253,"codeCpp":5851,"run":759},"例 1：层数","链 1-2-3，从 1 BFS，3 在第几层？","距离 2，第 3 层（含起点第1层）。",{"choice":7768,"coding":7787},[7769,7774,7781],{"id":38,"stem":7770,"options":7771,"answer":9,"explanation":7773},"BFS 用哪种结构？",[6565,4617,7772,1434],"优先队列","FIFO 逐层。",{"id":47,"stem":7775,"options":7776,"answer":9,"explanation":7780},"边权为 1 时 BFS 求的是？",[7777,7749,7778,7779],"最长路","拓扑","连通块","首达即最短。",{"id":56,"stem":7782,"options":7783,"answer":9,"explanation":7786},"BFS 适合？",[7784,7785,3117,4166],"枚举排列","最少步数\u002F层序","逐层扩散。",[7788],{"id":67,"title":7789,"prompt":7790,"inputFormat":7791,"outputFormat":6275,"samples":7792,"starterPy":7795,"starterCpp":7796,"hint":7797,"referencePy":7798,"referenceCpp":7796},"BFS 可达","n 点 m 边无向图，从 1 出发 BFS 能到达多少点？","第一行 n m；接下来 m 行 u v。",[7793],{"input":7794,"output":890},"3 2\n1 2\n2 3","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1); q=deque([1]); vis[1]=True; cnt=0\nwhile q:\n    u=q.popleft(); cnt+=1\n    for v in g[u]:\n        if not vis[v]: vis[v]=True; q.append(v)\nprint(cnt)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; bool vis[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} queue\u003Cint> q; q.push(1); vis[1]=1; int cnt=0; while(!q.empty()){ int u=q.front(); q.pop(); cnt++; for(int v:g[u]) if(!vis[v]){ vis[v]=1; q.push(v);} } cout\u003C\u003Ccnt; }","队列逐层扩展并计数。","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1); q=deque([1]); vis[1]=True; cnt=0\nwhile q:\n    u=q.popleft(); cnt+=1\n    for v in g[u]:\n        if not vis[v]: vis[v]=True; q.append(v)\nprint(cnt)","BFS 用队列逐层扩展，首次到达节点即为最短距离；适合求最少步数、层序遍历与连通块，边权为 1 时就是最短路。","10BFS-A1-2","BFS 与最短路（边权=1）",{"id":7800,"title":7801,"level":6178,"module":7744,"seq":63,"goal":7803,"keywords":7804,"explanation":7808,"examples":7819,"exercises":7824,"summary":7854,"next":7855,"nextTitle":7856},"用 BFS 求无权图（边权均为 1）从起点到各点的最短距离，首次访问即定距，避免重复入队。",[7749,7805,7806,7807],"dist","首次访问","边权1",[7809,7814],{"title":7810,"content":7811,"codePy":7812,"codeCpp":7813},"1. 距离数组","dist 初始化 -1（不可达），起点 dist=0；出队时更新邻居 dist=d+1 并入队。","from collections import deque\ndef bfs(s):\n    dist=[-1]*(n+1); dist[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if dist[v]==-1:\n                dist[v]=dist[u]+1; q.append(v)\n    return dist","\u002F\u002F dist 初 -1；更新未访问邻居",{"title":7815,"content":7816,"codePy":7817,"codeCpp":7818},"2. 正确性","队列按距离分层，先处理距离 d 的所有点再处理 d+1，故首次访问即最短。","print(\"分层保证首次=最短\")","\u002F\u002F 已访问不再入队",[7820],{"title":7821,"prompt":7822,"analysis":7823,"codePy":6544,"codeCpp":6545,"run":890},"例 1：距离","链 1-2-3-4，1 到 4 距离？","3 条边，距离 3。",{"choice":7825,"coding":7843},[7826,7831,7838],{"id":38,"stem":7827,"options":7828,"answer":9,"explanation":7830},"BFS 距离初值？",[891,7829,892,3054],"-1\u002FINF","未达标记不可达。",{"id":47,"stem":7832,"options":7833,"answer":9,"explanation":7837},"邻居距离为？",[7834,7835,7836,1568],"d","d+1","d-1","当前距离加一。",{"id":56,"stem":7839,"options":7840,"answer":9,"explanation":7842},"为何 BFS 距离最短？",[1568,7841,4854,3117],"分层扩展","逐层保证首次即最短。",[7844],{"id":67,"title":7845,"prompt":7846,"inputFormat":7791,"outputFormat":6275,"samples":7847,"starterPy":7850,"starterCpp":7851,"hint":7852,"referencePy":7853,"referenceCpp":7851},"最短距离","n 点 m 边无向图，求 1 到 n 的最短距离（边权 1），不可达输出 -1。",[7848],{"input":7849,"output":890},"4 3\n1 2\n2 3\n3 4","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndist=[-1]*(n+1); dist[1]=0; q=deque([1])\nwhile q:\n    u=q.popleft()\n    for v in g[u]:\n        if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\nprint(dist[n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; int dist[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} for(int i=1;i\u003C=n;i++) dist[i]=-1; dist[1]=0; queue\u003Cint> q; q.push(1); while(!q.empty()){ int u=q.front(); q.pop(); for(int v:g[u]) if(dist[v]==-1){ dist[v]=dist[u]+1; q.push(v);} } cout\u003C\u003Cdist[n]; }","dist 初 -1，邻居 dist[u]+1。","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndist=[-1]*(n+1); dist[1]=0; q=deque([1])\nwhile q:\n    u=q.popleft()\n    for v in g[u]:\n        if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\nprint(dist[n])","无权图最短路用 BFS：dist 初 -1，起点 0，邻居 dist[u]+1 并入队；分层扩展保证首次访问即最短距离。","10BFS-A1-3","BFS 与多源最短路",{"id":7855,"title":7856,"level":6178,"module":7744,"seq":703,"goal":7858,"keywords":7859,"explanation":7863,"examples":7873,"exercises":7880,"summary":7917,"next":7918,"nextTitle":7919},"掌握多源 BFS：把所有起点同时入队（距离 0），一次 BFS 即求每个点到最近起点的距离。",[7860,7861,7862],"多源 BFS","同时入队","最近源",[7864,7869],{"title":7865,"content":7866,"codePy":7867,"codeCpp":7868},"1. 同时入队","把所有“源点”初始距离设为 0 并一起入队，之后与普通 BFS 完全相同。","q=deque(srcs)\nfor s in srcs: dist[s]=0\nwhile q:\n    u=q.popleft()\n    for v in g[u]:\n        if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)","\u002F\u002F 多个起点同时作为第 0 层",{"title":4421,"content":7870,"codePy":7871,"codeCpp":7872},"如“每个格子到最近消防站的距离”“地图最近水源”——本质是单源 BFS 的起点集合化。","print(\"多源 = 把源点当同一层\")","\u002F\u002F 等价于建虚拟超级源连所有源点",[7874],{"title":7875,"prompt":7876,"analysis":7877,"codePy":7878,"codeCpp":7879,"run":892},"例 1：多源","点 1 与 4 为源，链 1-2-3-4，点 2 最近源距离？","到 1 为 1。","print(1)","\u002F\u002F 1",{"choice":7881,"coding":7903},[7882,7890,7896],{"id":38,"stem":7883,"options":7884,"answer":9,"explanation":7889},"多源 BFS 起点如何入队？",[7885,7886,7887,7888],"逐个","同时（距离0）","按序","不排队","同层第 0 层。",{"id":47,"stem":7891,"options":7892,"answer":9,"explanation":7895},"多源 BFS 求的是？",[7893,7894,7778,7779],"最远源","最近源距离","到最近起点距离。",{"id":56,"stem":7897,"options":7898,"answer":607,"explanation":7902},"等价于建什么？",[7899,7900,7901,6210],"虚拟超级源","新图","树","超级源连所有起点。",[7904],{"id":67,"title":7905,"prompt":7906,"inputFormat":7907,"outputFormat":7908,"samples":7909,"starterPy":7913,"starterCpp":7914,"hint":7915,"referencePy":7916,"referenceCpp":7914},"多源距离","n 点 m 边图，给定 k 个源点，求每个点到最近源点的距离（不可达 -1）。","第一行 n m k；接下来 m 行 u v；下一行 k 个源点。","一行 n 个整数（点 1..n 的距离）。",[7910],{"input":7911,"output":7912},"4 3 2\n1 2\n2 3\n3 4\n1 4","0 1 1 0","from collections import deque\nn,m,k=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nsrc=list(map(int,input().split())); dist=[-1]*(n+1); q=deque()\nfor s in src: dist[s]=0; q.append(s)\nwhile q:\n    u=q.popleft()\n    for v in g[u]:\n        if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\nprint(*dist[1:])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; int dist[100005];\nint main(){ int n,m,k; cin>>n>>m>>k; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} for(int i=1;i\u003C=n;i++) dist[i]=-1; queue\u003Cint> q; for(int i=0,s;i\u003Ck;i++){cin>>s; dist[s]=0; q.push(s);} while(!q.empty()){ int u=q.front(); q.pop(); for(int v:g[u]) if(dist[v]==-1){ dist[v]=dist[u]+1; q.push(v);} } for(int i=1;i\u003C=n;i++) cout\u003C\u003Cdist[i]\u003C\u003C' '; }","所有源点 dist=0 同时入队。","from collections import deque\nn,m,k=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nsrc=list(map(int,input().split())); dist=[-1]*(n+1); q=deque()\nfor s in src: dist[s]=0; q.append(s)\nwhile q:\n    u=q.popleft()\n    for v in g[u]:\n        if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\nprint(*dist[1:])","多源 BFS 把所有源点以距离 0 同时入队，一次遍历即得到每个点到最近源的距离，等价于引入虚拟超级源连接所有源点。","10BFS-A1-4","BFS 与 Flood Fill \u002F 连通块",{"id":7918,"title":7919,"level":6178,"module":7744,"seq":770,"goal":7921,"keywords":7922,"explanation":7925,"examples":7937,"exercises":7944,"summary":7979,"next":7980,"nextTitle":7981},"在网格上用 BFS（Flood Fill）求连通块大小、数量与最远格，理解“四连通\u002F八连通”的邻居扩展。",[7923,3717,7779,7924],"Flood Fill","连通",[7926,7931],{"title":7927,"content":7928,"codePy":7929,"codeCpp":7930},"1. 网格 BFS","从某格出发，向上下左右（四连通）未访问且与条件相符的格扩展，计访问数即块大小。","from collections import deque\ndef fill(sr,sc):\n    q=deque([(sr,sc)]); vis[sr][sc]=True; cnt=1\n    while q:\n        r,c=q.popleft()\n        for dr,dc in ((1,0),(-1,0),(0,1),(0,-1)):\n            nr,nc=r+dr,c+dc\n            if 0\u003C=nr\u003Ch and 0\u003C=nc\u003Cw and not vis[nr][nc] and ok(nr,nc):\n                vis[nr][nc]=True; q.append((nr,nc)); cnt+=1\n    return cnt","\u002F\u002F 四方向扩展；边界判断",{"title":7932,"content":7933,"codePy":7934,"codeCpp":7935,"tip":7936},"2. 计数块","扫描全网格，遇未访问的“陆地”就 BFS 填色并计数。","print(\"每遇新块启动一次 BFS\")","\u002F\u002F 八连通再加四对角","tip: 网格题常用 BFS\u002FDFS Flood Fill。",[7938],{"title":7939,"prompt":7940,"analysis":7941,"codePy":7942,"codeCpp":7943,"run":2707},"例 1：块大小","3x3 全为陆地（#），一块大小？","9 格一块。","print(9)","\u002F\u002F 9",{"choice":7945,"coding":7967},[7946,7954,7960],{"id":38,"stem":7947,"options":7948,"answer":607,"explanation":7953},"四连通邻居有？",[7949,7950,7951,7952],"4 个","8 个","2 个","6 个","上下左右。",{"id":47,"stem":7955,"options":7956,"answer":9,"explanation":7959},"Flood Fill 用于？",[3117,7957,7958,6040],"网格连通块","最短路权","填充连通区域。",{"id":56,"stem":7961,"options":7962,"answer":9,"explanation":7966},"网格 BFS 必须判断？",[7963,7964,6269,7965],"颜色","边界越界","层数","防止越界。",[7968],{"id":67,"title":7969,"prompt":7970,"inputFormat":7971,"outputFormat":6275,"samples":7972,"starterPy":7975,"starterCpp":7976,"hint":7977,"referencePy":7978,"referenceCpp":7976},"岛屿数量","h 行 w 列网格，# 为陆地 . 为水，求岛屿（四连通）个数。","第一行 h w；接下来 h 行字符串。",[7973],{"input":7974,"output":892},"3 3\n###\n#.#\n###","from collections import deque\nh,w=map(int,input().split()); g=[input() for _ in range(h)]; vis=[[False]*w for _ in range(h)]\ndef bfs(r,c):\n    q=deque([(r,c)]); vis[r][c]=True\n    while q:\n        r,c=q.popleft()\n        for dr,dc in ((1,0),(-1,0),(0,1),(0,-1)):\n            nr,nc=r+dr,c+dc\n            if 0\u003C=nr\u003Ch and 0\u003C=nc\u003Cw and not vis[nr][nc] and g[nr][nc]==\"#\":\n                vis[nr][nc]=True; q.append((nr,nc))\nblk=0\nfor i in range(h):\n    for j in range(w):\n        if g[i][j]==\"#\" and not vis[i][j]: bfs(i,j); blk+=1\nprint(blk)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Cstring>\nusing namespace std;\nint h,w; vector\u003Cstring> g; vector\u003Cvector\u003Cbool>> vis;\nvoid bfs(int r,int c){ queue\u003Cpair\u003Cint,int>> q; q.push({r,c}); vis[r][c]=1; int d[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; while(!q.empty()){ auto [r,c]=q.front(); q.pop(); for(int k=0;k\u003C4;k++){ int nr=r+d[k][0],nc=c+d[k][1]; if(nr>=0&&nr\u003Ch&&nc>=0&&nc\u003Cw&&!vis[nr][nc]&&g[nr][nc]==\"#\"){ vis[nr][nc]=1; q.push({nr,nc}); } } } }\nint main(){ cin>>h>>w; g.resize(h); for(int i=0;i\u003Ch;i++) cin>>g[i]; vis.assign(h,vector\u003Cbool>(w,0)); int blk=0; for(int i=0;i\u003Ch;i++) for(int j=0;j\u003Cw;j++) if(g[i][j]==\"#\"&&!vis[i][j]){ bfs(i,j); blk++; } cout\u003C\u003Cblk; }","每遇新陆地启动 BFS 填色。","from collections import deque\nh,w=map(int,input().split()); g=[input() for _ in range(h)]; vis=[[False]*w for _ in range(h)]\ndef bfs(r,c):\n    q=deque([(r,c)]); vis[r][c]=True\n    while q:\n        r,c=q.popleft()\n        for dr,dc in ((1,0),(-1,0),(0,1),(0,-1)):\n            nr,nc=r+dr,c+dc\n            if 0\u003C=nr\u003Ch and 0\u003C=nc\u003Cw and not vis[nr][nc] and g[nr][nc]==\"#\":\n                vis[nr][nc]=True; q.append((nr,nc))\nblk=0\nfor i in range(h):\n    for j in range(w):\n        if g[i][j]==\"#\" and not vis[i][j]: bfs(i,j); blk+=1\nprint(blk)","网格 Flood Fill 用 BFS 从某格向四连通（或八连通）扩展并标记访问，可求连通块大小与数量；务必做边界判断防止越界。","11线性结构-A2-1","栈与括号匹配 \u002F 表达式求值",{"id":7983,"title":7860,"level":6350,"module":7984,"seq":230,"goal":7985,"keywords":7986,"explanation":7988,"examples":7997,"exercises":8002,"summary":8022,"next":8023,"nextTitle":8024},"10BFS-A2-1","搜索（回溯 \u002F 状态搜索）","掌握多源 BFS：把所有起点同时入队（距离 0），一次遍历即求每个点到最近起点的距离，常用于“最近设施”“地图扩散”。",[7860,7861,7862,7987],"扩散",[7989,7993],{"title":7865,"content":7990,"codePy":7991,"codeCpp":7992},"将所有源点 dist 置 0 并一起入队，之后与普通 BFS 完全相同；等价于建虚拟超级源连所有源点。","from collections import deque\ndef multi_src(sources, n):\n    dist=[-1]*(n+1); q=deque()\n    for s in sources: dist[s]=0; q.append(s)\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\n    return dist","\u002F\u002F 多个起点同属第 0 层",{"title":4421,"content":7994,"codePy":7995,"codeCpp":7996},"如“每个格子到最近商店距离”“病毒从多个点同时扩散的到达时间”，一次 BFS 解决。","print(\"多源=把源点当同一层\")","\u002F\u002F 与单源 BFS 仅起点不同",[7998],{"title":7999,"prompt":8000,"analysis":8001,"codePy":7878,"codeCpp":7879,"run":892},"例 1：多源距离","点 1 与 4 为源，链 1-2-3-4，点 2 最近距离？","到 1 距离 1。",{"choice":8003,"coding":8015},[8004,8007,8011],{"id":38,"stem":7883,"options":8005,"answer":9,"explanation":7889},[7885,8006,7887,7888],"同时(距离0)",{"id":47,"stem":7897,"options":8008,"answer":9,"explanation":8010},[7900,8009,7901,6210],"虚拟超级源连所有源","超级源距离 0。",{"id":56,"stem":7891,"options":8012,"answer":9,"explanation":8014},[7893,7894,7778,8013],"块","到最近起点。",[8016],{"id":67,"title":8017,"prompt":7906,"inputFormat":7907,"outputFormat":8018,"samples":8019,"starterPy":7913,"starterCpp":8021,"hint":7915,"referencePy":7916,"referenceCpp":8021},"多源最短路","一行 n 个整数（点 1..n）。",[8020],{"input":7911,"output":7912},"#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; int dist[100005];\nint main(){ int n,m,k; cin>>n>>m>>k; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} for(int i=1;i\u003C=n;i++) dist[i]=-1; queue\u003Cint> q; for(int i=0,s;i\u003Ck;i++){cin>>s; dist[s]=0; q.push(s);} while(!q.empty()){ int u=q.front(); q.pop(); for(int v:g[u]) if(dist[v]==-1){ dist[v]=dist[u]+1; q.push(v);} } for(int i=1;i\u003C=n;i++) cout\u003C\u003Cdist[i]\u003C\u003C\" \"; }","多源 BFS 把所有源点以距离 0 同时入队，一次遍历得到每个点到最近源的距离；等价于引入虚拟超级源连接所有源点，常用于最近设施\u002F扩散类问题。","10BFS-A2-2","分层 BFS（记录层数）",{"id":8023,"title":8024,"level":6350,"module":7984,"seq":301,"goal":8026,"keywords":8027,"explanation":8032,"examples":8043,"exercises":8047,"summary":8077,"next":8078,"nextTitle":8079},"掌握分层 BFS：用“每层结束标记”或“按层循环”依次处理同一距离的所有节点，便于层序遍历与按层统计。",[8028,8029,8030,8031],"分层 BFS","层序","距离层","按层处理",[8033,8038],{"title":8034,"content":8035,"codePy":8036,"codeCpp":8037},"1. 按层循环","每一层先记录当前队列大小 sz，只处理这 sz 个节点，再把新节点入队；从而区分层级。","from collections import deque\ndef bfs_levels(s):\n    q=deque([s]); vis[s]=True; level=0\n    while q:\n        sz=len(q)\n        for _ in range(sz):\n            u=q.popleft()\n            for v in g[u]:\n                if not vis[v]: vis[v]=True; q.append(v)\n        level+=1  # 处理完一层","\u002F\u002F 先记 sz 再循环，区分层",{"title":4421,"content":8039,"codePy":8040,"codeCpp":8041,"tip":8042},"层序遍历二叉树、求每层的节点数、按层扩散（如病毒的逐轮感染）。","print(\"每层一起处理\")","\u002F\u002F 层序=BFS 天然分层","tip: 分层能在不存 dist 时按层做事。",[8044],{"title":7764,"prompt":8045,"analysis":8046,"codePy":5966,"codeCpp":4490,"run":1663},"链 1-2-3-4，从 1 分层，点 4 在第几层？","距离 3，第 4 层（含起点为第1层）。",{"choice":8048,"coding":8063},[8049,8054,8059],{"id":38,"stem":8050,"options":8051,"answer":9,"explanation":8053},"分层 BFS 如何区分层？",[1568,8052,3117,4166],"先记本层 sz 再循环","固定处理本层节点。",{"id":47,"stem":8055,"options":8056,"answer":9,"explanation":8058},"层序遍历对应？",[7111,8057,7778,6210],"BFS 分层","BFS 天然分层。",{"id":56,"stem":8060,"options":8061,"answer":9,"explanation":8062},"链 1-2-3-4 点4层序层号？",[890,1663,759,892],"含起点为第1层。",[8064],{"id":67,"title":8065,"prompt":8066,"inputFormat":8067,"outputFormat":8068,"samples":8069,"starterPy":8073,"starterCpp":8074,"hint":8075,"referencePy":8076,"referenceCpp":8074},"层序遍历（二叉树）","给定二叉树，按层序（广度优先）输出节点值，每层一行（节点数≤1000）。","第一行 n；第二行 n 个整数，-1 表示空节点，按层序给出（完全二叉树填充）。","每行一层节点值，空格分隔。",[8070],{"input":8071,"output":8072},"7\n1 2 3 4 5 -1 -1","1\n2 3\n4 5","n=int(input()); a=list(map(int,input().split()))\nfrom collections import deque\nq=deque([0])\nlevel=[]\nwhile q:\n    sz=len(q); row=[]\n    for _ in range(sz):\n        i=q.popleft()\n        if a[i]!=-1: row.append(a[i])\n        l=2*i+1; r=2*i+2\n        if l\u003Cn: q.append(l)\n        if r\u003Cn: q.append(r)\n    if row: level.append(row)\nfor row in level: print(*row)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; vector\u003Cint> q; q.push_back(0); while(!q.empty()){ int sz=q.size(); vector\u003Cint> row; for(int k=0;k\u003Csz;k++){ int i=q.front(); q.erase(q.begin()); if(a[i]!=-1) row.push_back(a[i]); int l=2*i+1,r=2*i+2; if(l\u003Cn) q.push_back(l); if(r\u003Cn) q.push_back(r); } if(!row.empty()){ for(int x:row) cout\u003C\u003Cx\u003C\u003C\" \"; cout\u003C\u003Cendl; } } }","先记本层大小再循环。","n=int(input()); a=list(map(int,input().split()))\nfrom collections import deque\nq=deque([0])\nlevel=[]\nwhile q:\n    sz=len(q); row=[]\n    for _ in range(sz):\n        i=q.popleft()\n        if a[i]!=-1: row.append(a[i])\n        l=2*i+1; r=2*i+2\n        if l\u003Cn: q.append(l)\n        if r\u003Cn: q.append(r)\n    if row: level.append(row)\nfor row in level: print(*row)","分层 BFS 在进入每层前记录当前队列大小 sz，只处理这 sz 个节点，从而把同一距离的节点归为同一层；适合层序遍历与按层扩散\u002F统计。","10BFS-A2-3","状态 BFS（最短步数）",{"id":8078,"title":8079,"level":6350,"module":7984,"seq":368,"goal":8081,"keywords":8082,"explanation":8086,"examples":8098,"exercises":8106,"summary":8135,"next":8136,"nextTitle":8137},"把“状态”作为 BFS 节点求最短步数（边权为 1）：状态=(位置,朝向\u002F钥匙\u002F已访问集合)，用 visited 去重保证首次到达即最短。",[8083,8084,8085,4355],"状态 BFS","最短步数","隐式图",[8087,8092],{"title":8088,"content":8089,"codePy":8090,"codeCpp":8091},"1. 状态即节点","如“走迷宫，每步可走四方向或转向”，状态=(x,y,dir)；BFS 在状态图上扩展，visited[x][y][dir] 去重。","from collections import deque\ndef bfs():\n    q=deque([(sx,sy,sd,0)]); vis[sx][sy][sd]=True\n    while q:\n        x,y,d,step=q.popleft()\n        if (x,y,d)==goal: return step\n        for nd in moves(d):\n            nx,ny=go(x,y,nd)\n            if ok(nx,ny) and not vis[nx][ny][nd]:\n                vis[nx][ny][nd]=True; q.append((nx,ny,nd,step+1))","\u002F\u002F 首次到达状态即最短",{"title":8093,"content":8094,"codePy":8095,"codeCpp":8096,"tip":8097},"2. 与 DFS 区别","求“最少步数\u002F最少操作”用 BFS（边权 1）；求“是否存在路径”DFS\u002FBFS 皆可，但 BFS 天然给最短。","print(\"最短步数用 BFS\")","\u002F\u002F 状态图同样适用","tip: 状态空间大时务必 visited 去重。",[8099],{"title":8100,"prompt":8101,"analysis":8102,"codePy":8103,"codeCpp":8104,"run":8105},"例 1：状态规模","10×10 网格、4 朝向，状态总数？","10×10×4=400。","print(400)","\u002F\u002F 400","400",{"choice":8107,"coding":8122},[8108,8113,8118],{"id":38,"stem":8109,"options":8110,"answer":9,"explanation":8112},"求最少步数为何用 BFS？",[1568,8111,1405,3117],"边权1首达即最短","分层保证最短。",{"id":47,"stem":8114,"options":8115,"answer":9,"explanation":8117},"状态 BFS 必须？",[3117,8116,6565,4166],"visited 去重","避免重复\u002F环。",{"id":56,"stem":8119,"options":8120,"answer":9,"explanation":8121},"10×10×4 状态数？",[2828,8105,816,2828],"100×4=400。",[8123],{"id":67,"title":8124,"prompt":8125,"inputFormat":8126,"outputFormat":8127,"samples":8128,"starterPy":8131,"starterCpp":8132,"hint":8133,"referencePy":8134,"referenceCpp":8132},"最少操作到目标","3×3 滑块拼图（0 表示空格），每次把与 0 相邻的数字滑入空格。给定初始与目标排列，求最少滑动步数（保证有解，3×3）。","两行各 9 个整数（0..8），分别为初始与目标。","一行整数，不可达输出 -1。",[8129],{"input":8130,"output":892},"1 2 3 4 5 6 7 8 0\n1 2 3 4 5 6 7 0 8","from collections import deque\nstart=tuple(map(int,input().split())); goal=tuple(map(int,input().split()))\nif start==goal: print(0); exit()\nq=deque([(start,start.index(0),0)]); vis={start}\nwhile q:\n    st,z,step=q.popleft()\n    zr,zc=divmod(z,3)\n    for dr,dc in ((1,0),(-1,0),(0,1),(0,-1)):\n        nr,nc=zr+dr,zc+dc\n        if 0\u003C=nr\u003C3 and 0\u003C=nc\u003C3:\n            ni=nr*3+nc; arr=list(st); arr[z],arr[ni]=arr[ni],arr[z]\n            ns=tuple(arr)\n            if ns==goal: print(step+1); exit()\n            if ns not in vis: vis.add(ns); q.append((ns,ni,step+1))\nprint(-1)\n","#include \u003Ciostream>\n#include \u003Cqueue>\n#include \u003Cvector>\n#include \u003Cmap>\nusing namespace std;\nint main(){ vector\u003Cint> s(9),g(9); for(int i=0;i\u003C9;i++) cin>>s[i]; for(int i=0;i\u003C9;i++) cin>>g[i]; string key; for(int x:s) key+=char(x+'0'); string gk; for(int x:g) gk+=char(x+'0'); if(key==gk){ cout\u003C\u003C0; return 0; } map\u003Cstring,int> vis; queue\u003Cpair\u003Cstring,int>> q; q.push({key,0}); vis[key]=1; while(!q.empty()){ auto [st,d]=q.front(); q.pop(); int z=st.find(\"0\"); int zr=z\u002F3,zc=z%3; int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; for(int k=0;k\u003C4;k++){ int nr=zr+dir[k][0],nc=zc+dir[k][1]; if(nr>=0&&nr\u003C3&&nc>=0&&nc\u003C3){ int ni=nr*3+nc; string ns=st; swap(ns[z],ns[ni]); if(ns==gk){ cout\u003C\u003Cd+1; return 0; } if(!vis[ns]){ vis[ns]=1; q.push({ns,d+1}); } } } } cout\u003C\u003C-1; }","状态=排列，0 与邻格交换扩展。","from collections import deque\nstart=tuple(map(int,input().split())); goal=tuple(map(int,input().split()))\nif start==goal: print(0); exit()\nq=deque([(start,start.index(0),0)]); vis={start}\nwhile q:\n    st,z,step=q.popleft()\n    zr,zc=divmod(z,3)\n    for dr,dc in ((1,0),(-1,0),(0,1),(0,-1)):\n        nr,nc=zr+dr,zc+dc\n        if 0\u003C=nr\u003C3 and 0\u003C=nc\u003C3:\n            ni=nr*3+nc; arr=list(st); arr[z],arr[ni]=arr[ni],arr[z]\n            ns=tuple(arr)\n            if ns==goal: print(step+1); exit()\n            if ns not in vis: vis.add(ns); q.append((ns,ni,step+1))\nprint(-1)","状态 BFS 把状态（位置、朝向、集合等）当作隐式图节点，用队列分层扩展，配 visited 去重；边权为 1 时首次到达某状态即为最少步数，适合滑块、机器人转向等最短操作问题。","10BFS-A2-4","BFS + 状态（双向 BFS）",{"id":8136,"title":8137,"level":6350,"module":7984,"seq":435,"goal":8139,"keywords":8140,"explanation":8144,"examples":8156,"exercises":8163,"summary":8197,"next":8198,"nextTitle":8199},"掌握双向 BFS：从起点与终点同时扩展，相遇时停止，可指数级减少搜索量；适用于状态空间大但路径深的场景。",[8141,8142,8143,3844],"双向 BFS","相遇","状态搜索",[8145,8150],{"title":8146,"content":8147,"codePy":8148,"codeCpp":8149},"1. 双向扩展","维护两个队列与两个 visited；每次扩展较小的一侧，当某状态在另一侧已访问即相遇，步数相加。","def bi_bfs(start, goal):\n    if start==goal: return 0\n    q1={start}; q2={goal}\n    d1={start:0}; d2={goal:0}\n    while q1 and q2:\n        if len(q1)\u003C=len(q2): q1, d1, q2, d2, side = expand(q1,d1,q2,d2,1)\n        else: q2, d2, q1, d1, side = expand(q2,d2,q1,d1,2)\n    return -1","\u002F\u002F 谁小扩谁",{"title":8151,"content":8152,"codePy":8153,"codeCpp":8154,"tip":8155},"2. 何时用","当分支因子 b、深度 d，单向 O(b^d)，双向 O(b^{d\u002F2})；当能轻松求出“前驱状态”时效果最佳。","print(\"深且宽时用双向\")","\u002F\u002F 状态可反推才可行","tip: 双向 BFS 需能从状态反推前驱。",[8157],{"title":8158,"prompt":8159,"analysis":8160,"codePy":8161,"codeCpp":8162,"run":2548},"例 1：复杂度","分支 2、深度 10，单向状态数约？","2^10=1024；双向约 2×2^5=64。","print(1024)","\u002F\u002F 1024",{"choice":8164,"coding":8185},[8165,8171,8179],{"id":38,"stem":8166,"options":8167,"answer":9,"explanation":8170},"双向 BFS 何时相遇？",[1568,8168,8169,5749],"两侧访问到同一状态","深度一半","状态被两侧都访问。",{"id":47,"stem":8172,"options":8173,"answer":9,"explanation":8178},"双向 BFS 复杂度？",[8174,8175,8176,8177],"O(b^d)","O(b^{d\u002F2})","O(d)","O(b*d)","平方级下降。",{"id":56,"stem":8180,"options":8181,"answer":9,"explanation":8184},"双向 BFS 前提？",[6810,8182,2971,8183],"能从状态反推前驱","小图","否则无法反向扩展。",[8186],{"id":67,"title":8187,"prompt":8188,"inputFormat":8189,"outputFormat":6275,"samples":8190,"starterPy":8193,"starterCpp":8194,"hint":8195,"referencePy":8196,"referenceCpp":8194},"双向 BFS（单词接龙）","给定起点词 begin、终点词 end 与词典 wordList，每次变一个字母且新词在词典中，求最短变换步数；无法则 0（按题目：不可达输出 0）。","第一行 begin end；第二行 n；接下来 n 行单词（长度均相同）。",[8191],{"input":8192,"output":290},"hit cog\n6\nhot\ndot\ndog\ncog\nlot\nlog","begin,end=input().split(); n=int(input()); words=[input().strip() for _ in range(n)]\nwords=set(words)|{begin}\nif end not in words: print(0); exit()\ndef nbr(w):\n    for i in range(len(w)):\n        for c in \"abcdefghijklmnopqrstuvwxyz\":\n            if c!=w[i]:\n                yield w[:i]+c+w[i+1:]\nq1={begin}; q2={end}; d1={begin:1}; d2={end:1}\nwhile q1 and q2:\n    if len(q1)\u003C=len(q2):\n        nq=set()\n        for w in q1:\n            for x in nbr(w):\n                if x in words:\n                    if x in d2: print(d1[w]+d2[x]); exit()\n                    if x not in d1: d1[x]=d1[w]+1; nq.add(x)\n        q1=nq\n    else:\n        nq=set()\n        for w in q2:\n            for x in nbr(w):\n                if x in words:\n                    if x in d1: print(d1[x]+d2[w]); exit()\n                    if x not in d2: d2[x]=d2[w]+1; nq.add(x)\n        q2=nq\nprint(0)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstring>\n#include \u003Cunordered_set>\n#include \u003Cqueue>\nusing namespace std;\nint main(){ string begin,end; cin>>begin>>end; int n; cin>>n; unordered_set\u003Cstring> words; for(int i=0;i\u003Cn;i++){ string w; cin>>w; words.insert(w);} words.insert(begin); if(words.find(end)==words.end()){ cout\u003C\u003C0; return 0; } unordered_set\u003Cstring> q1,q2; q1.insert(begin); q2.insert(end); unordered_map\u003Cstring,int> d1,d2; d1[begin]=1; d2[end]=1; while(!q1.empty()&&!q2.empty()){ if(q1.size()\u003C=q2.size()){ unordered_set\u003Cstring> nq; for(auto w:q1){ string s=w; for(int i=0;i\u003Cs.size();i++){ char o=s[i]; for(char c='a';c\u003C='z';c++){ if(c!=o){ s[i]=c; if(words.count(s)){ if(d2.count(s)){ cout\u003C\u003Cd1[w]+d2[s]; return 0; } if(!d1.count(s)){ d1[s]=d1[w]+1; nq.insert(s);} } } s[i]=o; } } } q1=nq; } else { unordered_set\u003Cstring> nq; for(auto w:q2){ string s=w; for(int i=0;i\u003Cs.size();i++){ char o=s[i]; for(char c='a';c\u003C='z';c++){ if(c!=o){ s[i]=c; if(words.count(s)){ if(d1.count(s)){ cout\u003C\u003Cd1[s]+d2[w]; return 0; } if(!d2.count(s)){ d2[s]=d2[w]+1; nq.insert(s);} } } s[i]=o; } } } q2=nq; } } cout\u003C\u003C0; }","两端同时扩展，相遇即步数和。","begin,end=input().split(); n=int(input()); words=[input().strip() for _ in range(n)]\nwords=set(words)|{begin}\nif end not in words: print(0); exit()\ndef nbr(w):\n    for i in range(len(w)):\n        for c in \"abcdefghijklmnopqrstuvwxyz\":\n            if c!=w[i]:\n                yield w[:i]+c+w[i+1:]\nq1={begin}; q2={end}; d1={begin:1}; d2={end:1}\nwhile q1 and q2:\n    if len(q1)\u003C=len(q2):\n        nq=set()\n        for w in q1:\n            for x in nbr(w):\n                if x in words:\n                    if x in d2: print(d1[w]+d2[x]); exit()\n                    if x not in d1: d1[x]=d1[w]+1; nq.add(x)\n        q1=nq\n    else:\n        nq=set()\n        for w in q2:\n            for x in nbr(w):\n                if x in words:\n                    if x in d1: print(d1[x]+d2[w]); exit()\n                    if x not in d2: d2[x]=d2[w]+1; nq.add(x)\n        q2=nq\nprint(0)","双向 BFS 从起点与终点同时扩展，每次扩展较小一侧，两侧访问到同一状态时步数相加即为最短路径；复杂度由 O(b^d) 降为 O(b^{d\u002F2})，适合状态空间大、路径深的搜索。","12-A2-1","链表插入与删除",{"id":7739,"title":7740,"level":6178,"module":8201,"seq":9,"goal":8202,"keywords":8203,"explanation":8205,"examples":8217,"exercises":8223,"summary":8252,"next":8253,"nextTitle":8254},"深度优先搜索（DFS）","掌握深度优先搜索：用递归（或栈）沿着一条路走到底再回溯，遍历所有状态\u002F节点。",[7111,1405,2796,8204],"访问标记",[8206,8211],{"title":8207,"content":8208,"codePy":8209,"codeCpp":8210},"1. 递归框架","访问节点→标记已访问→对未访问的邻居递归。回溯时注意恢复现场（视问题而定）。","def dfs(u):\n    vis[u]=True\n    for v in g[u]:\n        if not vis[v]: dfs(v)\n# g 为邻接表","\u002F\u002F void dfs(int u){ vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs(v); }",{"title":8212,"content":8213,"codePy":8214,"codeCpp":8215,"tip":8216},"2. 为什么用 DFS","适合遍历连通块、找路径、枚举全排列\u002F子集（回溯）；空间 O(深度)，比 BFS 省内存。","print(\"DFS 走到底再回退\")","\u002F\u002F 回溯=撤销选择再试别的","tip: 需要“恢复现场”时在递归返回前撤销。",[8218],{"title":8219,"prompt":8220,"analysis":8221,"codePy":8222,"codeCpp":5315,"run":74},"例 1：遍历","链 1-2-3，从 1 DFS 访问顺序？","1→2→3。","print([1,2,3])",{"choice":8224,"coding":8242},[8225,8232,8237],{"id":38,"stem":8226,"options":8227,"answer":9,"explanation":8231},"DFS 的突出特点是？",[7748,8228,8229,8230],"走到底再回溯","用队列","最短","深度优先。",{"id":47,"stem":8233,"options":8234,"answer":9,"explanation":8236},"避免重复访问用？",[4166,8235,3117,6565],"vis 标记","标记已访问。",{"id":56,"stem":8238,"options":8239,"answer":9,"explanation":8241},"回溯常用于？",[7749,8240,3117,4166],"枚举\u002F搜索","枚举所有方案。",[8243],{"id":67,"title":8244,"prompt":8245,"inputFormat":7791,"outputFormat":6275,"samples":8246,"starterPy":8248,"starterCpp":8249,"hint":8250,"referencePy":8251,"referenceCpp":8249},"连通块大小","n 点 m 边无向图，求含节点 1 的连通块大小。",[8247],{"input":7794,"output":890},"import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True; c=1\n    for v in g[u]:\n        if not vis[v]: c+=dfs(v)\n    return c\nprint(dfs(1))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; bool vis[100005];\nint dfs(int u){ vis[u]=1; int c=1; for(int v:g[u]) if(!vis[v]) c+=dfs(v); return c; }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} cout\u003C\u003Cdfs(1); }","递归累加邻居块大小。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True; c=1\n    for v in g[u]:\n        if not vis[v]: c+=dfs(v)\n    return c\nprint(dfs(1))","DFS 用递归沿一条分支走到底再回溯，配合 vis 标记避免重复；适合遍历、找路径与回溯枚举，空间随深度增长。","10DFS-A1-2","DFS 与连通性",{"id":8253,"title":8254,"level":6178,"module":8201,"seq":63,"goal":8256,"keywords":8257,"explanation":8260,"examples":8271,"exercises":8276,"summary":8306,"next":8307,"nextTitle":8308},"用 DFS 判断两点是否连通、统计连通块个数，理解“从一个点能到达的所有点”构成连通块。",[8258,7779,8259],"连通性","可达",[8261,8266],{"title":8262,"content":8263,"codePy":8264,"codeCpp":8265},"1. 可达性","从 s 出发 DFS，凡被访问到的点都从 s 可达；若 t 被访问则连通。","def reachable(s,t):\n    dfs(s); return vis[t]\n# 复用 vis","\u002F\u002F 从 s 跑一遍，看 vis[t]",{"title":8267,"content":8268,"codePy":8269,"codeCpp":8270},"2. 连通块计数","对每个未访问点做一次 DFS，每次启动算一个新连通块。","blocks=0\nfor i in range(1,n+1):\n    if not vis[i]:\n        dfs(i); blocks+=1","\u002F\u002F 未访问就 dfs，blocks++",[8272],{"title":8273,"prompt":8274,"analysis":8275,"codePy":6253,"codeCpp":5851,"run":759},"例 1：连通块","点 1-2 相连、3 孤立，连通块数？","两个块 {1,2}、{3}。",{"choice":8277,"coding":8295},[8278,8284,8290],{"id":38,"stem":8279,"options":8280,"answer":9,"explanation":8283},"判断 s 到 t 连通？",[8281,8282,3117,4166],"比较度数","从 s DFS 看是否访问 t","可达即连通。",{"id":47,"stem":8285,"options":8286,"answer":9,"explanation":8289},"统计连通块的方法是？",[8287,8288,7747,7778],"一次 DFS","每个未访问点启动 DFS","每新起点一个块。",{"id":56,"stem":8291,"options":8292,"answer":9,"explanation":8294},"孤立点算几个连通块？",[891,892,759,8293],"看边数","自身一个块。",[8296],{"id":67,"title":8297,"prompt":8298,"inputFormat":7791,"outputFormat":6275,"samples":8299,"starterPy":8302,"starterCpp":8303,"hint":8304,"referencePy":8305,"referenceCpp":8303},"连通块计数","n 点 m 边无向图，求连通块个数。",[8300],{"input":8301,"output":759},"3 1\n1 2","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True\n    for v in g[u]:\n        if not vis[v]: dfs(v)\nblk=0\nfor i in range(1,n+1):\n    if not vis[i]: dfs(i); blk+=1\nprint(blk)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; bool vis[100005];\nvoid dfs(int u){ vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs(v); }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} int blk=0; for(int i=1;i\u003C=n;i++) if(!vis[i]){ dfs(i); blk++; } cout\u003C\u003Cblk; }","每个未访问点跑一次 DFS。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True\n    for v in g[u]:\n        if not vis[v]: dfs(v)\nblk=0\nfor i in range(1,n+1):\n    if not vis[i]: dfs(i); blk+=1\nprint(blk)","从一点 DFS 能到达的所有点即其连通块；对每个未访问点启动一次 DFS 即可统计全图连通块数。","10DFS-A1-3","DFS 与回溯枚举",{"id":8307,"title":8308,"level":6178,"module":8201,"seq":703,"goal":8310,"keywords":8311,"explanation":8315,"examples":8327,"exercises":8334,"summary":8361,"next":8362,"nextTitle":8363},"用 DFS 回溯系统地枚举所有方案（全排列、子集），并在递归返回前“恢复现场”以尝试其他分支。",[2796,8312,8313,8314],"恢复现场","全排列","子集",[8316,8321],{"title":8317,"content":8318,"codePy":8319,"codeCpp":8320},"1. 全排列","依次确定每个位置选哪个未用元素，递归到底收集一种排列，返回前取消选择（恢复现场）。","def perm(path, used):\n    if len(path)==n: ans.append(path[:])\n    for i in range(n):\n        if not used[i]:\n            used[i]=True; path.append(i)\n            perm(path, used)\n            path.pop(); used[i]=False","\u002F\u002F 选 i：标记 used、加入 path；回溯时弹出并取消",{"title":8322,"content":8323,"codePy":8324,"codeCpp":8325,"tip":8326},"2. 子集","每个元素“选\u002F不选”两种分支，DFS 到底得到一种子集。","def sub(path, i):\n    if i==n: ans.append(path[:]); return\n    sub(path, i+1)            # 不选\n    sub(path+[a[i]], i+1)     # 选","\u002F\u002F 2^n 种，DFS 自然枚举","tip: 回溯的核心=“做选择→递归→撤销选择”。",[8328],{"title":8329,"prompt":8330,"analysis":8331,"codePy":8332,"codeCpp":8333,"run":148},"例 1：子集数","3 个元素的子集总数？","2^3=8 个。","print(8)","\u002F\u002F 8",{"choice":8335,"coding":8350},[8336,8340,8345],{"id":38,"stem":8337,"options":8338,"answer":9,"explanation":8339},"回溯枚举必须？",[3117,8312,8229,4166],"撤销选择再试别的分支。",{"id":47,"stem":8341,"options":8342,"answer":9,"explanation":8344},"n 元素全排列数？",[7130,7145,8343,3054],"n^n","阶乘。",{"id":56,"stem":8346,"options":8347,"answer":9,"explanation":8349},"子集枚举分支数？",[892,8348,3054,6499],"2（选\u002F不选）","每元素两种。",[8351],{"id":67,"title":8313,"prompt":8352,"inputFormat":1456,"outputFormat":8353,"samples":8354,"starterPy":8357,"starterCpp":8358,"hint":8359,"referencePy":8360,"referenceCpp":8358},"输出 1..n 的所有全排列（n≤8）。","每行一个排列，空格分隔。",[8355],{"input":759,"output":8356},"1 2\n2 1","n=int(input())\nused=[False]*(n+1); path=[]\ndef dfs(u):\n    if u>n:\n        print(*path); return\n    for i in range(1,n+1):\n        if not used[i]:\n            used[i]=True; path.append(i); dfs(u+1); path.pop(); used[i]=False\ndfs(1)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Cint> path; bool used[9];\nvoid dfs(int d){ if(d>n){ for(int x:path) cout\u003C\u003Cx\u003C\u003C' '; cout\u003C\u003Cendl; return; } for(int i=1;i\u003C=n;i++) if(!used[i]){ used[i]=1; path.push_back(i); dfs(d+1); path.pop_back(); used[i]=0; } }\nint main(){ cin>>n; dfs(1); }","选未用 i，递归后撤销。","n=int(input())\nused=[False]*(n+1); path=[]\ndef dfs(u):\n    if u>n:\n        print(*path); return\n    for i in range(1,n+1):\n        if not used[i]:\n            used[i]=True; path.append(i); dfs(u+1); path.pop(); used[i]=False\ndfs(1)","回溯 = 做选择→递归→撤销选择（恢复现场）；用 DFS 可系统枚举全排列（n!）与子集（2^n），是搜索问题的基础。","10DFS-A1-4","DFS 与环检测",{"id":8362,"title":8363,"level":6178,"module":8201,"seq":770,"goal":8365,"keywords":8366,"explanation":8371,"examples":8382,"exercises":8390,"summary":8423,"next":8424,"nextTitle":8425},"用 DFS 的三色标记（白\u002F灰\u002F黑）检测有向图是否存在环：遇到“灰色”祖先说明有回边即环。",[8367,8368,8369,8370],"环检测","三色","回边","有向图",[8372,8377],{"title":8373,"content":8374,"codePy":8375,"codeCpp":8376},"1. 三色法","0=未访问，1=正在访问（递归栈中），2=已完成。DFS 中遇到状态 1 的邻居即有环。","def has_cycle():\n    color=[0]*(n+1)\n    def dfs(u):\n        color[u]=1\n        for v in g[u]:\n            if color[v]==1: return True\n            if color[v]==0 and dfs(v): return True\n        color[u]=2; return False\n    return any(dfs(i) for i in range(1,n+1))","\u002F\u002F 灰点=在栈中；遇灰点=>环",{"title":8378,"content":8379,"codePy":8380,"codeCpp":8381},"2. 无向图判环","无向图只需记录父节点，遇到已访问且非父的邻居即环（注意无向边双向）。","print(\"无向图：遇已访问且≠父 => 环\")","\u002F\u002F 有向用三色，无向用父判断",[8383],{"title":8384,"prompt":8385,"analysis":8386,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：有环？","有向边 1→2→3→1，是否有环？","3→1 回到祖先，有环。","print(True)","\u002F\u002F true","true",{"choice":8391,"coding":8410},[8392,8399,8406],{"id":38,"stem":8393,"options":8394,"answer":9,"explanation":8398},"有向图遇哪种状态说明有环？",[8395,8396,8397,3952],"白","灰（在栈中）","黑","回边指向祖先。",{"id":47,"stem":8400,"options":8401,"answer":607,"explanation":8405},"无向图判环要记录？",[8402,8403,8404,7965],"父节点","度数","颜色数","排除父边。",{"id":56,"stem":8407,"options":8408,"answer":63,"explanation":8409},"三色中“已完成”是？",[891,892,759,890],"2=已离开递归栈。",[8411],{"id":67,"title":8412,"prompt":8413,"inputFormat":7791,"outputFormat":8414,"samples":8415,"starterPy":8419,"starterCpp":8420,"hint":8421,"referencePy":8422,"referenceCpp":8420},"有向图判环","n 点 m 边有向图，判断是否存在环。","一行 YES 或 NO。",[8416],{"input":8417,"output":8418},"3 3\n1 2\n2 3\n3 1","YES","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ncolor=[0]*(n+1)\ndef dfs(u):\n    color[u]=1\n    for v in g[u]:\n        if color[v]==1: return True\n        if color[v]==0 and dfs(v): return True\n    color[u]=2; return False\nok=any(dfs(i) for i in range(1,n+1))\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; int color[100005];\nbool dfs(int u){ color[u]=1; for(int v:g[u]){ if(color[v]==1) return true; if(color[v]==0 && dfs(v)) return true; } color[u]=2; return false; }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} bool ok=0; for(int i=1;i\u003C=n;i++) if(!color[i]) ok=dfs(i); cout\u003C\u003C(ok?\"YES\":\"NO\"); }","三色：灰点遇灰点即环。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ncolor=[0]*(n+1)\ndef dfs(u):\n    color[u]=1\n    for v in g[u]:\n        if color[v]==1: return True\n        if color[v]==0 and dfs(v): return True\n    color[u]=2; return False\nok=any(dfs(i) for i in range(1,n+1))\nprint(\"YES\" if ok else \"NO\")","有向图用三色标记（白\u002F灰\u002F黑）：DFS 中遇到“灰色”祖先即存在回边，说明有环；无向图改用“记录父节点”避免把来边当环。","10DFS-A1-5","DFS 求拓扑序",{"id":8424,"title":8425,"level":6178,"module":8201,"seq":838,"goal":8427,"keywords":8428,"explanation":8433,"examples":8444,"exercises":8449,"summary":8482,"next":7742,"nextTitle":7743},"用 DFS（逆后序）求出有向无环图的拓扑排序：每次离开节点时把它加入答案头部。",[8429,8430,8431,8432],"拓扑排序","逆后序","DAG","依赖",[8434,8439],{"title":8435,"content":8436,"codePy":8437,"codeCpp":8438},"1. 逆后序","对 DAG 做 DFS，节点“完成”的顺序反过来就是拓扑序（被依赖者先完成）。","def topo():\n    order=[]\n    def dfs(u):\n        color[u]=1\n        for v in g[u]:\n            if color[v]==0: dfs(v)\n        color[u]=2; order.append(u)\n    for i in range(1,n+1):\n        if color[i]==0: dfs(i)\n    return order[::-1]","\u002F\u002F 完成即入栈，最后反转",{"title":8440,"content":8441,"codePy":8442,"codeCpp":8443},"2. 存在环则无拓扑序","若图有环（dfs 遇灰点）则不能拓扑排序；可先判环。","print(\"有环 => 无拓扑序\")","\u002F\u002F 拓扑排序仅对 DAG 存在",[8445],{"title":8446,"prompt":8447,"analysis":8448,"codePy":8222,"codeCpp":5315,"run":74},"例 1：拓扑","边 1→2, 1→3，一个拓扑序？","1 在前：1 2 3 或 1 3 2。",{"choice":8450,"coding":8472},[8451,8458,8465],{"id":38,"stem":8452,"options":8453,"answer":9,"explanation":8457},"拓扑排序只能用于？",[8454,8431,8455,8456],"任意图","无向图","完全图","有环则无解。",{"id":47,"stem":8459,"options":8460,"answer":9,"explanation":8464},"DFS 拓扑把完成的节点？",[8461,8462,8463,4166],"丢弃","入栈后反转","置顶","逆后序。",{"id":56,"stem":8466,"options":8467,"answer":9,"explanation":8471},"被依赖者应在答案的？",[8468,8469,3952,8470],"后面","前面","中间","先完成依赖。",[8473],{"id":67,"title":8429,"prompt":8474,"inputFormat":7791,"outputFormat":5404,"samples":8475,"starterPy":8478,"starterCpp":8479,"hint":8480,"referencePy":8481,"referenceCpp":8479},"n 点 m 边 DAG，输出一种拓扑序（保证有解）。",[8476],{"input":8477,"output":74},"3 2\n1 2\n1 3","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ncolor=[0]*(n+1); order=[]\ndef dfs(u):\n    color[u]=1\n    for v in g[u]:\n        if color[v]==0: dfs(v)\n    color[u]=2; order.append(u)\nfor i in range(1,n+1):\n    if color[i]==0: dfs(i)\nprint(*order[::-1])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; int color[100005]; vector\u003Cint> order;\nvoid dfs(int u){ color[u]=1; for(int v:g[u]) if(color[v]==0) dfs(v); color[u]=2; order.push_back(u); }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} for(int i=1;i\u003C=n;i++) if(!color[i]) dfs(i); for(int i=order.size()-1;i>=0;i--) cout\u003C\u003Corder[i]\u003C\u003C' '; }","完成后入 order，最后反转输出。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ncolor=[0]*(n+1); order=[]\ndef dfs(u):\n    color[u]=1\n    for v in g[u]:\n        if color[v]==0: dfs(v)\n    color[u]=2; order.append(u)\nfor i in range(1,n+1):\n    if color[i]==0: dfs(i)\nprint(*order[::-1])","对 DAG 做 DFS，把“完成”的节点依次入栈、最后反转即得拓扑序；被依赖者先完成。有环则不存在拓扑排序。",{"id":6640,"title":6641,"level":6350,"module":7984,"seq":936,"goal":8484,"keywords":8485,"explanation":8489,"examples":8500,"exercises":8505,"summary":8530,"next":8531,"nextTitle":8532},"用 DFS 在无向\u002F有向图上求连通分量个数与每个块包含的节点，理解“从每个未访问点出发能到达的全部点”构成一个分量。",[8486,7111,8487,8488],"连通分量","分量计数","图",[8490,8495],{"title":8491,"content":8492,"codePy":8493,"codeCpp":8494},"1. 分量计数","对每个未访问节点做一次 DFS，访问到的所有点归入同一分量；启动次数即分量数。","def components(g, n):\n    vis=[False]*(n+1); comp=0; size=[]\n    def dfs(u):\n        vis[u]=True; c[0]+=1\n        for v in g[u]:\n            if not vis[v]: dfs(v)\n    for i in range(1,n+1):\n        if not vis[i]:\n            c=[0]; dfs(i); comp+=1; size.append(c[0])\n    return comp, size","\u002F\u002F 每新起点一个分量",{"title":8496,"content":8497,"codePy":8498,"codeCpp":8499},"2. 有向图的强连通","无向图用上述方法；有向图的强连通分量需用 Tarjan\u002FKosaraju（后续进阶），此处先掌握无向情形。","print(\"有向 SCC 需 Tarjan\")","\u002F\u002F 无向分量=DFS 块",[8501],{"title":8502,"prompt":8503,"analysis":8504,"codePy":6253,"codeCpp":5851,"run":759},"例 1：分量数","点 1-2、3-4 两对相连，分量数？","两个分量 {1,2},{3,4}。",{"choice":8506,"coding":8523},[8507,8510,8518],{"id":38,"stem":8508,"options":8509,"answer":9,"explanation":8289},"统计连通分量方法？",[8287,8288,7747,7778],{"id":47,"stem":8511,"options":8512,"answer":9,"explanation":8517},"无向图分量用？",[8513,8514,8515,8516],"Tarjan","DFS\u002FBFS 遍历","最短路径","并查集必用","遍历即可。",{"id":56,"stem":8519,"options":8520,"answer":9,"explanation":8522},"有向图强连通分量需？",[7111,8521,7747,8429],"Tarjan\u002FKosaraju","SCC 需专门算法。",[8524],{"id":67,"title":8486,"prompt":8525,"inputFormat":7791,"outputFormat":6275,"samples":8526,"starterPy":8302,"starterCpp":8303,"hint":8529,"referencePy":8305,"referenceCpp":8303},"n 点 m 边无向图，求连通分量个数。",[8527],{"input":8528,"output":759},"4 2\n1 2\n3 4","未访问就 DFS 一次。","从每个未访问节点启动一次 DFS\u002FBFS，能到达的全部节点构成一个连通分量；启动次数即分量数。有向图的强连通分量需 Tarjan 等专门算法。","10DFS-A2-2","全排列（去重）",{"id":8531,"title":8532,"level":6350,"module":7984,"seq":998,"goal":8534,"keywords":8535,"explanation":8538,"examples":8550,"exercises":8555,"summary":8587,"next":8588,"nextTitle":8589},"掌握含重复元素的全排列：排序后用“同一层相同值只用一次”的剪枝避免重复排列，理解回溯中“层内去重”。",[8313,4356,8536,8537],"回溯剪枝","层内剪枝",[8539,8544],{"title":8540,"content":8541,"codePy":8542,"codeCpp":8543},"1. 含重去重","先排序，递归选位置时跳过“与前一个相同且前一个未被使用”的情况（说明同层已用过该值）。","def permute(a):\n    a.sort(); n=len(a); used=[False]*n; res=[]; path=[]\n    def dfs():\n        if len(path)==n: res.append(path[:]); return\n        for i in range(n):\n            if used[i]: continue\n            if i>0 and a[i]==a[i-1] and not used[i-1]: continue\n            used[i]=True; path.append(a[i]); dfs(); path.pop(); used[i]=False\n    dfs(); return res","\u002F\u002F i>0 && a[i]==a[i-1] && !used[i-1] 则剪",{"title":8545,"content":8546,"codePy":8547,"codeCpp":8548,"tip":8549},"2. 为何 used[i-1] 为假","若前一相同值已结束（回溯还原），说明它在更浅层已贡献过排列，本层再选会重复；只保留“前一值正被使用”的分支。","print(\"同一层同值只用一次\")","\u002F\u002F 经典去重条件","tip: 该剪枝保证每种排列仅生成一次。",[8551],{"title":8552,"prompt":8553,"analysis":8554,"codePy":6544,"codeCpp":6545,"run":890},"例 1：去重排列","[1,1,2] 的排列数？","3!\u002F2!=3 种：112,121,211。",{"choice":8556,"coding":8575},[8557,8563,8571],{"id":38,"stem":8558,"options":8559,"answer":9,"explanation":8562},"含重全排列为何要排序？",[3345,8560,6446,8561],"便于相邻相同值剪枝","无需","相邻相同才可判重。",{"id":47,"stem":8564,"options":8565,"answer":703,"explanation":8570},"剪枝条件关键是？",[8566,8567,8568,8569],"used[i-1] 为假","a[i]==a[i-1]","i>0","都用","三条件组合。",{"id":56,"stem":8572,"options":8573,"answer":9,"explanation":8574},"[1,1,2] 排列数？",[624,890,759,892],"3!\u002F2!=3。",[8576],{"id":67,"title":8532,"prompt":8577,"inputFormat":1801,"outputFormat":8578,"samples":8579,"starterPy":8583,"starterCpp":8584,"hint":8585,"referencePy":8586,"referenceCpp":8584},"给定 n 个可能重复的整数，输出所有不重复的排列（n≤8）。","每行一个排列，空格分隔；按字典序。",[8580],{"input":8581,"output":8582},"3\n1 1 2","1 1 2\n1 2 1\n2 1 1","n=int(input()); a=list(map(int,input().split())); a.sort()\nused=[False]*n; res=[]; path=[]\ndef dfs():\n    if len(path)==n: print(*path); return\n    for i in range(n):\n        if used[i]: continue\n        if i>0 and a[i]==a[i-1] and not used[i-1]: continue\n        used[i]=True; path.append(a[i]); dfs(); path.pop(); used[i]=False\ndfs()\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> a,path; vector\u003Cbool> used;\nvoid dfs(){ if(path.size()==n){ for(int x:path) cout\u003C\u003Cx\u003C\u003C\" \"; cout\u003C\u003Cendl; return; } for(int i=0;i\u003Cn;i++){ if(used[i]) continue; if(i>0&&a[i]==a[i-1]&&!used[i-1]) continue; used[i]=1; path.push_back(a[i]); dfs(); path.pop_back(); used[i]=0; } }\nint main(){ cin>>n; a.resize(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; sort(a.begin(),a.end()); used.assign(n,0); dfs(); }","排序后层内相同值剪枝。","n=int(input()); a=list(map(int,input().split())); a.sort()\nused=[False]*n; res=[]; path=[]\ndef dfs():\n    if len(path)==n: print(*path); return\n    for i in range(n):\n        if used[i]: continue\n        if i>0 and a[i]==a[i-1] and not used[i-1]: continue\n        used[i]=True; path.append(a[i]); dfs(); path.pop(); used[i]=False\ndfs()","含重复元素的全排列需先排序，再用“i>0 且 a[i]==a[i-1] 且前一相同值未被使用”的剪枝，保证同一层相同值只选一次，避免重复排列。","10DFS-A2-3","组合（C(n,k) 枚举）",{"id":8588,"title":8589,"level":6350,"module":7984,"seq":1066,"goal":8591,"keywords":8592,"explanation":8595,"examples":8607,"exercises":8612,"summary":8640,"next":8641,"nextTitle":8642},"用回溯枚举从 n 个元素中选 k 个的所有组合，理解“按位置升序选”以避免重复并自然去重。",[1473,2796,8593,8594],"C(n,k)","升序选",[8596,8601],{"title":8597,"content":8598,"codePy":8599,"codeCpp":8600},"1. 升序选数","从下标 start 开始向后选，每次选一个就递归选下一个（start+1），选满 k 个收集；天然避免同组合不同顺序。","def combine(n, k):\n    res=[]; path=[]\n    def dfs(start):\n        if len(path)==k: res.append(path[:]); return\n        for i in range(start, n+1):\n            path.append(i); dfs(i+1); path.pop()\n    dfs(1); return res","\u002F\u002F 从 start 起向后选，避免重复",{"title":8602,"content":8603,"codePy":8604,"codeCpp":8605,"tip":8606},"2. 剪枝上界","若剩余可选不足 k-len(path) 个，可提前结束；或 for i 上限 n-(k-len(path))+1。","print(\"剩余不够可剪枝\")","\u002F\u002F 减少无效递归","tip: 组合与排列区别：组合不关心顺序。",[8608],{"title":8609,"prompt":8610,"analysis":8611,"codePy":4786,"codeCpp":4432,"run":624},"例 1：组合数","C(4,2) 共几种？","6 种：12,13,14,23,24,34。",{"choice":8613,"coding":8630},[8614,8619,8623],{"id":38,"stem":8615,"options":8616,"answer":9,"explanation":8618},"枚举组合为何从 start 向后选？",[1568,8617,6446,3117],"避免同组不同序重复","顺序固定不重。",{"id":47,"stem":8620,"options":8621,"answer":9,"explanation":8622},"C(4,2) 数量？",[1663,624,148,884],"4×3\u002F2=6。",{"id":56,"stem":8624,"options":8625,"answer":9,"explanation":8629},"组合与排列区别？",[6810,8626,8627,8628],"组合不关心顺序","组合更多","组合更少","组合无序。",[8631],{"id":67,"title":1473,"prompt":8632,"inputFormat":7151,"outputFormat":8633,"samples":8634,"starterPy":8636,"starterCpp":8637,"hint":8638,"referencePy":8639,"referenceCpp":8637},"输出 1..n 中任选 k 个的所有组合（按字典序，n≤20）。","每行一个组合，空格分隔。",[8635],{"input":7155,"output":7156},"n,k=map(int,input().split())\npath=[]\ndef dfs(start):\n    if len(path)==k: print(*path); return\n    for i in range(start, n-(k-len(path))+2):\n        path.append(i); dfs(i+1); path.pop()\ndfs(1)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n,k; vector\u003Cint> path;\nvoid dfs(int start){ if(path.size()==k){ for(int x:path) cout\u003C\u003Cx\u003C\u003C\" \"; cout\u003C\u003Cendl; return; } for(int i=start; i\u003C=n-(k-path.size())+1; i++){ path.push_back(i); dfs(i+1); path.pop_back(); } }\nint main(){ cin>>n>>k; dfs(1); }","从 start 向后选满 k 个。","n,k=map(int,input().split())\npath=[]\ndef dfs(start):\n    if len(path)==k: print(*path); return\n    for i in range(start, n-(k-len(path))+2):\n        path.append(i); dfs(i+1); path.pop()\ndfs(1)","组合枚举用回溯：从 start 下标向后选数（start+1 递归），选满 k 个收集；按位升序选自然避免同一组合的不同排列重复，还可据剩余数量剪枝。","10DFS-A2-4","子集（含限制）",{"id":8641,"title":8642,"level":6350,"module":7984,"seq":1131,"goal":8644,"keywords":8645,"explanation":8648,"examples":8660,"exercises":8667,"summary":8700,"next":8701,"nextTitle":8702},"在子集枚举基础上加入约束（如子集和等于目标、或大小限制），掌握“选\u002F不选 + 剪枝”的回溯框架。",[8314,8646,2796,8647],"背包剪枝","目标约束",[8649,8654],{"title":8650,"content":8651,"codePy":8652,"codeCpp":8653},"1. 带和约束","每个元素选或不选，记录当前和；当和==target 收集，当和>target 可剪枝（元素为正时）。","def subsets_sum(a, T):\n    res=[]; path=[]\n    def dfs(i, s):\n        if s==T: res.append(path[:])\n        if i==len(a) or s>T: return\n        for take in (False, True):\n            if take: path.append(a[i]); dfs(i+1, s+a[i]); path.pop()\n            else: dfs(i+1, s)\n    dfs(0,0); return res","\u002F\u002F 选\u002F不选 + 和约束剪枝",{"title":8655,"content":8656,"codePy":8657,"codeCpp":8658,"tip":8659},"2. 与组合关系","子集是“从给定数组选任意个”，组合是“选恰好 k 个”；二者都是回溯选\u002F不选的变体。","print(\"子集不限数量\")","\u002F\u002F 组合=限 k 个的子集","tip: 正元素时 s>T 可剪枝。",[8661],{"title":8662,"prompt":8663,"analysis":8664,"codePy":8665,"codeCpp":8666,"run":759},"例 1：子集和","[2,3,5]，target=5，哪些子集？","[5] 与 [2,3]。","print([[5],[2,3]])","\u002F\u002F [5],[2,3]",{"choice":8668,"coding":8686},[8669,8675,8680],{"id":38,"stem":8670,"options":8671,"answer":9,"explanation":8674},"子集枚举每元素？",[3117,8672,8673,505],"选或不选","只选","两分支递归。",{"id":47,"stem":8676,"options":8677,"answer":9,"explanation":8679},"元素为正且和>T 时？",[8678,3844,6553,3117],"继续","后续只会更大。",{"id":56,"stem":8681,"options":8682,"answer":9,"explanation":8685},"子集与组合区别？",[6810,8683,8684,2661],"子集不限数量","组合无序","子集数量任意。",[8687],{"id":67,"title":8688,"prompt":8689,"inputFormat":8690,"outputFormat":8691,"samples":8692,"starterPy":8696,"starterCpp":8697,"hint":8698,"referencePy":8699,"referenceCpp":8697},"子集和等于目标","n 个正整数与 target，输出所有元素和恰为 target 的子集（按任意顺序，n≤20）。","第一行 n target；第二行 n 个整数。","每行一个子集，空格分隔。",[8693],{"input":8694,"output":8695},"3 5\n2 3 5","5\n2 3","n,T=map(int,input().split()); a=list(map(int,input().split()))\npath=[]\ndef dfs(i,s):\n    if s==T: print(*path); return\n    if i==n or s>T: return\n    dfs(i+1,s)\n    path.append(a[i]); dfs(i+1,s+a[i]); path.pop()\ndfs(0,0)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n,T; vector\u003Cint> a,path;\nvoid dfs(int i,int s){ if(s==T){ for(int x:path) cout\u003C\u003Cx\u003C\u003C\" \"; cout\u003C\u003Cendl; return; } if(i>=n||s>T) return; dfs(i+1,s); path.push_back(a[i]); dfs(i+1,s+a[i]); path.pop_back(); }\nint main(){ cin>>n>>T; a.resize(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; dfs(0,0); }","两分支：不选 \u002F 选，和>T 剪枝。","n,T=map(int,input().split()); a=list(map(int,input().split()))\npath=[]\ndef dfs(i,s):\n    if s==T: print(*path); return\n    if i==n or s>T: return\n    dfs(i+1,s)\n    path.append(a[i]); dfs(i+1,s+a[i]); path.pop()\ndfs(0,0)","子集枚举对每个元素分“选\u002F不选”两分支递归；加入目标和等约束时，可在超过目标（正元素）或剩余不足时剪枝，从而高效枚举可行子集。","10DFS-A2-5","回溯通用框架与剪枝",{"id":8701,"title":8702,"level":6350,"module":7984,"seq":89,"goal":8704,"keywords":8705,"explanation":8710,"examples":8722,"exercises":8730,"summary":8760,"next":8761,"nextTitle":8762},"归纳回溯的通用范式：做选择→递归→撤销选择；并掌握两类剪枝——可行性剪枝与最优性剪枝，用以削减搜索树。",[8706,8707,8708,8709],"回溯框架","可行性剪枝","最优性剪枝","搜索树",[8711,8716],{"title":8712,"content":8713,"codePy":8714,"codeCpp":8715},"1. 通用范式","递归函数维护“已做选择”与“当前状态”；进入时做选择，递归返回前撤销；当达到目标或非法时结束。","def backtrack(path, state):\n    if goal(state): record(path); return\n    if invalid(state): return\n    for choice in candidates(state):\n        path.append(choice); make(choice)\n        backtrack(path, state)\n        path.pop(); undo(choice)","\u002F\u002F 选->递归->撤销",{"title":8717,"content":8718,"codePy":8719,"codeCpp":8720,"tip":8721},"2. 两类剪枝","可行性剪枝：当前状态已不可能达成目标则返回；最优性剪枝：当前代价已差于已知最优则返回（如分支定界）。","print(\"能剪则剪，缩小树\")","\u002F\u002F 剪枝决定能否通过","tip: 剪枝是搜索题过大数据的关键。",[8723],{"title":8724,"prompt":8725,"analysis":8726,"codePy":8727,"codeCpp":8728,"run":8729},"例 1：剪枝效果","n=30 全排列用剪枝？","30! 太大，需针对性剪枝否则 TLE。","print(\"必须剪枝\")","\u002F\u002F prune","prune",{"choice":8731,"coding":8748},[8732,8737,8743],{"id":38,"stem":8733,"options":8734,"answer":9,"explanation":8736},"回溯核心三步？",[3117,8735,192,4166],"选-递归-撤销","做选择、递归、撤销。",{"id":47,"stem":8738,"options":8739,"answer":9,"explanation":8742},"可行性剪枝指？",[8740,8741,3117,6039],"换算法","已不可能达成即返回","提前终止死路。",{"id":56,"stem":8744,"options":8745,"answer":9,"explanation":8747},"最优性剪枝依据？",[1568,8746,6269,7965],"当前已差于最优","分支定界。",[8749],{"id":67,"title":8750,"prompt":8751,"inputFormat":1456,"outputFormat":8752,"samples":8753,"starterPy":8756,"starterCpp":8757,"hint":8758,"referencePy":8759,"referenceCpp":8757},"N 皇后","n×n 棋盘放 n 个皇后互不攻击，输出所有合法摆法（n≤10）。","每行一个解，用列号表示（第 i 个数字为第 i 行皇后所在列）。",[8754],{"input":1663,"output":8755},"2 4 1 3\n3 1 4 2","n=int(input()); cols=[]; res=[]\ndef ok(r,c):\n    for i in range(r):\n        if cols[i]==c or abs(cols[i]-c)==r-i: return False\n    return True\ndef dfs(r):\n    if r==n: res.append(cols[:]); return\n    for c in range(n):\n        if ok(r,c): cols.append(c); dfs(r+1); cols.pop()\ndfs(0)\nfor s in res: print(*[x+1 for x in s])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Cint> cols; vector\u003Cvector\u003Cint>> res;\nbool ok(int r,int c){ for(int i=0;i\u003Cr;i++) if(cols[i]==c||abs(cols[i]-c)==r-i) return false; return true; }\nvoid dfs(int r){ if(r==n){ res.push_back(cols); return; } for(int c=0;c\u003Cn;c++) if(ok(r,c)){ cols.push_back(c); dfs(r+1); cols.pop_back(); } }\nint main(){ cin>>n; dfs(0); for(auto& s:res){ for(int x:s) cout\u003C\u003Cx+1\u003C\u003C\" \"; cout\u003C\u003Cendl; } }","按行选列，剪掉同列\u002F同对角线。","n=int(input()); cols=[]; res=[]\ndef ok(r,c):\n    for i in range(r):\n        if cols[i]==c or abs(cols[i]-c)==r-i: return False\n    return True\ndef dfs(r):\n    if r==n: res.append(cols[:]); return\n    for c in range(n):\n        if ok(r,c): cols.append(c); dfs(r+1); cols.pop()\ndfs(0)\nfor s in res: print(*[x+1 for x in s])","回溯通用框架是“做选择→递归→撤销选择”，并以目标\u002F非法作为终止；用可行性剪枝（死路即返）与最优性剪枝（已差于最优即返）大幅削减搜索树，是搜索题通过大数据的关键。","10DFS-A2-6","状态搜索（隐式图 DFS）",{"id":8761,"title":8762,"level":6350,"module":7984,"seq":158,"goal":8764,"keywords":8765,"explanation":8768,"examples":8779,"exercises":8787,"summary":8819,"next":7983,"nextTitle":7860},"把“状态”当作节点进行 DFS\u002F回溯搜索：状态含当前位置、已收集物等；通过 visited 记录已搜状态避免重复与环。",[8143,8085,8766,8767],"状态去重","搜索空间",[8769,8773],{"title":8088,"content":8770,"codePy":8771,"codeCpp":8772},"如“从起点收集所有钥匙到达终点”，状态=(x,y,钥匙集合)；DFS 在状态图上探索，用 visited[x][y][mask] 去重。","def dfs(x,y,mask):\n    if (x,y,mask) in vis: return\n    vis.add((x,y,mask))\n    if is_goal(x,y,mask): record(); return\n    for nx,ny in nxt(x,y):\n        nm = mask | key_at(nx,ny)\n        dfs(nx,ny,nm)","\u002F\u002F 状态=(位置,已得钥匙)",{"title":8774,"content":8775,"codePy":8776,"codeCpp":8777,"tip":8778},"2. 状态压缩","当维度小（如钥匙≤16 种）时用位掩码 mask 表示集合，便于存储与判等。","print(\"小集合用位掩码\")","\u002F\u002F mask 第 k 位=是否拥有第 k 把钥匙","tip: 状态搜索一定要 visited，否则指数爆炸或死循环。",[8780],{"title":8781,"prompt":8782,"analysis":8783,"codePy":8784,"codeCpp":8785,"run":8786},"例 1：状态数","4 把钥匙，状态 mask 共几种？","2^4=16 种钥匙集合。","print(16)","\u002F\u002F 16","16",{"choice":8788,"coding":8807},[8789,8796,8802],{"id":38,"stem":8790,"options":8791,"answer":9,"explanation":8795},"状态搜索把什么当节点？",[8792,8793,8794,7965],"边","状态(位置+集合)","权值","隐式图节点=状态。",{"id":47,"stem":8797,"options":8798,"answer":9,"explanation":8801},"为何要 visited？",[8799,8800,3117,4166],"美观","避免重复\u002F环","防止指数爆炸。",{"id":56,"stem":8803,"options":8804,"answer":9,"explanation":8806},"小集合常用？",[1434,8805,3259,6565],"位掩码","mask 位表示集合。",[8808],{"id":67,"title":8809,"prompt":8810,"inputFormat":8811,"outputFormat":8127,"samples":8812,"starterPy":8815,"starterCpp":8816,"hint":8817,"referencePy":8818,"referenceCpp":8816},"收集钥匙到终点","h×w 网格，S 起点、E 终点、. 空地、# 墙、数字 1..4 表示钥匙。求从 S 拿到全部 4 把钥匙并到达 E 的最少步数（不能穿墙）。","第一行 h w；接下来 h 行网格。",[8813],{"input":8814,"output":1567},"3 4\nS.1.\n###.\n2..E","from collections import deque\nh,w=map(int,input().split()); g=[input() for _ in range(h)]\nimport sys\nsys.setrecursionlimit(100000)\nvis=[[[False]*16 for _ in range(w)] for _ in range(h)]\nq=deque(); si=sj=None\nfor i in range(h):\n    for j in range(w):\n        if g[i][j]==\"S\": si,sj=i,j\nq.append((si,sj,0,0)); vis[si][sj][0]=True\nwhile q:\n    i,j,m,d=q.popleft()\n    if g[i][j]==\"E\" and m==15: print(d); break\n    for di,dj in ((1,0),(-1,0),(0,1),(0,-1)):\n        ni,nj=i+di,j+dj\n        if 0\u003C=ni\u003Ch and 0\u003C=nj\u003Cw and g[ni][nj]!=\"#\":\n            nm=m\n            if g[ni][nj].isdigit(): nm|=1\u003C\u003C(int(g[ni][nj])-1)\n            if not vis[ni][nj][nm]: vis[ni][nj][nm]=True; q.append((ni,nj,nm,d+1))\nelse: print(-1)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Cstring>\nusing namespace std;\nint h,w; vector\u003Cstring> g; bool vis[12][12][16];\nint main(){ cin>>h>>w; g.resize(h); for(int i=0;i\u003Ch;i++) cin>>g[i]; int si=-1,sj=-1; for(int i=0;i\u003Ch;i++) for(int j=0;j\u003Cw;j++) if(g[i][j]==\"S\") si=i,j=j; queue\u003Cpair\u003Cpair\u003Cint,int>,pair\u003Cint,int>>> q; q.push({{{si,sj}},{0,0}}); vis[si][sj][0]=1; int ans=-1; while(!q.empty()){ auto p=q.front(); q.pop(); int i=p.first.first,j=p.first.second,m=p.second.first,d=p.second.second; if(g[i][j]==\"E\"&&m==15){ ans=d; break; } int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; for(int k=0;k\u003C4;k++){ int ni=i+dir[k][0],nj=j+dir[k][1]; if(ni>=0&&ni\u003Ch&&nj>=0&&nj\u003Cw&&g[ni][nj]!=\"#\"){ int nm=m; if(g[ni][nj]>='1'&&g[ni][nj]\u003C='4') nm|=1\u003C\u003C(g[ni][nj]-'1'); if(!vis[ni][nj][nm]){ vis[ni][nj][nm]=1; q.push({{{ni,nj}},{nm,d+1}}); } } } } cout\u003C\u003Cans; }","状态=(i,j,mask)，BFS 求最短路。","from collections import deque\nh,w=map(int,input().split()); g=[input() for _ in range(h)]\nimport sys\nsys.setrecursionlimit(100000)\nvis=[[[False]*16 for _ in range(w)] for _ in range(h)]\nq=deque(); si=sj=None\nfor i in range(h):\n    for j in range(w):\n        if g[i][j]==\"S\": si,sj=i,j\nq.append((si,sj,0,0)); vis[si][sj][0]=True\nwhile q:\n    i,j,m,d=q.popleft()\n    if g[i][j]==\"E\" and m==15: print(d); break\n    for di,dj in ((1,0),(-1,0),(0,1),(0,-1)):\n        ni,nj=i+di,j+dj\n        if 0\u003C=ni\u003Ch and 0\u003C=nj\u003Cw and g[ni][nj]!=\"#\":\n            nm=m\n            if g[ni][nj].isdigit(): nm|=1\u003C\u003C(int(g[ni][nj])-1)\n            if not vis[ni][nj][nm]: vis[ni][nj][nm]=True; q.append((ni,nj,nm,d+1))\nelse: print(-1)","状态搜索把“位置+已收集物（等）”组成的状态视为隐式图节点，用 DFS\u002FBFS 探索并用 visited（常配合位掩码）去重，避免重复与环；是许多迷宫\u002F收集类问题的核心。",{"id":7386,"title":1434,"level":2505,"module":8821,"seq":9,"goal":8822,"keywords":8823,"explanation":8825,"examples":8837,"exercises":8844,"summary":8873,"next":8874,"nextTitle":8875},"数据结构","掌握数组：连续存储、随机访问 O(1)、按下标读写，是其他数据结构的基础。",[1434,8824,3974,3586],"随机访问",[8826,8831],{"title":8827,"content":8828,"codePy":8829,"codeCpp":8830},"1. 特性","元素在内存中连续存放，按下标 a[i] 直接寻址，访问 O(1)；长度固定（静态）或动态。","a = [10, 20, 30]\nprint(a[1])   # O(1) 随机访问","int a[3] = {10,20,30};\ncout \u003C\u003C a[1];  \u002F\u002F O(1)",{"title":8832,"content":8833,"codePy":8834,"codeCpp":8835,"tip":8836},"2. 操作代价","按下标读写 O(1)；在中间插入\u002F删除需搬移元素 O(n)。","# 中间插入要挪动后续元素 O(n)","\u002F\u002F 插入\u002F删除中间 O(n)","需要频繁中间插入考虑用链表。",[8838],{"title":8839,"prompt":8840,"analysis":8841,"codePy":8842,"codeCpp":8843,"run":892},"例 1：随机访问","a=[1,2,3]，访问 a[0] 与在头部插入的代价？","a[0] 是 O(1)；头部插入需整体后移 O(n)。","print(a[0])","\u002F\u002F O(1)",{"choice":8845,"coding":8861},[8846,8850,8854],{"id":38,"stem":8847,"options":8848,"answer":9,"explanation":8849},"数组按下标访问的复杂度？",[2338,1245,1246,2339],"连续存储直接寻址。",{"id":47,"stem":8851,"options":8852,"answer":9,"explanation":8853},"在数组头部插入的代价？",[1245,2338,1246,2339],"需搬移后续元素。",{"id":56,"stem":8855,"options":8856,"answer":9,"explanation":8860},"数组最适合？",[8857,8824,8858,8859],"频繁头部插入","频繁中间删除","未知长度递归","随机访问 O(1)。",[8862],{"id":67,"title":8863,"prompt":8864,"inputFormat":8865,"outputFormat":6275,"samples":8866,"starterPy":8870,"starterCpp":8871,"hint":8872,"referencePy":8870,"referenceCpp":8871},"插入到位置 i","输入 n（1≤n≤100000）和 n 个整数、下标 i（0≤i≤n）与值 v，把 v 插入位置 i（其后元素后移）并输出最终数组（空格分隔）。","第一行 n；第二行 n 个整数；第三行 i v。",[8867],{"input":8868,"output":8869},"4\n1 2 3 4\n2 9","1 2 9 3 4","n = int(input())\na = list(map(int, input().split()))\ni, v = map(int, input().split())\na.insert(i, v)\nprint(*a)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> a;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ int x; cin>>x; a.push_back(x); } int i,v; cin>>i>>v; a.insert(a.begin()+i, v); for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","位置 i 后插入：把 v 放在下标 i，后续后移。","数组连续存储、随机访问 O(1)；按下标读写快，但中间插入\u002F删除需 O(n) 搬移。","11线性结构-A1-2","vector",{"id":8874,"title":8875,"level":2505,"module":8821,"seq":63,"goal":8877,"keywords":8878,"explanation":8882,"examples":8894,"exercises":8901,"summary":8924,"next":8925,"nextTitle":8926},"掌握 C++ vector（动态数组）：自动扩容、尾部增删 O(1)、随机访问，是可变长数组。",[8875,8879,8880,8881],"动态数组","push_back","扩容",[8883,8888],{"title":8884,"content":8885,"codePy":8886,"codeCpp":8887},"1. 基本","vector\u003Cint> v; v.push_back(x) 尾部加（均摊 O(1)）；v[i] 随机访问；v.size() 长度。","# Python list 即动态数组\na = []\na.append(1)   # O(1) amortized","vector\u003Cint> v;\nv.push_back(1);   \u002F\u002F 尾部 O(1)\ncout \u003C\u003C v[0];",{"title":8889,"content":8890,"codePy":8891,"codeCpp":8892,"tip":8893},"2. 扩容","空间满时按约 2 倍重新分配并拷贝，单次可能 O(n) 但均摊 O(1)；中间插入 O(n)。","# 动态数组满了就翻倍重分配","\u002F\u002F 2 倍扩容，均摊 O(1)","预估大小可用 reserve 减少重分配。",[8895],{"title":8896,"prompt":8897,"analysis":8898,"codePy":8899,"codeCpp":8900,"run":8879},"例 1：动态数组","用动态数组读 n 个数并倒序输出。","尾部追加 O(1) 均摊。","a = []\nfor _ in range(n): a.append(int(input()))\nprint(*reversed(a))","\u002F\u002F 尾部加 + 倒序",{"choice":8902,"coding":8915},[8903,8908,8912],{"id":38,"stem":8904,"options":8905,"answer":9,"explanation":8907},"vector 尾部 push_back 的均摊复杂度？",[2338,8906,1246,2339],"O(1) 均摊","翻倍扩容均摊 O(1)。",{"id":47,"stem":8909,"options":8910,"answer":607,"explanation":8911},"vector 随机访问？",[1245,2338,1246,2339],"下标直接访问。",{"id":56,"stem":8913,"options":8914,"answer":9,"explanation":8853},"vector 中间插入的复杂度？",[1245,2338,1246,2339],[8916],{"id":67,"title":8917,"prompt":8918,"inputFormat":1801,"outputFormat":5404,"samples":8919,"starterPy":8921,"starterCpp":8922,"hint":8923,"referencePy":8921,"referenceCpp":8922},"动态数组读写","输入 n（1≤n≤100000）和 n 个数，用动态数组存并按输入顺序输出。",[8920],{"input":2901,"output":2212},"n = int(input())\na = list(map(int, input().split()))\nprint(*a)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> a;\nint main(){ int n,x; cin>>n; while(n--){ cin>>x; a.push_back(x); } for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","读入 push_back，再遍历输出。","vector 是动态数组，push_back 均摊 O(1)、随机访问 O(1)、自动扩容（约 2 倍）；中间插入 O(n)。","11线性结构-A1-3","stack",{"id":8925,"title":8926,"level":2505,"module":8821,"seq":703,"goal":8928,"keywords":8929,"explanation":8934,"examples":8945,"exercises":8952,"summary":8983,"next":8984,"nextTitle":8985},"掌握栈：后进先出（LIFO），支持压栈\u002F弹栈\u002F取顶，常用于括号匹配、DFS。",[6565,8930,8931,8932,8933],"LIFO","push","pop","括号匹配",[8935,8940],{"title":8936,"content":8937,"codePy":8938,"codeCpp":8939},"1. 操作","push 入栈、pop 出栈、top 看顶（不出）、empty 判空；后进先出。","st = []\nst.append(1); st.append(2)\nprint(st.pop())   # 2（最后进）","stack\u003Cint> s;\ns.push(1); s.push(2);\ncout \u003C\u003C s.top(); s.pop();",{"title":4421,"content":8941,"codePy":8942,"codeCpp":8943,"tip":8944},"括号匹配——遇左括号入栈，遇右括号看栈顶是否匹配；结束栈应空。","for ch in s:\n    if ch in \"({[\": st.append(ch)\n    else:\n        if not st or not match(st.pop(), ch): return False","\u002F\u002F 括号匹配用栈","栈是 DFS 与回溯的底层结构（递归隐式用栈）。",[8946],{"title":8947,"prompt":8948,"analysis":8949,"codePy":8950,"codeCpp":8951,"run":8418},"例 1：括号匹配","\"({})\" 是否匹配？","依次 ( { } )，栈最终为空，匹配。","print(\"YES\")","\u002F\u002F YES",{"choice":8953,"coding":8970},[8954,8960,8964],{"id":38,"stem":8955,"options":8956,"answer":9,"explanation":8959},"栈的特点是？",[8957,8958,1568,5772],"先进先出","后进先出 LIFO","后入先出。",{"id":47,"stem":8961,"options":8962,"answer":9,"explanation":8963},"括号匹配用？",[4617,6565,1434,3259],"最近未匹配的左括号。",{"id":56,"stem":8965,"options":8966,"answer":9,"explanation":8969},"栈顶操作是？",[8932,8967,8931,8968],"top（不出）","反转","top 看顶不删除。",[8971],{"id":67,"title":8933,"prompt":8972,"inputFormat":2004,"outputFormat":8414,"samples":8973,"starterPy":8980,"starterCpp":8981,"hint":8982,"referencePy":8980,"referenceCpp":8981},"输入一个括号串（只含 ()[]{}），输出是否匹配（YES\u002FNO）。",[8974,8976,8978],{"input":8975,"output":8418},"({})",{"input":8977,"output":8418},"[()]",{"input":8979,"output":1943},"([)]","s = input().strip()\nstk = []\nmp = {\")\":\"(\", \"]\":\"[\", \"}\":\"{\"}\nok = True\nfor ch in s:\n    if ch==\"(\" or ch==\"[\" or ch==\"{\": stk.append(ch)\n    else:\n        if not stk or stk.pop() != mp[ch]: ok = False; break\nprint(\"YES\" if ok and not stk else \"NO\")","#include \u003Ciostream>\n#include \u003Cstack>\n#include \u003Cstring>\n#include \u003Cmap>\nusing namespace std;\nint main(){ string s; cin>>s; stack\u003Cchar> st; map\u003Cchar,char> mp={{\")\",\"(\"},{\"]\",\"[\"},{\"}\",\"{\"}}; for(char c:s){ if(c==\"(\"||c==\"[\"||c==\"{\") st.push(c); else { if(st.empty()||st.top()!=mp[c]){ cout\u003C\u003C\"NO\"; return 0; } st.pop(); } } cout\u003C\u003C(st.empty()?\"YES\":\"NO\"); }","遇左入栈，遇右比顶并弹出；结束栈空且全程匹配。","栈 LIFO，支持 push\u002Fpop\u002Ftop；常用于括号匹配、DFS、撤销；括号匹配遇左入栈、遇右比顶。","11线性结构-A1-4","queue",{"id":8984,"title":8985,"level":2505,"module":8821,"seq":770,"goal":8987,"keywords":8988,"explanation":8990,"examples":9000,"exercises":9005,"summary":9035,"next":9036,"nextTitle":9037},"掌握队列：先进先出（FIFO），支持入队\u002F出队\u002F看队首，常用于 BFS、缓冲。",[4617,8989,7747,8957],"FIFO",[8991,8995],{"title":8936,"content":8992,"codePy":8993,"codeCpp":8994},"push 入队尾、pop 出队首、front 看首；先进先出。","from collections import deque\nq = deque(); q.append(1); q.append(2)\nprint(q.popleft())   # 1（最先进）","queue\u003Cint> q;\nq.push(1); q.push(2);\ncout \u003C\u003C q.front(); q.pop();",{"title":4421,"content":8996,"codePy":8997,"codeCpp":8998,"tip":8999},"BFS 用队列按层扩展，保证\"先发现的先扩展\"（最短路径）。","while q:\n    u = q.popleft()\n    for v in g[u]: q.append(v)","\u002F\u002F BFS 用队列","BFS 与队列、DFS 与栈是经典搭配。",[9001],{"title":9002,"prompt":9003,"analysis":9004,"codePy":7878,"codeCpp":7879,"run":892},"例 1：FIFO","依次入队 1,2，出队首？","先入的 1 先出。",{"choice":9006,"coding":9023},[9007,9013,9017],{"id":38,"stem":9008,"options":9009,"answer":9,"explanation":9012},"队列的特点是？",[9010,9011,1568,5772],"后进先出","先进先出 FIFO","先入先出。",{"id":47,"stem":9014,"options":9015,"answer":9,"explanation":9016},"BFS 用？",[6565,4617,1434,3259],"按层扩展。",{"id":56,"stem":9018,"options":9019,"answer":9,"explanation":9022},"队列出队是从？",[9020,9021,8470,1568],"队尾","队首","队首出。",[9024],{"id":67,"title":9025,"prompt":9026,"inputFormat":9027,"outputFormat":9028,"samples":9029,"starterPy":9032,"starterCpp":9033,"hint":9034,"referencePy":9032,"referenceCpp":9033},"BFS 层次遍历","输入 n 个节点（编号 1..n）的树（n-1 条边）和根，用队列 BFS 输出层次遍历（节点空格分隔）。","第一行 n；其后 n-1 行每行 u v（无向边）；最后一行根。","一行节点编号，空格分隔。",[9030],{"input":9031,"output":2212},"4\n1 2\n1 3\n2 4\n1","import sys\nfrom collections import deque\nn = int(input())\ng = [[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u, v = map(int, input().split()); g[u].append(v); g[v].append(u)\nroot = int(input())\nvis = [False]*(n+1); vis[root]=True\nq = deque([root]); out = []\nwhile q:\n    u = q.popleft(); out.append(u)\n    for v in g[u]:\n        if not vis[v]: vis[v]=True; q.append(v)\nprint(*out)","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[1005];\nint main(){ int n; cin>>n; for(int i=1;i\u003Cn;i++){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } int root; cin>>root; vector\u003Cbool> vis(n+1,false); vis[root]=true; queue\u003Cint> q; q.push(root); while(!q.empty()){ int u=q.front(); q.pop(); cout\u003C\u003Cu\u003C\u003C\" \"; for(int v:g[u]) if(!vis[v]){ vis[v]=true; q.push(v); } } }","队列 BFS：出队一个就把未访问邻居入队。","队列 FIFO，支持入队\u002F出队\u002F看首；常用于 BFS、缓冲区；BFS 用队列保证按层扩展。","11线性结构-A1-5","deque",{"id":9036,"title":9037,"level":2505,"module":8821,"seq":838,"goal":9039,"keywords":9040,"explanation":9043,"examples":9053,"exercises":9061,"summary":9092,"next":9093,"nextTitle":9094},"掌握双端队列 deque：两端都能 O(1) 增删，兼具栈与队列功能，常用于滑动窗口。",[9037,9041,6465,9042],"双端","两端",[9044,9048],{"title":8936,"content":9045,"codePy":9046,"codeCpp":9047},"两端 append\u002Fpop（左\u002F右）都是 O(1)；比用 list 在头部 pop（O(n)）高效。","from collections import deque\nd = deque([1,2])\nd.append(3); d.appendleft(0)\nprint(d.pop(), d.popleft())","deque\u003Cint> d;\nd.push_back(3); d.push_front(0);",{"title":4421,"content":9049,"codePy":9050,"codeCpp":9051,"tip":9052},"单调队列\u002F滑动窗口最大值，在两端维护候选。","# 滑动窗口：两端操作 O(1)","\u002F\u002F 双端队列维护窗口","需要两端操作优先用 deque 而非 list。",[9054],{"title":9055,"prompt":9056,"analysis":9057,"codePy":9058,"codeCpp":9059,"run":9060},"例 1：双端操作","deque([1,2]) 右压 3、左压 0 后？","[0,1,2,3]。","print(deque([0,1,2,3]))","\u002F\u002F 0 1 2 3","[0, 1, 2, 3]",{"choice":9062,"coding":9079},[9063,9067,9072],{"id":38,"stem":9064,"options":9065,"answer":9,"explanation":9066},"deque 两端操作的复杂度？",[2338,1245,1246,2339],"双端 O(1)。",{"id":47,"stem":9068,"options":9069,"answer":9,"explanation":9071},"deque 常用于？",[3117,9070,2509,3259],"滑动窗口\u002F单调队列","两端维护候选。",{"id":56,"stem":9073,"options":9074,"answer":9,"explanation":9078},"Python 在头部频繁增删应改用？",[9075,9037,9076,9077],"list","tuple","set","deque 头部 O(1)。",[9080],{"id":67,"title":9081,"prompt":9082,"inputFormat":9083,"outputFormat":9084,"samples":9085,"starterPy":9089,"starterCpp":9090,"hint":9091,"referencePy":9089,"referenceCpp":9090},"滑动窗口最大值","输入 n（1≤n≤100000）、数组和窗口大小 k，用 deque 输出每个窗口的最大值（共 n-k+1 个，空格分隔）。","第一行 n k；第二行 n 个整数。","一行 n-k+1 个整数。",[9086],{"input":9087,"output":9088},"8 3\n1 3 -1 -3 5 3 6 7","3 3 5 5 6 7","from collections import deque\nn, k = map(int, input().split())\na = list(map(int, input().split()))\ndq = deque()\nout = []\nfor i in range(n):\n    while dq and a[dq[-1]] \u003C= a[i]: dq.pop()\n    dq.append(i)\n    if dq[0] \u003C= i - k: dq.popleft()\n    if i >= k - 1: out.append(str(a[dq[0]]))\nprint(*out)","#include \u003Ciostream>\n#include \u003Cdeque>\nusing namespace std;\nint a[100005];\nint main(){ int n,k; cin>>n>>k; for(int i=0;i\u003Cn;i++) cin>>a[i]; deque\u003Cint> dq; for(int i=0;i\u003Cn;i++){ while(!dq.empty() && a[dq.back()]\u003C=a[i]) dq.pop_back(); dq.push_back(i); if(dq.front()\u003C=i-k) dq.pop_front(); if(i>=k-1) cout\u003C\u003Ca[dq.front()]\u003C\u003C\" \"; } }","维护递减 deque：尾部弹出小的，头部超窗口弹出，队首即窗口最大。","deque 两端增删均 O(1)，兼具栈与队列；常用于滑动窗口、单调队列。","12-A1-1","单链表",{"id":7980,"title":7981,"level":6178,"module":9096,"seq":9,"goal":9097,"keywords":9098,"explanation":9100,"examples":9112,"exercises":9117,"summary":9149,"next":9150,"nextTitle":9151},"线性结构进阶（栈\u002F队列\u002F双端队列）","巩固栈的“后进先出”：用栈判断括号是否匹配，并理解中缀转后缀后用栈求值。",[6565,8930,8933,9099],"表达式求值",[9101,9106],{"title":9102,"content":9103,"codePy":9104,"codeCpp":9105},"1. 括号匹配","遇左括号入栈，遇右括号弹出栈顶看是否配对；结束栈空则匹配。","def match(s):\n    st=[]\n    for ch in s:\n        if ch in \"({[\": st.append(ch)\n        elif ch in \")}]\":\n            if not st or not pair(st.pop(), ch): return False\n    return not st","\u002F\u002F 左括号入栈，右括号弹栈配对",{"title":9107,"content":9108,"codePy":9109,"codeCpp":9110,"tip":9111},"2. 表达式求值","把中缀转后缀（用栈处理运算符优先级），再扫描后缀用栈计算。","print(\"栈天然适合嵌套结构\")","\u002F\u002F 后缀求值：数入栈，运算符取栈顶两数","tip: 所有“就近配对”问题都用栈。",[9113],{"title":9114,"prompt":9115,"analysis":9116,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：匹配","\"([])\" 是否匹配？","配对正确，匹配。",{"choice":9118,"coding":9137},[9119,9123,9130],{"id":38,"stem":9120,"options":9121,"answer":9,"explanation":9122},"括号匹配用什么？",[4617,6565,1434,6210],"就近配对 LIFO。",{"id":47,"stem":9124,"options":9125,"answer":9,"explanation":9129},"结束栈空说明？",[9126,9127,1818,9128],"错误","全部匹配","未开始","左右配对完。",{"id":56,"stem":9131,"options":9132,"answer":9,"explanation":9136},"后缀求值遇运算符",[9133,9134,3560,9135],"入栈","取栈顶两运算","入队","弹出两操作数计算。",[9138],{"id":67,"title":8933,"prompt":9139,"inputFormat":2004,"outputFormat":9140,"samples":9141,"starterPy":9144,"starterCpp":9145,"hint":9146,"referencePy":9147,"referenceCpp":9148},"判断括号串是否合法（仅含 ()[]{}）。","一行 YES\u002FNO。",[9142],{"input":9143,"output":8418},"([])","s=input().strip()\nstk=[]\np={\")\":\"(\",\"]\":\"[\",\"}\":\"{\"}\nfor ch in s:\n    if ch in \"([{\": stk.append(ch)\n    elif ch in p:\n        if not stk or stk.pop()!=p[ch]: print(\"NO\"); raise SystemExit\nprint(\"NO\" if stk else \"YES\")\n","#include \u003Ciostream>\n#include \u003Cstack>\n#include \u003Cstring>\nusing namespace std;\nint main(){ string s; cin>>s; stack\u003Cchar> st; for(char c:s){ if(c=='('||c=='['||c=='{') st.push(c); else { if(st.empty()){cout\u003C\u003C\"NO\";return 0;} char t=st.top(); st.pop(); if((c==')&&'(')||(c==']'&&'[')||(c=='}'&&'{')){} else {cout\u003C\u003C\"NO\";return 0;} } } cout\u003C\u003C(st.empty()?\"YES\":\"NO\"); }","右括号弹栈比对。","s=input().strip(); stk=[]; p={\")\":\"(\",\"]\":\"[\",\"}\":\"{\"}\nok=True\nfor ch in s:\n    if ch in \"([{\": stk.append(ch)\n    elif ch in p:\n        if not stk or stk.pop()!=p[ch]: ok=False; break\nprint(\"YES\" if ok and not stk else \"NO\")","#include \u003Ciostream>\n#include \u003Cstack>\n#include \u003Cstring>\nusing namespace std;\nint main(){ string s; cin>>s; stack\u003Cchar> st; for(char c:s){ if(c=='('||c=='['||c=='{') st.push(c); else { if(st.empty()){cout\u003C\u003C\"NO\";return 0;} char t=st.top(); st.pop(); if((c==')'&&t!='(')||(c==']'&&t!='[')||(c=='}'&&t!='{')){cout\u003C\u003C\"NO\";return 0;} } } cout\u003C\u003C(st.empty()?\"YES\":\"NO\"); }","栈的“后进先出”适合一切就近配对：括号匹配遇左入栈、遇右弹栈配对；表达式求值可经后缀+栈完成。","11线性结构-A2-2","循环队列",{"id":9150,"title":9151,"level":6178,"module":9096,"seq":63,"goal":9153,"keywords":9154,"explanation":9157,"examples":9168,"exercises":9173,"summary":9207,"next":9208,"nextTitle":9209},"理解循环队列：用定长数组 + 头尾指针（取模）实现 O(1) 入队出队，区分“满”与“空”。",[9151,843,9155,9156],"head\u002Ftail","假溢出",[9158,9163],{"title":9159,"content":9160,"codePy":9161,"codeCpp":9162},"1. 为什么循环","普通队列出队后前部空间浪费（假溢出）；用 (tail+1)%N 回卷，空间复用。","class CQ:\n    def __init__(self,n): self.a=[0]*n; self.h=0; self.t=0; self.n=n\n    def push(self,x): self.a[self.t]=x; self.t=(self.t+1)%self.n\n    def pop(self): v=self.a[self.h]; self.h=(self.h+1)%self.n; return v","\u002F\u002F 用 (tail+1)%N 回卷",{"title":9164,"content":9165,"codePy":9166,"codeCpp":9167},"2. 判空\u002F判满","常用“空一格”约定：满当 (tail+1)%N==h，空当 t==h；或另记 size。","print(\"留一格区分满\u002F空，或记元素个数\")","\u002F\u002F 也可 size 计数避免歧义",[9169],{"title":9170,"prompt":9171,"analysis":9172,"codePy":5966,"codeCpp":4490,"run":1663},"例 1：容量","容量 5 的循环队列最多存几个元素（留空约定）？","最多 4 个。",{"choice":9174,"coding":9194},[9175,9180,9188],{"id":38,"stem":9176,"options":9177,"answer":9,"explanation":9179},"循环队列解决？",[9178,9156,3117,4624],"慢","回卷复用空间。",{"id":47,"stem":9181,"options":9182,"answer":9,"explanation":9187},"判满常用？",[9183,9184,9185,9186],"h==t","(t+1)%N==h","空","size=0","留一格约定。",{"id":56,"stem":9189,"options":9190,"answer":9,"explanation":9193},"入队 tail 如何移动？",[9191,9192,1567,1307],"+1","(t+1)%N","取模回卷。",[9195],{"id":67,"title":9151,"prompt":9196,"inputFormat":9197,"outputFormat":9198,"samples":9199,"starterPy":9203,"starterCpp":9204,"hint":9205,"referencePy":9206,"referenceCpp":9204},"实现容量 n 的循环队列：支持 push x（若满忽略\u002F报满）、pop（若空报空），最后输出从队首到队尾元素。","第一行 n；第二行若干操作（p x 入队 \u002F o 出队）。","一行当前队列元素，空格分隔。",[9200],{"input":9201,"output":9202},"3\np 1\np 2\no\np 3","2 3","n=int(input()); a=[0]*n; h=t=0; sz=0\nfor _ in range(4):\n    op=input().split()\n    if op[0]==\"p\":\n        if sz\u003Cn: a[t]=int(op[1]); t=(t+1)%n; sz+=1\n    else:\n        if sz>0: h=(h+1)%n; sz-=1\nres=[]\nfor i in range(sz): res.append(a[(h+i)%n])\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n); int h=0,t=0,sz=0; string op; for(int i=0;i\u003C4;i++){ cin>>op; if(op==\"p\"){ int x; cin>>x; if(sz\u003Cn){ a[t]=x; t=(t+1)%n; sz++; } } else if(sz>0){ h=(h+1)%n; sz--; } } for(int i=0;i\u003Csz;i++) cout\u003C\u003Ca[(h+i)%n]\u003C\u003C' '; }","用 sz 记元素个数最省心。","n=int(input()); a=[0]*n; h=t=0; sz=0\nfor _ in range(4):\n    op=input().split()\n    if op[0]==\"p\":\n        if sz\u003Cn: a[t]=int(op[1]); t=(t+1)%n; sz+=1\n    else:\n        if sz>0: h=(h+1)%n; sz-=1\nres=[]\nfor i in range(sz): res.append(a[(h+i)%n])\nprint(*res)","循环队列用定长数组与头尾指针取模回卷，避免假溢出；用“留一格”或记录元素个数来区分满与空。","11线性结构-A2-3","双端队列 deque",{"id":9208,"title":9209,"level":6178,"module":9096,"seq":703,"goal":9211,"keywords":9212,"explanation":9214,"examples":9225,"exercises":9232,"summary":9260,"next":9261,"nextTitle":9262},"掌握双端队列：两端都可 O(1) 入队\u002F出队，用于滑动窗口、单调队列等进阶场景。",[9037,9041,9213],"两端操作",[9215,9220],{"title":9216,"content":9217,"codePy":9218,"codeCpp":9219},"1. 两端操作","deque 支持 appendleft\u002Fpopleft\u002Fappend\u002Fpop，均为 O(1)；比手写数组方便。","from collections import deque\ndq=deque()\ndq.append(1); dq.appendleft(0); dq.pop(); dq.popleft()","\u002F\u002F 头尾都能 O(1) 增删",{"title":9221,"content":9222,"codePy":9223,"codeCpp":9224},"2. 典型用途","滑动窗口、BFS 优化、回文判断、单调队列底层都依赖 deque。","print(\"窗口两端进出用 deque\")","\u002F\u002F 注意：中间删除不是 O(1)",[9226],{"title":9227,"prompt":9228,"analysis":9229,"codePy":9230,"codeCpp":9231,"run":891},"例 1：两端","依次 append(1),appendleft(0)，队首是？","0 在队首。","print(0)","\u002F\u002F 0",{"choice":9233,"coding":9249},[9234,9241,9245],{"id":38,"stem":9235,"options":9236,"answer":9,"explanation":9240},"deque 特点是？",[9237,9238,3117,9239],"仅尾部","两端 O(1)","定长","头尾都快。",{"id":47,"stem":9242,"options":9243,"answer":9,"explanation":9244},"滑动窗口常用？",[1434,9037,6210,6565],"两端进出。",{"id":56,"stem":9246,"options":9247,"answer":9,"explanation":9248},"deque 中间删除复杂度？",[1245,2338,1246,2339],"需要移动。",[9250],{"id":67,"title":9251,"prompt":9252,"inputFormat":2004,"outputFormat":9140,"samples":9253,"starterPy":9256,"starterCpp":9257,"hint":9258,"referencePy":9259,"referenceCpp":9257},"回文判断","判断字符串是否为回文（可用 deque）。",[9254],{"input":9255,"output":8418},"abba","from collections import deque\ns=input().strip(); dq=deque(s)\nok=True\nwhile len(dq)>1:\n    if dq.popleft()!=dq.pop(): ok=False; break\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){ string s; cin>>s; int i=0,j=s.size()-1; bool ok=1; while(i\u003Cj){ if(s[i]!=s[j]){ok=0;break;} i++;j--; } cout\u003C\u003C(ok?\"YES\":\"NO\"); }","两端向中间比较。","from collections import deque\ns=input().strip(); dq=deque(s)\nok=True\nwhile len(dq)>1:\n    if dq.popleft()!=dq.pop(): ok=False; break\nprint(\"YES\" if ok else \"NO\")","双端队列 deque 头尾均可 O(1) 增删，是滑动窗口、单调队列、回文判断等问题的理想底层结构；注意中间删除不是 O(1)。","11线性结构-A2-4","单调栈",{"id":9261,"title":9262,"level":6178,"module":9096,"seq":770,"goal":9264,"keywords":9265,"explanation":9269,"examples":9279,"exercises":9287,"summary":9313,"next":9314,"nextTitle":9315},"掌握单调栈：维护栈内元素单调递增\u002F减，在线性时间内求“左边\u002F右边第一个更大（更小）元素”。",[9262,9266,9267,9268],"单调递增","下一个更大","Next Greater",[9270,9275],{"title":9271,"content":9272,"codePy":9273,"codeCpp":9274},"1. 求下一个更大","栈维持递减；遍历时若当前元素比栈顶大，则栈顶的“下一个更大”就是它，弹出并记录。","def next_greater(a):\n    res=[-1]*len(a); st=[]\n    for i,x in enumerate(a):\n        while st and a[st[-1]]\u003Cx:\n            res[st.pop()]=x\n        st.append(i)\n    return res","\u002F\u002F 栈递减；遇更大则结算栈顶",{"title":4421,"content":9276,"codePy":9277,"codeCpp":9278},"每日温度、柱状图最大矩形、接雨水等都可转化为“找左右边界”。","print(\"单调栈=在线性时间找最近的大\u002F小\")","\u002F\u002F 单调递增用于找更小",[9280],{"title":9281,"prompt":9282,"analysis":9283,"codePy":9284,"codeCpp":9285,"run":9286},"例 1：下一个更大","[2,1,3] 每个的下一个更大？","2→3,1→3,3→-1。","print([3,3,-1])","\u002F\u002F 3 3 -1","3 3 -1",{"choice":9288,"coding":9303},[9289,9295,9299],{"id":38,"stem":9290,"options":9291,"answer":607,"explanation":9294},"求下一个更大用？",[9292,9293,4617,6210],"递减栈","递增栈","栈顶小遇大结算。",{"id":47,"stem":9296,"options":9297,"answer":9,"explanation":9298},"[2,1,3] 的 2 下一个更大是？",[892,890,1567,759],"3。",{"id":56,"stem":9300,"options":9301,"answer":9,"explanation":9302},"单调栈复杂度？",[2339,2338,2564,1245],"每个元素进出栈一次。",[9304],{"id":67,"title":9267,"prompt":9305,"inputFormat":1801,"outputFormat":5404,"samples":9306,"starterPy":9309,"starterCpp":9310,"hint":9311,"referencePy":9312,"referenceCpp":9310},"给定 n 个整数，输出每个数右边第一个比它大的数（无则 -1）。",[9307],{"input":9308,"output":9286},"3\n2 1 3","n=int(input()); a=list(map(int,input().split())); res=[-1]*n; st=[]\nfor i,x in enumerate(a):\n    while st and a[st[-1]]\u003Cx:\n        res[st.pop()]=x\n    st.append(i)\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstack>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n),res(n,-1); stack\u003Cint> st; for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int i=0;i\u003Cn;i++){ while(!st.empty()&&a[st.top()]\u003Ca[i]){ res[st.top()]=a[i]; st.pop(); } st.push(i); } for(int x:res) cout\u003C\u003Cx\u003C\u003C' '; }","递减栈，遇大结算栈顶。","n=int(input()); a=list(map(int,input().split())); res=[-1]*n; st=[]\nfor i,x in enumerate(a):\n    while st and a[st[-1]]\u003Cx:\n        res[st.pop()]=x\n    st.append(i)\nprint(*res)","单调栈在线性时间内求每个元素“左边\u002F右边第一个更大（更小）”：维持单调，遇破坏单调的元素时结算栈顶；典型用于每日温度、柱状图最大矩形。","11线性结构-A2-5","单调队列与滑动窗口最值",{"id":9314,"title":9315,"level":6178,"module":9096,"seq":838,"goal":9317,"keywords":9318,"explanation":9321,"examples":9331,"exercises":9339,"summary":9369,"next":9370,"nextTitle":9371},"掌握单调队列：用 deque 维护窗口内元素的单调位置，O(n) 求滑动窗口最大值\u002F最小值。",[9319,6465,9320,9037],"单调队列","窗口最值",[9322,9327],{"title":9323,"content":9324,"codePy":9325,"codeCpp":9326},"1. 窗口最大值","deque 存下标，维持对应值递减；新元素入队前弹出队尾比它小的，保证队首为窗口最大；队首超出窗口则弹出。","def slide_max(a,k):\n    from collections import deque\n    dq=deque(); res=[]\n    for i,x in enumerate(a):\n        while dq and a[dq[-1]]\u003Cx: dq.pop()\n        dq.append(i)\n        if dq[0]\u003C=i-k: dq.popleft()\n        if i>=k-1: res.append(a[dq[0]])\n    return res","\u002F\u002F 队首=窗口最大；越界弹队首",{"title":6191,"content":9328,"codePy":9329,"codeCpp":9330},"每个下标进出 deque 一次，总 O(n)；比每窗口扫一遍 O(nk) 快得多。","print(\"O(n) 求所有窗口最值\")","\u002F\u002F 窗口最小维护递增",[9332],{"title":9333,"prompt":9334,"analysis":9335,"codePy":9336,"codeCpp":9337,"run":9338},"例 1：窗口最大","[1,3,2] 窗口 2 的最大值序列？","max(1,3)=3, max(3,2)=3 → 3,3。","print([3,3])","\u002F\u002F 3 3","3 3",{"choice":9340,"coding":9358},[9341,9345,9353],{"id":38,"stem":9342,"options":9343,"answer":607,"explanation":9344},"滑动窗口最大用？",[9319,6565,3117,6210],"deque 维护单调。",{"id":47,"stem":9346,"options":9347,"answer":9,"explanation":9352},"队首表示？",[9348,9349,9350,9351],"窗口最小","窗口最大","最新","最旧","递减队首最大。",{"id":56,"stem":9354,"options":9355,"answer":9,"explanation":9357},"单调队列总复杂度？",[9356,2338,2339,2564],"O(nk)","每下标进出一次。",[9359],{"id":67,"title":9360,"prompt":9361,"inputFormat":9083,"outputFormat":5404,"samples":9362,"starterPy":9365,"starterCpp":9366,"hint":9367,"referencePy":9368,"referenceCpp":9366},"窗口最大值","n 个整数、窗口大小 k，输出每个长度为 k 的窗口的最大值。",[9363],{"input":9364,"output":9338},"3 2\n1 3 2","from collections import deque\nn,k=map(int,input().split()); a=list(map(int,input().split())); dq=deque(); res=[]\nfor i,x in enumerate(a):\n    while dq and a[dq[-1]]\u003Cx: dq.pop()\n    dq.append(i)\n    if dq[0]\u003C=i-k: dq.popleft()\n    if i>=k-1: res.append(a[dq[0]])\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cdeque>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; deque\u003Cint> dq; vector\u003Cint> res; for(int i=0;i\u003Cn;i++){ while(!dq.empty()&&a[dq.back()]\u003Ca[i]) dq.pop_back(); dq.push_back(i); if(dq.front()\u003C=i-k) dq.pop_front(); if(i>=k-1) res.push_back(a[dq.front()]); } for(int x:res) cout\u003C\u003Cx\u003C\u003C' '; }","队尾弹小、队首越界弹出。","from collections import deque\nn,k=map(int,input().split()); a=list(map(int,input().split())); dq=deque(); res=[]\nfor i,x in enumerate(a):\n    while dq and a[dq[-1]]\u003Cx: dq.pop()\n    dq.append(i)\n    if dq[0]\u003C=i-k: dq.popleft()\n    if i>=k-1: res.append(a[dq[0]])\nprint(*res)","单调队列用 deque 维护窗口内元素下标的单调性：队尾弹去更小的再入队、队首越界则弹出，队首即为窗口最值；整体 O(n)。","14基础树-A1-1","树的基本概念与术语",{"id":9093,"title":9094,"level":2505,"module":9373,"seq":9,"goal":9374,"keywords":9375,"explanation":9378,"examples":9390,"exercises":9396,"summary":9429,"next":9430,"nextTitle":9431},"链表","掌握单链表：节点含值域与 next 指针，只能单向遍历，插入\u002F删除只需改指针 O(1)（已知前驱）。",[9094,1435,9376,9377],"next","节点",[9379,9384],{"title":9380,"content":9381,"codePy":9382,"codeCpp":9383},"1. 结构","每个节点存 data 与指向下一节点的指针；头指针指向首节点，末尾 next=null。","class Node:\n    def __init__(s, v): s.v = v; s.next = None\nhead = Node(1); head.next = Node(2)","struct Node{ int v; Node* next; };",{"title":9385,"content":9386,"codePy":9387,"codeCpp":9388,"tip":9389},"2. 操作","在已知前驱 p 后插入只需 p.next=New; New.next=p.next（原）；删除 p.next 只需 p.next=p.next.next。无需搬移。","def insert_after(p, v):\n    nxt = p.next; p.next = Node(v); p.next.next = nxt","\u002F\u002F 改指针即可，O(1)","单链表删除某节点需知道其前驱。",[9391],{"title":9392,"prompt":9393,"analysis":9394,"codePy":9395,"codeCpp":8843,"run":1245},"例 1：插入节点","在链表头部后插入节点？","新节点 next 指向原首，头指向新节点。","print(\"O(1) 改指针\")",{"choice":9397,"coding":9416},[9398,9405,9409],{"id":38,"stem":9399,"options":9400,"answer":9,"explanation":9404},"单链表节点包含？",[9401,9402,9403,1434],"只有值","值与 next 指针","前驱指针","data + next。",{"id":47,"stem":9406,"options":9407,"answer":9,"explanation":9408},"在已知前驱后插入的复杂度？",[2338,1245,1246,2339],"只改指针。",{"id":56,"stem":9410,"options":9411,"answer":9,"explanation":9415},"单链表遍历方向？",[9412,9413,1568,9414],"双向","单向","不可遍历","只能 next 向前。",[9417],{"id":67,"title":9418,"prompt":9419,"inputFormat":9420,"outputFormat":6275,"samples":9421,"starterPy":9425,"starterCpp":9426,"hint":9427,"referencePy":9428,"referenceCpp":9426},"单链表插入","输入 n（1≤n≤100000）和 n 个整数（构建单链表，按输入顺序），位置 pos（0≤pos≤n，从头部数第 pos 个节点后；pos=0 表示头节点前）和值 v，输出最终链表各节点值（空格分隔，用数组 next 模拟）。","第一行 n；第二行 n 个整数；第三行 pos v。",[9422],{"input":9423,"output":9424},"3\n1 2 3\n1 9","1 9 2 3","n = int(input())\nvals = list(map(int, input().split()))\npos, v = map(int, input().split())\n# 用数组模拟单链表：data 与 next 下标关系\nseq = vals[:]\nseq.insert(pos, v)   # pos 处前插入 = 第 pos 个节点后插入（pos 从 0）等价于插入位置 pos\nprint(*seq)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> a;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ int x; cin>>x; a.push_back(x); } int pos,v; cin>>pos>>v; a.insert(a.begin()+pos, v); for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","按 pos 插入 v（数组模拟链表插入语义）。","n = int(input())\nvals = list(map(int, input().split()))\npos, v = map(int, input().split())\nseq = vals[:]\nseq.insert(pos, v)\nprint(*seq)","单链表节点含值与 next 指针，单向遍历；已知前驱时插入\u002F删除只需改指针 O(1)，无需搬移。","12-A1-2","双链表",{"id":9430,"title":9431,"level":2505,"module":9373,"seq":63,"goal":9433,"keywords":9434,"explanation":9436,"examples":9447,"exercises":9453,"summary":9483,"next":9484,"nextTitle":9485},"掌握双链表：节点含 prev 与 next，可双向遍历，删除\u002F插在前驱或后继都方便。",[9431,9435,9376,9412],"prev",[9437,9441],{"title":9380,"content":9438,"codePy":9439,"codeCpp":9440},"每个节点有 prev（前驱）与 next（后继）；可从任意节点向前或向后走。","class Node:\n    def __init__(s, v): s.v = v; s.prev = None; s.next = None","struct Node{ int v; Node* prev; Node* next; };",{"title":9442,"content":9443,"codePy":9444,"codeCpp":9445,"tip":9446},"2. 删除自身","x.prev.next=x.next; x.next.prev=x.prev; 无需知道前驱节点即可删除（比单链表方便）。","def remove(x):\n    x.prev.next = x.next\n    if x.next: x.next.prev = x.prev","\u002F\u002F 双向，删自身 O(1)","双链表常用于 LRU 缓存等需要快速删除任意节点的场景。",[9448],{"title":9449,"prompt":9450,"analysis":9451,"codePy":9452,"codeCpp":8843,"run":1245},"例 1：删除节点","双链表中删除某节点 x（已知 x 自身）？","改前驱后继指针即可 O(1)。","print(\"O(1)\")",{"choice":9454,"coding":9471},[9455,9462,9466],{"id":38,"stem":9456,"options":9457,"answer":9,"explanation":9461},"双链表节点含？",[9458,9459,9460,1434],"只 next","prev 与 next","只 prev","双向指针。",{"id":47,"stem":9463,"options":9464,"answer":9,"explanation":9465},"双链表删除自身复杂度？",[2338,1245,1246,2339],"改指针即可。",{"id":56,"stem":9467,"options":9468,"answer":9,"explanation":9470},"双链表常用于？",[3117,9469,2509,3259],"LRU 缓存\u002F快速删除","快速删任意节点。",[9472],{"id":67,"title":9473,"prompt":9474,"inputFormat":9475,"outputFormat":6275,"samples":9476,"starterPy":9480,"starterCpp":9481,"hint":9482,"referencePy":9480,"referenceCpp":9481},"双链表删第 k 个","输入 n（1≤n≤100000）和 n 个整数构建双链表（顺序），删除第 k 个节点（1-indexed）后输出剩余链表（用数组模拟）。","第一行 n；第二行 n 个整数；第三行 k。",[9477],{"input":9478,"output":9479},"4\n1 2 3 4\n2","1 3 4","n = int(input())\na = list(map(int, input().split()))\nk = int(input())\na.pop(k-1)\nprint(*a)","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> a;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ int x; cin>>x; a.push_back(x); } int k; cin>>k; a.erase(a.begin()+k-1); for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","删除第 k 个（数组模拟双链表删除语义）。","双链表节点含 prev 与 next，可双向遍历；删除\u002F插入自身 O(1)，比单链表更灵活（如 LRU 缓存）。","12-A1-3","循环链表",{"id":9484,"title":9485,"level":2505,"module":9373,"seq":703,"goal":9487,"keywords":9488,"explanation":9492,"examples":9502,"exercises":9507,"summary":9539,"next":9540,"nextTitle":9541},"理解循环链表：尾节点 next 指向头，形成环，适合\"轮转\u002F环形\"场景。",[9485,9489,9490,9491],"环形","轮转","环",[9493,9497],{"title":9380,"content":9494,"codePy":9495,"codeCpp":9496},"单\u002F双链表的尾节点 next 指向头节点，没有 null 终点；遍历需记录起点防止死循环。","# 构建环：tail.next = head\n# 遍历要记起点，避免无限循环","\u002F\u002F tail->next = head; 遍历需设终止条件",{"title":4421,"content":9498,"codePy":9499,"codeCpp":9500,"tip":9501},"约瑟夫环、轮转调度、环形缓冲区；用取模或环结构模拟\"绕圈\"。","# 约瑟夫：不断删第 k 个，环上轮转","\u002F\u002F 环形轮转","遍历循环链表务必设置\"回到起点即停\"的条件。",[9503],{"title":9504,"prompt":9505,"analysis":9506,"codePy":6544,"codeCpp":6545,"run":890},"例 1：约瑟夫环","5 人报数，每数到 2 删一人，最后剩下谁（编号 1..5）？","顺序：删 2,4,1,5，剩 3。",{"choice":9508,"coding":9528},[9509,9517,9523],{"id":38,"stem":9510,"options":9511,"answer":9,"explanation":9516},"循环链表与单链表的区别？",[9512,9513,9514,9515],"更慢","尾连头成环","不能遍历","无指针","尾 next 指向头。",{"id":47,"stem":9518,"options":9519,"answer":9,"explanation":9522},"遍历循环链表要？",[9520,9521,4854,8229],"直接走","记起点防死循环","无 null 终点。",{"id":56,"stem":9524,"options":9525,"answer":9,"explanation":9527},"循环链表常用于？",[3117,9526,2509,3259],"环形\u002F轮转（如约瑟夫）","轮转调度。",[9529],{"id":67,"title":9530,"prompt":9531,"inputFormat":7151,"outputFormat":6275,"samples":9532,"starterPy":9535,"starterCpp":9536,"hint":9537,"referencePy":9538,"referenceCpp":9536},"约瑟夫环","输入 n（1≤n≤100000）和 k（1≤k≤n），从编号 1 开始，每次删除从当前起第 k 个人（环上轮转），输出最后剩下的人的编号。",[9533],{"input":9534,"output":890},"5 2","n, k = map(int, input().split())\n# 用列表模拟环：pos 从 0 起，每次 (pos + k - 1) % len 删除\npeople = list(range(1, n+1))\npos = 0\nwhile len(people) > 1:\n    pos = (pos + k - 1) % len(people)\n    people.pop(pos)\nprint(people[0])","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; vector\u003Cint> p; for(int i=1;i\u003C=n;i++) p.push_back(i); int pos=0; while((int)p.size()>1){ pos=(pos+k-1)%p.size(); p.erase(p.begin()+pos); } cout\u003C\u003Cp[0]; }","用动态数组模拟环上轮转删除（取模定位）。","n, k = map(int, input().split())\npeople = list(range(1, n+1))\npos = 0\nwhile len(people) > 1:\n    pos = (pos + k - 1) % len(people)\n    people.pop(pos)\nprint(people[0])","循环链表尾连头成环，适合轮转\u002F环形缓冲；遍历要记起点防死循环；如约瑟夫环。","12-A1-4","链表遍历",{"id":9540,"title":9541,"level":2505,"module":9373,"seq":770,"goal":9543,"keywords":9544,"explanation":9545,"examples":9557,"exercises":9565,"summary":9596,"next":9597,"nextTitle":8805},"掌握链表遍历与常见处理：从头指针沿 next 走到尾，期间可计数、查找、反转等。",[9541,1435,4166,8968],[9546,9551],{"title":9547,"content":9548,"codePy":9549,"codeCpp":9550},"1. 遍历","p=head; while p: 处理 p.v; p=p.next。注意不要丢指针、避免访问空指针。","p = head; cnt = 0\nwhile p:\n    cnt += 1; p = p.next\nprint(cnt)","\u002F\u002F p=p->next 直到空",{"title":9552,"content":9553,"codePy":9554,"codeCpp":9555,"tip":9556},"2. 反转链表","边遍历边把指针反向（cur.next=prev），经典 O(n) 单遍。","prev = None; cur = head\nwhile cur:\n    nxt = cur.next; cur.next = prev; prev = cur; cur = nxt\nhead = prev","\u002F\u002F 三指针反转链表","反转链表是链表题的高频考点。",[9558],{"title":9559,"prompt":9560,"analysis":9561,"codePy":9562,"codeCpp":9563,"run":9564},"例 1：反转","反转 [1,2,3] → ？","[3,2,1]。","print([3,2,1])","\u002F\u002F 3 2 1","[3, 2, 1]",{"choice":9566,"coding":9584},[9567,9575,9580],{"id":38,"stem":9568,"options":9569,"answer":9,"explanation":9574},"单链表遍历条件是？",[9570,9571,9572,9573],"p != head","p 非空","p.next 非空","p.prev","走到 null 为止。",{"id":47,"stem":9576,"options":9577,"answer":9,"explanation":9579},"反转链表需要？",[3117,9578,2509,3259],"三指针改向","cur.next=prev。",{"id":56,"stem":9581,"options":9582,"answer":9,"explanation":9583},"反转链表复杂度？",[2339,2338,1246,1245],"单遍 O(n)。",[9585],{"id":67,"title":9586,"prompt":9587,"inputFormat":1801,"outputFormat":9588,"samples":9589,"starterPy":9593,"starterCpp":9594,"hint":9595,"referencePy":9593,"referenceCpp":9594},"反转链表","输入 n（1≤n≤100000）和 n 个整数构建单链表（按序），反转并输出（用数组模拟反转）。","一行反转后整数，空格分隔。",[9590],{"input":9591,"output":9592},"3\n1 2 3","3 2 1","n = int(input())\na = list(map(int, input().split()))\na.reverse()\nprint(*a)","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nvector\u003Cint> a;\nint main(){ int n; cin>>n; for(int i=0;i\u003Cn;i++){ int x; cin>>x; a.push_back(x); } reverse(a.begin(), a.end()); for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","反转即逆序输出（数组模拟）。","链表遍历沿 next 走到空（防空指针）；反转链表用\"当前指向前驱\"三指针法 O(n)。","23-A2-1",{"id":8198,"title":8199,"level":6350,"module":9373,"seq":502,"goal":9599,"keywords":9600,"explanation":9604,"examples":9616,"exercises":9624,"summary":9660,"next":9661,"nextTitle":9662},"掌握单链表在指定位置\u002F按值插入与删除节点的指针操作：插入要“先连后断”防止断链，删除要记得释放\u002F跳过。",[9373,9601,9602,9603],"插入","删除","指针操作",[9605,9610],{"title":9606,"content":9607,"codePy":9608,"codeCpp":9609},"1. 插入节点","在 cur 后插入 x：x.next=cur.next; cur.next=x（顺序不能反，否则丢链）。头插\u002F尾插\u002F中间插同理。","class Node:\n    def __init__(self,v): self.v=v; self.next=None\ndef insert_after(cur, x):\n    x.next = cur.next\n    cur.next = x","\u002F\u002F x->next=cur->next; cur->next=x;",{"title":9611,"content":9612,"codePy":9613,"codeCpp":9614,"tip":9615},"2. 删除节点","删除 cur 的后继：cur.next = cur.next.next。若要删除头节点，返回 head.next 作为新头。","def delete_after(cur):\n    if cur.next: cur.next = cur.next.next","\u002F\u002F 跨过要删的节点","tip: 插入牢记“先接后继再接前驱”，避免链表断裂。",[9617],{"title":9618,"prompt":9619,"analysis":9620,"codePy":9621,"codeCpp":9622,"run":9623},"例 1：头插","链表 1->2 头插 0，结果？","0->1->2。","print(\"0->1->2\")","\u002F\u002F 0 1 2","0->1->2",{"choice":9625,"coding":9647},[9626,9633,9641],{"id":38,"stem":9627,"options":9628,"answer":9,"explanation":9632},"在 cur 后插入 x 的正确顺序？",[9629,9630,3952,9631],"cur.next=x; x.next=cur.next","x.next=cur.next; cur.next=x","x.next=cur","先接后继再接前驱。",{"id":47,"stem":9634,"options":9635,"answer":607,"explanation":9640},"删除 cur 的后继？",[9636,9637,9638,9639],"cur.next=cur.next.next","cur.next=cur","del cur","cur=cur.next","跨过被删节点。",{"id":56,"stem":9642,"options":9643,"answer":607,"explanation":9646},"删除头节点应？",[9644,9645,1307,567],"head=head.next","head.next=None","新头为原第二节点。",[9648],{"id":67,"title":9649,"prompt":9650,"inputFormat":9651,"outputFormat":5404,"samples":9652,"starterPy":9656,"starterCpp":9657,"hint":9658,"referencePy":9659,"referenceCpp":9657},"按值删除节点","给定单链表头节点（值不重复）与目标值 val，删除第一个值为 val 的节点，返回新头。链表长度≤1000。","第一行 n val；第二行 n 个整数。",[9653],{"input":9654,"output":9655},"4 3\n1 2 3 4","1 2 4","n,val=map(int,input().split()); a=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\ndummy=Node(0,head); p=dummy\nwhile p.next:\n    if p.next.v==val: p.next=p.next.next\n    else: p=p.next\nres=[]; p=dummy.next\nwhile p: res.append(p.v); p=p.next\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node* next; Node(int v,Node* n=NULL):v(v),next(n){} };\nint main(){ int n,val; cin>>n>>val; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; Node* head=NULL; for(int i=n-1;i>=0;i--) head=new Node(a[i],head); Node dummy(0,head); Node* p=&dummy; while(p->next){ if(p->next->v==val) p->next=p->next->next; else p=p->next; } p=dummy.next; while(p){ cout\u003C\u003Cp->v\u003C\u003C\" \"; p=p->next; } }","用 dummy 头简化头删。","n,val=map(int,input().split()); a=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\ndummy=Node(0,head); p=dummy\nwhile p.next:\n    if p.next.v==val: p.next=p.next.next\n    else: p=p.next\nres=[]; p=dummy.next\nwhile p: res.append(p.v); p=p.next\nprint(*res)","单链表插入要“先让新节点接上后继，再让前驱接新节点”，避免断链；删除节点即让前驱跨过后继，删除头节点时用 dummy 哨兵节点可统一处理。","12-A2-2","链表反转",{"id":9661,"title":9662,"level":6350,"module":9373,"seq":563,"goal":9664,"keywords":9665,"explanation":9667,"examples":9679,"exercises":9685,"summary":9711,"next":9712,"nextTitle":9713},"掌握单链表反转的迭代与递归两种写法：迭代用三指针（prev\u002Fcur\u002Fnext）逐个翻转指向，递归从尾部回溯翻转。",[9586,9666,6879,1405],"三指针",[9668,9673],{"title":9669,"content":9670,"codePy":9671,"codeCpp":9672},"1. 迭代三指针","prev=None；cur=head；每步 nxt=cur.next; cur.next=prev; prev=cur; cur=nxt；最后 prev 即新头。","def reverse(head):\n    prev=None; cur=head\n    while cur:\n        nxt=cur.next\n        cur.next=prev\n        prev=cur; cur=nxt\n    return prev","\u002F\u002F prev\u002Fcur\u002Fnxt 滚动",{"title":9674,"content":9675,"codePy":9676,"codeCpp":9677,"tip":9678},"2. 递归反转","递归到尾节点作为新头，回溯时让 cur.next.next=cur 并断开 cur.next，避免成环。","def rev_rec(head):\n    if not head or not head.next: return head\n    newh=rev_rec(head.next)\n    head.next.next=head; head.next=None\n    return newh","\u002F\u002F 回溯翻转指向","tip: 递归反转务必 head.next=None 断环。",[9680],{"title":9559,"prompt":9681,"analysis":9682,"codePy":9683,"codeCpp":9563,"run":9684},"1->2->3 反转后？","3->2->1。","print(\"3->2->1\")","3->2->1",{"choice":9686,"coding":9702},[9687,9692,9697],{"id":38,"stem":9688,"options":9689,"answer":9,"explanation":9691},"迭代反转需几个指针？",[892,9690,759,1663],"3(prev\u002Fcur\u002Fnxt)","三指针滚动。",{"id":47,"stem":9693,"options":9694,"answer":9,"explanation":9696},"递归反转为何 head.next=None？",[3345,9695,3117,6449],"断环防自环","否则新头指向自身。",{"id":56,"stem":9698,"options":9699,"answer":9,"explanation":9701},"1->2->3 反转头节点？",[892,890,759,9700],"NULL","3 成为新头。",[9703],{"id":67,"title":9586,"prompt":9704,"inputFormat":1801,"outputFormat":5404,"samples":9705,"starterPy":9707,"starterCpp":9708,"hint":9709,"referencePy":9710,"referenceCpp":9708},"反转单链表，返回新头。长度≤1000。",[9706],{"input":9591,"output":9592},"n=int(input()); a=list(map(int,input().split())) if n else []\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\nprev=None; cur=head\nwhile cur:\n    nxt=cur.next; cur.next=prev; prev=cur; cur=nxt\nres=[]; p=prev\nwhile p: res.append(p.v); p=p.next\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node* next; Node(int v,Node* n=NULL):v(v),next(n){} };\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; Node* head=NULL; for(int i=n-1;i>=0;i--) head=new Node(a[i],head); Node* prev=NULL; Node* cur=head; while(cur){ Node* nxt=cur->next; cur->next=prev; prev=cur; cur=nxt; } while(prev){ cout\u003C\u003Cprev->v\u003C\u003C\" \"; prev=prev->next; } }","三指针逐个翻转。","n=int(input()); a=list(map(int,input().split())) if n else []\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\nprev=None; cur=head\nwhile cur:\n    nxt=cur.next; cur.next=prev; prev=cur; cur=nxt\nres=[]; p=prev\nwhile p: res.append(p.v); p=p.next\nprint(*res)","反转链表迭代法用 prev\u002Fcur\u002Fnxt 三指针逐个把 cur.next 指向 prev；递归法则在回溯时令 cur.next.next=cur 并置 cur.next=None 防自环。两种方法都需谨慎处理指针顺序避免断链。","12-A2-3","快慢指针",{"id":9712,"title":9713,"level":6350,"module":9373,"seq":9715,"goal":9716,"keywords":9717,"explanation":9720,"examples":9732,"exercises":9737,"summary":9768,"next":9769,"nextTitle":9770},18,"掌握快慢指针（龟兔赛跑）：快指针走两步、慢指针走一步，可判环、求中点、找倒数第 k 个节点。",[9713,9718,5709,9719],"判环","倒数第k",[9721,9726],{"title":9722,"content":9723,"codePy":9724,"codeCpp":9725},"1. 判环","快慢指针同起点，快走 2 慢走 1；若有环必相遇，无环快指针先到尾。","def has_cycle(head):\n    slow=fast=head\n    while fast and fast.next:\n        slow=slow.next; fast=fast.next.next\n        if slow is fast: return True\n    return False","\u002F\u002F 有环必相遇",{"title":9727,"content":9728,"codePy":9729,"codeCpp":9730,"tip":9731},"2. 求中点与倒数第 k","中点：快到尾时慢恰在中间。倒数第 k：先让快走 k 步，再同速，快到尾时慢即倒数第 k。","print(\"同速差 k 步 => 倒数第 k\")","\u002F\u002F 快 2 慢 1 => 中点","tip: 快慢指针是链表高频技巧，避免两次遍历。",[9733],{"title":9734,"prompt":9735,"analysis":9736,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：判环","链表尾指向第二节点，是否有环？","有环，快慢相遇。",{"choice":9738,"coding":9756},[9739,9744,9748],{"id":38,"stem":9740,"options":9741,"answer":9,"explanation":9743},"快慢指针判环依据？",[1568,9742,6269,3117],"有环必相遇","相对速度差 1。",{"id":47,"stem":9745,"options":9746,"answer":9,"explanation":9747},"求链表中点用？",[3259,9713,6565,1405],"快2慢1，快到尾慢居中。",{"id":56,"stem":9749,"options":9750,"answer":607,"explanation":9755},"倒数第k个先让快走？",[9751,9752,9753,9754],"k 步","1 步","k-1 步","尾部","差 k 步再同速。",[9757],{"id":67,"title":9758,"prompt":9759,"inputFormat":9760,"outputFormat":8414,"samples":9761,"starterPy":9764,"starterCpp":9765,"hint":9766,"referencePy":9767,"referenceCpp":9765},"环形链表","判断单链表是否有环（节点值均为正整数，pos 表示尾节点指向第 pos 个节点，pos=-1 表示无环）。","第一行 n pos；第二行 n 个整数。",[9762],{"input":9763,"output":8418},"4 1\n3 2 0 4","n,pos=map(int,input().split()); a=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\nif pos>=0:\n    p=head; i=0\n    while i\u003Cpos: p=p.next; i+=1\n    tail=head; c=0\n    while c\u003Cn-1: tail=tail.next; c+=1\n    tail.next=p\nslow=fast=head\nwhile fast and fast.next:\n    slow=slow.next; fast=fast.next.next\n    if slow is fast: print(\"YES\"); break\nelse: print(\"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node* next; Node(int v,Node* n=NULL):v(v),next(n){} };\nint main(){ int n,pos; cin>>n>>pos; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; Node* head=NULL; for(int i=n-1;i>=0;i--) head=new Node(a[i],head); if(pos>=0){ Node* p=head; for(int i=0;i\u003Cpos;i++) p=p->next; Node* t=head; for(int i=0;i\u003Cn-1;i++) t=t->next; t->next=p; } Node* s=head; Node* f=head; bool cyc=false; while(f&&f->next){ s=s->next; f=f->next->next; if(s==f){ cyc=true; break; } } cout\u003C\u003C(cyc?\"YES\":\"NO\"); }","快慢指针，相遇即有环。","n,pos=map(int,input().split()); a=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead=None\nfor v in reversed(a): head=Node(v,head)\nif pos>=0:\n    p=head; i=0\n    while i\u003Cpos: p=p.next; i+=1\n    tail=head; c=0\n    while c\u003Cn-1: tail=tail.next; c+=1\n    tail.next=p\nslow=fast=head\nwhile fast and fast.next:\n    slow=slow.next; fast=fast.next.next\n    if slow is fast: print(\"YES\"); break\nelse: print(\"NO\")","快慢指针让快指针每次走 2 步、慢指针走 1 步：有环时二者必相遇可用于判环，快到尾时慢指针恰在中点，先让快走 k 步再同速则慢指针落在倒数第 k 个节点。","12-A2-4","合并两个有序链表",{"id":9769,"title":9770,"level":6350,"module":9373,"seq":9772,"goal":9773,"keywords":9774,"explanation":9778,"examples":9790,"exercises":9796,"summary":9826,"next":9827,"nextTitle":9828},19,"掌握合并两个升序链表：用 dummy 哨兵节点，比较两链表头节点值，小的接上并后移，最后接剩余。",[9775,5245,9776,9777],"有序链表","dummy","双指针",[9779,9784],{"title":9780,"content":9781,"codePy":9782,"codeCpp":9783},"1. 双指针归并","p 指向结果尾；比较 l1.val 与 l2.val，小者接到 p 后并前进；任一方空后直接接另一方。","def merge(l1, l2):\n    dummy=Node(0); p=dummy\n    while l1 and l2:\n        if l1.v\u003C=l2.v: p.next=l1; l1=l1.next\n        else: p.next=l2; l2=l2.next\n        p=p.next\n    p.next = l1 or l2\n    return dummy.next","\u002F\u002F 小的接上，p 后移",{"title":9785,"content":9786,"codePy":9787,"codeCpp":9788,"tip":9789},"2. 与数组归并","与归并排序中合并两有序数组同理，只是用指针操作而非下标。","print(\"链表版归并\")","\u002F\u002F 注意接剩余部分","tip: dummy 哨兵避免特判空链表头。",[9791],{"title":5260,"prompt":9792,"analysis":9793,"codePy":9794,"codeCpp":5264,"run":9795},"1->3 与 2->4 合并？","1->2->3->4。","print(\"1->2->3->4\")","1->2->3->4",{"choice":9797,"coding":9814},[9798,9803,9808],{"id":38,"stem":9799,"options":9800,"answer":9,"explanation":9802},"合并有序链表用？",[5459,9801,3259,6565],"双指针归并","比较头节点接小者。",{"id":47,"stem":9804,"options":9805,"answer":9,"explanation":9807},"为何用 dummy？",[3345,9806,3117,6449],"免特判新头","哨兵统一处理。",{"id":56,"stem":9809,"options":9810,"answer":9,"explanation":9813},"一方为空后？",[6554,9811,567,9812],"接上另一方","清空","直接接剩余。",[9815],{"id":67,"title":9816,"prompt":9817,"inputFormat":9818,"outputFormat":5404,"samples":9819,"starterPy":9822,"starterCpp":9823,"hint":9824,"referencePy":9825,"referenceCpp":9823},"合并有序链表","合并两个升序单链表为一个升序链表，返回新头（长度均≤1000）。","第一行 n m；第二行 n 个整数；第三行 m 个整数。",[9820],{"input":9821,"output":2212},"2 2\n1 3\n2 4","import sys\nn,m=map(int,input().split()); a=list(map(int,input().split())); b=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead1=head2=None\nfor v in reversed(a): head1=Node(v,head1)\nfor v in reversed(b): head2=Node(v,head2)\ndummy=Node(0); p=dummy\nwhile head1 and head2:\n    if head1.v\u003C=head2.v: p.next=head1; head1=head1.next\n    else: p.next=head2; head2=head2.next\n    p=p.next\np.next=head1 or head2\nres=[]; p=dummy.next\nwhile p: res.append(p.v); p=p.next\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node* next; Node(int v,Node* n=NULL):v(v),next(n){} };\nint main(){ int n,m; cin>>n>>m; vector\u003Cint> a(n),b(m); for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int i=0;i\u003Cm;i++) cin>>b[i]; Node* h1=NULL; for(int i=n-1;i>=0;i--) h1=new Node(a[i],h1); Node* h2=NULL; for(int i=m-1;i>=0;i--) h2=new Node(b[i],h2); Node dummy(0); Node* p=&dummy; while(h1&&h2){ if(h1->v\u003C=h2->v){ p->next=h1; h1=h1->next; } else { p->next=h2; h2=h2->next; } p=p->next; } p->next = h1?h1:h2; p=dummy.next; while(p){ cout\u003C\u003Cp->v\u003C\u003C\" \"; p=p->next; } }","比较头节点，接小者，最后接剩余。","import sys\nn,m=map(int,input().split()); a=list(map(int,input().split())); b=list(map(int,input().split()))\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nhead1=head2=None\nfor v in reversed(a): head1=Node(v,head1)\nfor v in reversed(b): head2=Node(v,head2)\ndummy=Node(0); p=dummy\nwhile head1 and head2:\n    if head1.v\u003C=head2.v: p.next=head1; head1=head1.next\n    else: p.next=head2; head2=head2.next\n    p=p.next\np.next=head1 or head2\nres=[]; p=dummy.next\nwhile p: res.append(p.v); p=p.next\nprint(*res)","合并两个升序链表用双指针归并：dummy 哨兵统一新头，比较两链表当前节点值接上较小者并后移，任一方耗尽后直接接上另一方，等价于链表版的归并排序合并。","15-A2-1","堆的基本操作",{"id":9370,"title":9371,"level":6178,"module":9830,"seq":9,"goal":9831,"keywords":9832,"explanation":9837,"examples":9849,"exercises":9854,"summary":9888,"next":9889,"nextTitle":9890},"树与二叉树基础","理解树的定义与基本术语：根、父\u002F子、叶子、度、深度、高度，以及二叉树与多叉树的区别。",[7901,9833,9834,9835,9836],"根","度","深度","高度",[9838,9843],{"title":9839,"content":9840,"codePy":9841,"codeCpp":9842},"1. 定义与术语","树是 n 个节点、n-1 条边的无环连通图；有唯一根，节点孩子数称“度”，无孩子的称“叶子”。","print(\"节点数 = 边数 + 1（无环连通）\")","\u002F\u002F 深度=从根到该节点的边数；高度=到最深叶子的边数",{"title":9844,"content":9845,"codePy":9846,"codeCpp":9847,"tip":9848},"2. 二叉树","每个节点最多两个孩子（左\u002F右），左右有序；空树也是二叉树。","print(\"二叉树：每个节点至多 2 个孩子且分左右\")","\u002F\u002F 多叉树可转为二叉树（孩子-兄弟）","tip: 二叉树是算法中最常用的树形结构。",[9850],{"title":9851,"prompt":9852,"analysis":9853,"codePy":5966,"codeCpp":4490,"run":1663},"例 1：节点边","5 个节点的树有几条边？","n-1=4 条。",{"choice":9855,"coding":9876},[9856,9863,9869],{"id":38,"stem":9857,"options":9858,"answer":9,"explanation":9862},"n 节点树边数？",[3054,9859,9860,9861],"n-1","n+1","2n","无环连通。",{"id":47,"stem":9864,"options":9865,"answer":9,"explanation":9868},"无孩子的节点叫？",[9833,9866,9867,9834],"叶子","父","叶子\u002F终端节点。",{"id":56,"stem":9870,"options":9871,"answer":9,"explanation":9875},"二叉树孩子？",[3952,9872,9873,9874],"至多 2 且分左右","仅 1","恰好 2","左右有序。",[9877],{"id":67,"title":9878,"prompt":9879,"inputFormat":9880,"outputFormat":6275,"samples":9881,"starterPy":9884,"starterCpp":9885,"hint":9886,"referencePy":9887,"referenceCpp":9885},"统计叶子数","给定一棵树（n 节点，n-1 边，节点编号 1..n，1 为根），输出叶子节点个数。","第一行 n；接下来 n-1 行 u v（无向边）。",[9882],{"input":9883,"output":759},"4\n1 2\n1 3\n2 4","import sys\nsys.setrecursionlimit(100000)\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nleaf=0\ndef dfs(u,p):\n    global leaf\n    if len(g[u])==1 and u!=1: leaf+=1\n    for v in g[u]:\n        if v!=p: dfs(v,u)\ndfs(1,0)\nif n==1: leaf=1\nprint(leaf)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; int leaf=0;\nvoid dfs(int u,int p){ if(g[u].size()==1 && u!=1) leaf++; for(int v:g[u]) if(v!=p) dfs(v,u); }\nint main(){ int n; cin>>n; for(int i=0,u,v;i\u003Cn-1;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} dfs(1,0); if(n==1) leaf=1; cout\u003C\u003Cleaf; }","度为 1 且非根即为叶子。","import sys\nsys.setrecursionlimit(100000)\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nleaf=0\ndef dfs(u,p):\n    global leaf\n    if len(g[u])==1 and u!=1: leaf+=1\n    for v in g[u]:\n        if v!=p: dfs(v,u)\ndfs(1,0)\nif n==1: leaf=1\nprint(leaf)","树是 n 节点 n-1 边的无环连通结构；术语含根、度、叶子、深度、高度。二叉树每个节点至多两个有序孩子（左右），是最常用的树形结构。","14基础树-A1-2","二叉树的定义与性质",{"id":9889,"title":9890,"level":6178,"module":9830,"seq":63,"goal":9892,"keywords":9893,"explanation":9897,"examples":9908,"exercises":9913,"summary":9949,"next":9950,"nextTitle":9951},"掌握二叉树的重要性质：第 i 层最多 2^(i-1) 个节点，深度为 h 的二叉树最多 2^h-1 个节点，叶子与度为 2 节点数关系。",[9894,9895,9896],"二叉树性质","节点数上界","叶子关系",[9898,9903],{"title":9899,"content":9900,"codePy":9901,"codeCpp":9902},"1. 层与总数上界","深度为 h 的二叉树最多有 2^h-1 个节点（满二叉树）；第 i 层最多 2^(i-1) 个。","h=3\nprint(2**h-1)  # 7","\u002F\u002F 满二叉树节点数 2^h-1",{"title":9904,"content":9905,"codePy":9906,"codeCpp":9907},"2. 叶子关系","任意二叉树中，叶子数 = 度为 2 的节点数 + 1（设 n0 叶子、n2 度2，则 n0=n2+1）。","print(\"n0 = n2 + 1\")","\u002F\u002F 由 总边 = n-1 = n1+2*n2 推得",[9909],{"title":9910,"prompt":9911,"analysis":9912,"codePy":6021,"codeCpp":6022,"run":885},"例 1：满二叉树","深度 3 的满二叉树最多几个节点？","2^3-1=7。",{"choice":9914,"coding":9939},[9915,9923,9931],{"id":38,"stem":9916,"options":9917,"answer":9,"explanation":9922},"深度 h 二叉树最多节点？",[9918,9919,9920,9921],"2^h","2^h-1","h","2h","满二叉树。",{"id":47,"stem":9924,"options":9925,"answer":9,"explanation":9930},"二叉树中 n0 与 n2 关系？",[9926,9927,9928,9929],"n0=n2","n0=n2+1","n0=n2-1","无关","叶子比度2多1。",{"id":56,"stem":9932,"options":9933,"answer":63,"explanation":9938},"第 i 层最多节点？",[9934,9935,9936,9937],"i","2^i","2^(i-1)","2i","每层翻倍。",[9940],{"id":67,"title":9941,"prompt":9942,"inputFormat":5992,"outputFormat":6275,"samples":9943,"starterPy":9945,"starterCpp":9946,"hint":9947,"referencePy":9948,"referenceCpp":9946},"叶子与度2","已知二叉树中“度为 2”的节点有 k 个，求叶子数（度为 0 的节点数）。",[9944],{"input":890,"output":1663},"k=int(input())\nprint(k+1)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int k; cin>>k; cout\u003C\u003Ck+1; }","n0=n2+1。","k=int(input())\nprint(k+1)","二叉树性质：深度 h 最多 2^h-1 节点、第 i 层最多 2^(i-1) 节点；且叶子数 n0 = 度为 2 的节点数 n2 + 1。","14基础树-A1-3","满二叉树与完全二叉树",{"id":9950,"title":9951,"level":6178,"module":9830,"seq":703,"goal":9953,"keywords":9954,"explanation":9959,"examples":9970,"exercises":9978,"summary":10013,"next":10014,"nextTitle":10015},"区分满二叉树（每层全满）与完全二叉树（层序填满、最后一层从左连续），并理解完全二叉树用数组存储的优势。",[9955,9956,9957,9958],"满二叉树","完全二叉树","数组存储","左移",[9960,9965],{"title":9961,"content":9962,"codePy":9963,"codeCpp":9964},"1. 定义","满二叉树：每层都达到最大节点数。完全二叉树：除最后一层外全满，且最后一层从左到右连续无空洞。","print(\"完全二叉树适合用数组按下标存\")","\u002F\u002F 堆就是完全二叉树",{"title":9966,"content":9967,"codePy":9968,"codeCpp":9969},"2. 数组下标","完全二叉树按层序编号：节点 i 的左孩子 2i、右孩子 2i+1，父 ⌊i\u002F2⌋。","print(\"i 的左=2i，右=2i+1，父=i\u002F\u002F2\")","\u002F\u002F 下标从 1 开始",[9971],{"title":9972,"prompt":9973,"analysis":9974,"codePy":9975,"codeCpp":9976,"run":9977},"例 1：下标","数组从 1 开始，节点 3 的父与左孩子？","父=1，左=6。","print(1,6)","\u002F\u002F 1 6","1 6",{"choice":9979,"coding":10000},[9980,9986,9993],{"id":38,"stem":9981,"options":9982,"answer":9,"explanation":9985},"完全二叉树最后一层？",[3952,9983,9984,9185],"从左连续","从右","无空洞。",{"id":47,"stem":9987,"options":9988,"answer":9,"explanation":9992},"节点 i 的左孩子下标？",[9989,9937,9990,9991],"i+1","2i+1","i\u002F2","从 1 编号。",{"id":56,"stem":9994,"options":9995,"answer":9,"explanation":9999},"堆是哪种树？",[9996,9997,3952,9998],"满","完全","斜树","完全二叉树。",[10001],{"id":67,"title":10002,"prompt":10003,"inputFormat":10004,"outputFormat":10005,"samples":10006,"starterPy":10009,"starterCpp":10010,"hint":10011,"referencePy":10012,"referenceCpp":10010},"父与子","完全二叉树层序编号（从 1 开始），输入节点 i，输出其父、左孩子、右孩子（不存在输出 0）。","一行整数 i（i≥1）。","一行三个整数。",[10007],{"input":890,"output":10008},"1 6 7","i=int(input())\nprint(i\u002F\u002F2, 2*i, 2*i+1)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int i; cin>>i; cout\u003C\u003Ci\u002F2\u003C\u003C\" \"\u003C\u003C2*i\u003C\u003C\" \"\u003C\u003C2*i+1; }","父 i\u002F2，左 2i，右 2i+1。","i=int(input())\nprint(i\u002F\u002F2, 2*i, 2*i+1)","满二叉树每层全满；完全二叉树除末层外全满且末层从左连续，可用数组按下标（i 的左=2i、右=2i+1、父=i\u002F2）紧凑存储，堆即其典型应用。","14基础树-A1-4","二叉树的顺序存储",{"id":10014,"title":10015,"level":6178,"module":9830,"seq":770,"goal":10017,"keywords":10018,"explanation":10022,"examples":10033,"exercises":10038,"summary":10070,"next":10071,"nextTitle":10072},"掌握二叉树的顺序存储（数组按下标表示左右孩子），理解其适合完全二叉树、对一般树有空洞浪费。",[10019,1434,10020,10021],"顺序存储","下标映射","空洞",[10023,10028],{"title":10024,"content":10025,"codePy":10026,"codeCpp":10027},"1. 映射规则","用数组 a，a[1] 为根，a[i] 的左孩子 a[2i]、右孩子 a[2i+1]，空节点用 0\u002FNone 占位。","a=[None,1,2,3,4,5,6]  # 1为根，2左3右...\nleft = lambda i: 2*i\nright = lambda i: 2*i+1","\u002F\u002F 数组下标即位置关系",{"title":10029,"content":10030,"codePy":10031,"codeCpp":10032},"2. 优缺点","完全二叉树无浪费、访问快；一般二叉树（特别是斜树）会出现大量空洞，空间浪费严重。","print(\"完全二叉树最省；斜树浪费大\")","\u002F\u002F 故一般树多用链式",[10034],{"title":10035,"prompt":10036,"analysis":10037,"codePy":7705,"codeCpp":7706,"run":290},"例 1：位置","根在 a[1]，节点 2 的右孩子在 a 的哪下标？","2*2+1=5。",{"choice":10039,"coding":10056},[10040,10044,10049],{"id":38,"stem":10041,"options":10042,"answer":9,"explanation":9985},"顺序存储最适合？",[9998,9956,3952,10043],"单链",{"id":47,"stem":10045,"options":10046,"answer":9,"explanation":10048},"节点 i 右孩子下标？",[9937,9990,9989,10047],"i\u002F\u002F2","左 2i 右 2i+1。",{"id":56,"stem":10050,"options":10051,"answer":9,"explanation":10055},"一般二叉树用顺序存储？",[10052,10053,1448,10054],"最省","有空洞浪费","不行","斜树空洞多。",[10057],{"id":67,"title":10058,"prompt":10059,"inputFormat":10060,"outputFormat":10061,"samples":10062,"starterPy":10066,"starterCpp":10067,"hint":10068,"referencePy":10069,"referenceCpp":10067},"顺序建树查询","给定完全二叉树的层序数组（下标 1..m，0 表示空），输入 i，输出其值及左右孩子值（空输出 0）。","第一行 m；第二行 m 个整数（a[1..m]）；第三行 i。","一行：val left right。",[10063],{"input":10064,"output":10065},"6\n1 2 3 4 5 6\n2","2 4 5","m=int(input()); a=[0]+list(map(int,input().split())); i=int(input())\nprint(a[i], a[2*i] if 2*i\u003C=m else 0, a[2*i+1] if 2*i+1\u003C=m else 0)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int m; cin>>m; vector\u003Cint> a(m+1); for(int i=1;i\u003C=m;i++) cin>>a[i]; int i; cin>>i; cout\u003C\u003Ca[i]\u003C\u003C\" \"\u003C\u003C(2*i\u003C=m?a[2*i]:0)\u003C\u003C\" \"\u003C\u003C(2*i+1\u003C=m?a[2*i+1]:0); }","注意下标越界判 0。","m=int(input()); a=[0]+list(map(int,input().split())); i=int(input())\nprint(a[i], a[2*i] if 2*i\u003C=m else 0, a[2*i+1] if 2*i+1\u003C=m else 0)","顺序存储用数组按下标表示父子关系（i 的左=2i、右=2i+1），适合完全二叉树、紧凑无浪费；一般二叉树会有空洞，通常用链式。","14基础树-A1-5","二叉树的链式存储",{"id":10071,"title":10072,"level":6178,"module":9830,"seq":838,"goal":10074,"keywords":10075,"explanation":10079,"examples":10090,"exercises":10095,"summary":10127,"next":10128,"nextTitle":10129},"掌握二叉树的链式存储：用节点结构（val, left, right）动态建树，理解它与顺序存储的取舍。",[10076,9377,10077,10078],"链式存储","left\u002Fright","建树",[10080,10085],{"title":10081,"content":10082,"codePy":10083,"codeCpp":10084},"1. 节点结构","每个节点存值与左右孩子指针；空孩子为 null。建树时按层序或显式左右指针构造。","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\nroot=Node(1); root.l=Node(2); root.r=Node(3)","\u002F\u002F struct Node{int v; Node *l,*r;};",{"title":10086,"content":10087,"codePy":10088,"codeCpp":10089},"2. 取舍","链式无空洞、灵活，适合任意形状；但需指针、访问需遍历。顺序适合完全二叉树。","print(\"链式适合一般二叉树\")","\u002F\u002F 动态分配，空间随节点数",[10091],{"title":10092,"prompt":10093,"analysis":10094,"codePy":6253,"codeCpp":5851,"run":759},"例 1：建树","根为 1，左 2 右 3，根的左孩子是？","2。",{"choice":10096,"coding":10114},[10097,10103,10108],{"id":38,"stem":10098,"options":10099,"answer":9,"explanation":10102},"链式节点存？",[10100,10101,3586,7965],"仅值","值+左右指针","左\u002F右孩子指针。",{"id":47,"stem":10104,"options":10105,"answer":9,"explanation":10107},"空孩子表示为？",[891,10106,892,9833],"null\u002FNone","指针为空。",{"id":56,"stem":10109,"options":10110,"answer":9,"explanation":10113},"链式相比顺序的优点？",[1448,10111,1582,10112],"无空洞灵活","无需指针","任意形状不浪费。",[10115],{"id":67,"title":10116,"prompt":10117,"inputFormat":10118,"outputFormat":10119,"samples":10120,"starterPy":10123,"starterCpp":10124,"hint":10125,"referencePy":10126,"referenceCpp":10124},"链式建树","按先序输入一棵二叉树（节点值，0 表示空），构造链式二叉树并输出根的值与左右孩子值（空输出 0）。","一行先序序列（0 为空），如 1 2 0 0 3 0 0。","一行：root left right。",[10121],{"input":10122,"output":74},"1 2 0 0 3 0 0","vals=list(map(int,input().split())); it=[0]\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\nroot=build()\nprint(root.v, (root.l.v if root.l else 0), (root.r.v if root.r else 0))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nint main(){ int x; while(cin>>x) a.push_back(x); Node* root=build(); cout\u003C\u003Croot->v\u003C\u003C\" \"\u003C\u003C(root->l?root->l->v:0)\u003C\u003C\" \"\u003C\u003C(root->r?root->r->v:0); }","先序递归建树：值→左→右。","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build()\nprint(root.v, (root.l.v if root.l else 0), (root.r.v if root.r else 0))","链式存储用节点（值 + 左右指针）动态建树，灵活无空洞，适合任意形状二叉树；空孩子用 null 表示，建树常按先序递归。","14基础树-A1-6","二叉树的先序\u002F中序\u002F后序遍历（递归）",{"id":10128,"title":10129,"level":6178,"module":9830,"seq":936,"goal":10131,"keywords":10132,"explanation":10137,"examples":10148,"exercises":10153,"summary":10185,"next":10186,"nextTitle":10187},"掌握三种深度遍历的递归写法：先序（根左右）、中序（左根右）、后序（左右根），理解访问根的顺序差异。",[10133,10134,10135,10136],"先序","中序","后序","递归遍历",[10138,10143],{"title":10139,"content":10140,"codePy":10141,"codeCpp":10142},"1. 三种顺序","先序：先访问根再左右；中序：先左再根后右；后序：先左右最后根。仅“根”的位置不同。","def pre(u):\n    if not u: return\n    print(u.v); pre(u.l); pre(u.r)\ndef ino(u):\n    if not u: return\n    ino(u.l); print(u.v); ino(u.r)\ndef post(u):\n    if not u: return\n    post(u.l); post(u.r); print(u.v)","\u002F\u002F 根在首\u002F中\u002F尾",{"title":10144,"content":10145,"codePy":10146,"codeCpp":10147},"2. 何时用哪种","复制树用先序；BST 中序得升序；释放节点用后序（先释放孩子）。","print(\"BST 中序=升序\")","\u002F\u002F 仅根位置不同",[10149],{"title":10150,"prompt":10151,"analysis":10152,"codePy":8222,"codeCpp":5315,"run":74},"例 1：中序","BST 根 2，左 1 右 3，中序序列？","左根右：1 2 3。",{"choice":10154,"coding":10172},[10155,10163,10168],{"id":38,"stem":10156,"options":10157,"answer":9,"explanation":10162},"中序顺序是？",[10158,10159,10160,10161],"根左右","左根右","左右根","根右左","左-根-右。",{"id":47,"stem":10164,"options":10165,"answer":9,"explanation":10167},"BST 中序得到？",[5328,5648,10166,8029],"乱序","二叉搜索树中序升序。",{"id":56,"stem":10169,"options":10170,"answer":63,"explanation":10171},"释放节点用？",[10133,10134,10135,8029],"先释放孩子。",[10173],{"id":67,"title":10174,"prompt":10175,"inputFormat":10176,"outputFormat":10177,"samples":10178,"starterPy":10181,"starterCpp":10182,"hint":10183,"referencePy":10184,"referenceCpp":10182},"三种遍历","按先序（0 表示空）建树，分别输出先序、中序、后序序列。","一行先序序列。","三行，分别为先序、中序、后序（空格分隔）。",[10179],{"input":10122,"output":10180},"1 2 3\n2 1 3\n2 3 1","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\npre=[]; ino=[]; post=[]\ndef dfs(u):\n    if not u: return\n    pre.append(u.v); dfs(u.l); ino.append(u.v); dfs(u.r); post.append(u.v)\ndfs(build())\nprint(*pre); print(*ino); print(*post)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0; vector\u003Cint> pre,ino,post;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nvoid dfs(Node* u){ if(!u) return; pre.push_back(u->v); dfs(u->l); ino.push_back(u->v); dfs(u->r); post.push_back(u->v); }\nint main(){ int x; while(cin>>x) a.push_back(x); dfs(build()); for(int v:pre) cout\u003C\u003Cv\u003C\u003C' '; cout\u003C\u003Cendl; for(int v:ino) cout\u003C\u003Cv\u003C\u003C' '; cout\u003C\u003Cendl; for(int v:post) cout\u003C\u003Cv\u003C\u003C' '; }","根在首\u002F中\u002F尾。","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\npre=[]; ino=[]; post=[]\ndef dfs(u):\n    if not u: return\n    pre.append(u.v); dfs(u.l); ino.append(u.v); dfs(u.r); post.append(u.v)\ndfs(build())\nprint(*pre); print(*ino); print(*post)","三种深度遍历仅“根”的位置不同：先序根左右、中序左根右、后序左右根。BST 中序得到升序；复制\u002F释放树常用先序\u002F后序。","14基础树-A1-7","由先序与中序重建二叉树",{"id":10186,"title":10187,"level":6178,"module":9830,"seq":998,"goal":10189,"keywords":10190,"explanation":10195,"examples":10206,"exercises":10214,"summary":10248,"next":10249,"nextTitle":10250},"掌握用“先序定根 + 中序定左右”递归重建二叉树的方法，并理解其唯一性条件。",[10191,10192,10193,10194],"重建","先序中序","分治建树","唯一",[10196,10201],{"title":10197,"content":10198,"codePy":10199,"codeCpp":10200},"1. 思路","先序首元素是当前根；在中序中找到它，左边是左子树、右边是右子树；对左右子树递归。","def build(pre, ino):\n    if not pre: return None\n    root=Node(pre[0]); i=ino.index(pre[0])\n    root.l=build(pre[1:i+1], ino[:i])\n    root.r=build(pre[i+1:], ino[i+1:])\n    return root","\u002F\u002F 先序首=根；中序切左右",{"title":10202,"content":10203,"codePy":10204,"codeCpp":10205},"2. 唯一性","先序+中序（或后序+中序）可唯一确定二叉树；仅有先序+后序不足以唯一确定。","print(\"先序+中序 唯一重建\")","\u002F\u002F 用哈希表存中序位置可加速",[10207],{"title":10208,"prompt":10209,"analysis":10210,"codePy":10211,"codeCpp":10212,"run":10213},"例 1：重建","先序 1 2 3，中序 2 1 3，根与左右？","根1，左2右3。","print(\"根1 左2 右3\")","\u002F\u002F root1 L2 R3","根1 左2 右3",{"choice":10215,"coding":10234},[10216,10222,10226],{"id":38,"stem":10217,"options":10218,"answer":9,"explanation":10221},"先序首元素是？",[10219,9833,10220,9866],"左子树","右子树","先序根左右。",{"id":47,"stem":10223,"options":10224,"answer":9,"explanation":10225},"在中序中根的左边是？",[10220,10219,9833,9185],"左根右。",{"id":56,"stem":10227,"options":10228,"answer":9,"explanation":10233},"能唯一重建的组合？",[10229,10230,10231,10232],"先序+后序","先序+中序","仅先序","仅中序","先序+中序唯一。",[10235],{"id":67,"title":10236,"prompt":10237,"inputFormat":10238,"outputFormat":10239,"samples":10240,"starterPy":10244,"starterCpp":10245,"hint":10246,"referencePy":10247,"referenceCpp":10245},"重建并后序","给定先序与中序序列，重建二叉树并输出后序序列。","第一行 n；第二行先序；第三行中序。","一行后序序列，空格分隔。",[10241],{"input":10242,"output":10243},"3\n1 2 3\n2 1 3","2 3 1","n=int(input()); pre=list(map(int,input().split())); ino=list(map(int,input().split()))\npos={v:i for i,v in enumerate(ino)}\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build(pl,pr,il,ir):\n    if pl>pr: return None\n    v=pre[pl]; i=pos[v]-il; r=Node(v)\n    r.l=build(pl+1,pl+i,il,il+i-1); r.r=build(pl+i+1,pr,il+i+1,ir); return r\npost=[]\ndef dfs(u):\n    if not u: return\n    dfs(u.l); dfs(u.r); post.append(u.v)\ndfs(build(0,n-1,0,n-1))\nprint(*post)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cunordered_map>\nusing namespace std;\nvector\u003Cint> pre,ino,post; unordered_map\u003Cint,int> pos;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nNode* build(int pl,int pr,int il,int ir){ if(pl>pr) return 0; int v=pre[pl], i=pos[v]-il; Node* r=new Node(v); r->l=build(pl+1,pl+i,il,il+i-1); r->r=build(pl+i+1,pr,il+i+1,ir); return r; }\nvoid dfs(Node* u){ if(!u) return; dfs(u->l); dfs(u->r); post.push_back(u->v); }\nint main(){ int n; cin>>n; pre.resize(n); ino.resize(n); for(int i=0;i\u003Cn;i++) cin>>pre[i]; for(int i=0;i\u003Cn;i++){ cin>>ino[i]; pos[ino[i]]=i; } dfs(build(0,n-1,0,n-1)); for(int v:post) cout\u003C\u003Cv\u003C\u003C' '; }","先序首为根，中序定位切分。","n=int(input()); pre=list(map(int,input().split())); ino=list(map(int,input().split()))\npos={v:i for i,v in enumerate(ino)}\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build(pl,pr,il,ir):\n    if pl>pr: return None\n    v=pre[pl]; i=pos[v]-il; r=Node(v)\n    r.l=build(pl+1,pl+i,il,il+i-1); r.r=build(pl+i+1,pr,il+i+1,ir); return r\npost=[]\ndef dfs(u):\n    if not u: return\n    dfs(u.l); dfs(u.r); post.append(u.v)\ndfs(build(0,n-1,0,n-1))\nprint(*post)","由先序（首=根）+ 中序（根左为左子树、根右为右子树）可递归唯一重建二叉树；用哈希存中序位置可提升效率。","14基础树-A2-1","层序遍历（BFS）",{"id":10249,"title":10250,"level":6178,"module":10252,"seq":9,"goal":10253,"keywords":10254,"explanation":10255,"examples":10265,"exercises":10270,"summary":10298,"next":10299,"nextTitle":10300},"二叉树进阶（BST\u002F哈夫曼\u002F遍历）","掌握二叉树的层序遍历：用队列按层扩展，输出逐层节点，可顺便记录每层或判断完全性。",[8029,4617,7747,7748],[10256,10261],{"title":10257,"content":10258,"codePy":10259,"codeCpp":10260},"1. 队列实现","根入队；出队时把其左右孩子（非空）入队，保证逐层访问。","from collections import deque\ndef level(root):\n    if not root: return []\n    q=deque([root]); res=[]\n    while q:\n        u=q.popleft(); res.append(u.v)\n        if u.l: q.append(u.l)\n        if u.r: q.append(u.r)\n    return res","\u002F\u002F 同图的 BFS",{"title":4421,"content":10262,"codePy":10263,"codeCpp":10264},"层序可求树高、按层打印、判断完全二叉树（出现空后不应再有节点）。","print(\"层序可判完全二叉树\")","\u002F\u002F 记录每层也很容易",[10266],{"title":10267,"prompt":10268,"analysis":10269,"codePy":8222,"codeCpp":5315,"run":74},"例 1：层序","根 1 左右 2 3，层序序列？","1 2 3。",{"choice":10271,"coding":10287},[10272,10276,10282],{"id":38,"stem":10273,"options":10274,"answer":9,"explanation":10275},"层序用？",[6565,4617,1405,6210],"逐层 BFS。",{"id":47,"stem":10277,"options":10278,"answer":9,"explanation":10281},"层序首元素是？",[9866,9833,10279,10280],"最深","最左","从根开始。",{"id":56,"stem":10283,"options":10284,"answer":9,"explanation":10286},"判完全二叉树可借？",[10133,10285,10134,10135],"层序（遇空后不再有节点）","空洞即非完全。",[10288],{"id":67,"title":10289,"prompt":10290,"inputFormat":10176,"outputFormat":10291,"samples":10292,"starterPy":10294,"starterCpp":10295,"hint":10296,"referencePy":10297,"referenceCpp":10295},"层序遍历","按先序（0 空）建树，输出层序遍历序列。","一行层序序列，空格分隔。",[10293],{"input":10122,"output":74},"from collections import deque\nvals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build(); q=deque([root]); res=[]\nwhile q:\n    u=q.popleft(); res.append(u.v)\n    if u.l: q.append(u.l)\n    if u.r: q.append(u.r)\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nint main(){ int x; while(cin>>x) a.push_back(x); Node* root=build(); queue\u003CNode*> q; q.push(root); while(!q.empty()){ Node* u=q.front(); q.pop(); cout\u003C\u003Cu->v\u003C\u003C' '; if(u->l) q.push(u->l); if(u->r) q.push(u->r); } }","队列逐层扩展。","from collections import deque\nvals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build(); q=deque([root]); res=[]\nwhile q:\n    u=q.popleft(); res.append(u.v)\n    if u.l: q.append(u.l)\n    if u.r: q.append(u.r)\nprint(*res)","二叉树层序遍历用队列逐层扩展（根入队、出队时把非空左右孩子入队）；可求树高、按层打印，并借“遇空后不再有节点”判断完全性。","14基础树-A2-2","二叉树的非递归遍历（栈）",{"id":10299,"title":10300,"level":6178,"module":10252,"seq":63,"goal":10302,"keywords":10303,"explanation":10306,"examples":10318,"exercises":10322,"summary":10357,"next":10358,"nextTitle":10359},"掌握用栈模拟递归实现先\u002F中\u002F后序遍历，理解递归隐式栈的显式化，避免深递归溢出。",[10304,6565,10305,4099],"非递归","显式栈",[10307,10312],{"title":10308,"content":10309,"codePy":10310,"codeCpp":10311},"1. 先序非递归","根入栈；循环弹栈访问，先压右再压左（保证左先出）。","def pre(root):\n    if not root: return []\n    st=[root]; res=[]\n    while st:\n        u=st.pop(); res.append(u.v)\n        if u.r: st.append(u.r)\n        if u.l: st.append(u.l)\n    return res","\u002F\u002F 右先压，左后压先出",{"title":10313,"content":10314,"codePy":10315,"codeCpp":10316,"tip":10317},"2. 中序非递归","沿左走到底并压栈，弹栈访问后转向右子树。后序可用“根右左+逆序”或标记法。","print(\"中序：一直向左压栈\")","\u002F\u002F 非递归利于控制与调试","tip: 非递归=把递归的调用栈显式化。",[10319],{"title":10320,"prompt":10321,"analysis":10269,"codePy":8222,"codeCpp":5315,"run":74},"例 1：先序非递归","根 1 左 2 右 3，先序非递归序列？",{"choice":10323,"coding":10345},[10324,10332,10339],{"id":38,"stem":10325,"options":10326,"answer":9,"explanation":10331},"先序非递归压栈顺序？",[10327,10328,10329,10330],"左先右后","右先左后","仅根","随意","右先压，左先出。",{"id":47,"stem":10333,"options":10334,"answer":9,"explanation":10338},"中序非递归核心？",[10335,10336,10337,8029],"向右","一直向左压栈","弹右","左链入栈。",{"id":56,"stem":10340,"options":10341,"answer":9,"explanation":10344},"非递归遍历的好处？",[9512,10342,10343,1582],"避免深递归溢出\u002F可控","更简单","显式栈可控。",[10346],{"id":67,"title":10347,"prompt":10348,"inputFormat":10176,"outputFormat":10349,"samples":10350,"starterPy":10353,"starterCpp":10354,"hint":10355,"referencePy":10356,"referenceCpp":10354},"非递归中序","按先序（0 空）建树，用栈实现中序遍历并输出。","一行中序序列，空格分隔。",[10351],{"input":10352,"output":74},"2 1 0 0 3 0 0","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build(); st=[]; u=root; res=[]\nwhile st or u:\n    while u: st.append(u); u=u.l\n    u=st.pop(); res.append(u.v); u=u.r\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstack>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nint main(){ int x; while(cin>>x) a.push_back(x); Node* root=build(); stack\u003CNode*> st; Node* u=root; vector\u003Cint> res; while(!st.empty()||u){ while(u){ st.push(u); u=u->l; } u=st.top(); st.pop(); res.push_back(u->v); u=u->r; } for(int v:res) cout\u003C\u003Cv\u003C\u003C' '; }","一直向左压栈，弹栈访问后转右。","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build(); st=[]; u=root; res=[]\nwhile st or u:\n    while u: st.append(u); u=u.l\n    u=st.pop(); res.append(u.v); u=u.r\nprint(*res)","非递归遍历用显式栈模拟递归调用栈：先序“根右左”压栈、中序“左链入栈再转右”；既可控又避免深递归栈溢出。","14基础树-A2-3","二叉搜索树（BST）定义与查找",{"id":10358,"title":10359,"level":6178,"module":10252,"seq":703,"goal":10361,"keywords":10362,"explanation":10365,"examples":10377,"exercises":10381,"summary":10411,"next":10412,"nextTitle":10413},"理解二叉搜索树（BST）：左子树均小于根、右子树均大于根；掌握递归\u002F迭代查找，复杂度与树高相关。",[10363,10364,4624,5772],"BST","二叉搜索树",[10366,10371],{"title":10367,"content":10368,"codePy":10369,"codeCpp":10370},"1. 性质","对任意节点，左子树所有值 \u003C 根 \u003C 右子树所有值；故中序遍历为升序。","print(\"BST 中序 = 升序\")","\u002F\u002F 左\u003C根\u003C右 对所有节点成立",{"title":10372,"content":10373,"codePy":10374,"codeCpp":10375,"tip":10376},"2. 查找","从根开始：等于返回，小于走左，大于走右；平均 O(log n)，退化成链时 O(n)。","def find(u,x):\n    while u:\n        if u.v==x: return True\n        u = u.l if x\u003Cu.v else u.r\n    return False","\u002F\u002F 每步排除一半","tip: BST 平衡时查找很快，失衡会退化。",[10378],{"title":5786,"prompt":10379,"analysis":10380,"codePy":8387,"codeCpp":8388,"run":8389},"BST 根 4 左 2 右 6，查找 6？","4→右 6，找到。",{"choice":10382,"coding":10397},[10383,10389,10393],{"id":38,"stem":10384,"options":10385,"answer":9,"explanation":10388},"BST 的核心性质？",[10386,10387,5735,9996],"左>根","左\u003C根\u003C右","左小右大。",{"id":47,"stem":10390,"options":10391,"answer":9,"explanation":10392},"BST 查找平均复杂度？",[2338,1246,1245,2339],"平衡时。",{"id":56,"stem":10394,"options":10395,"answer":9,"explanation":10396},"BST 中序遍历得到？",[5328,5648,10166,8029],"左根右升序。",[10398],{"id":67,"title":10399,"prompt":10400,"inputFormat":10401,"outputFormat":10402,"samples":10403,"starterPy":10407,"starterCpp":10408,"hint":10409,"referencePy":10410,"referenceCpp":10408},"BST 查找","给定 BST 的先序（0 空）建树，查询若干值是否存在。","第一行先序序列；第二行 q 个待查值。","一行 q 个 YES\u002FNO。",[10404],{"input":10405,"output":10406},"4 2 0 0 6 0 0\n6 1","YES NO","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build()\ndef find(u,x):\n    while u:\n        if u.v==x: return True\n        u = u.l if x\u003Cu.v else u.r\n    return False\nfor x in map(int,input().split()):\n    print(\"YES\" if find(root,x) else \"NO\", end=\" \")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nbool find(Node* u,int x){ while(u){ if(u->v==x) return 1; u = x\u003Cu->v? u->l : u->r; } return 0; }\nint main(){ int x; while(cin>>x) a.push_back(x); Node* root=build(); while(cin>>x) cout\u003C\u003C(find(root,x)?\"YES \":\"NO \"); }","小于走左、大于走右。","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build()\ndef find(u,x):\n    while u:\n        if u.v==x: return True\n        u = u.l if x\u003Cu.v else u.r\n    return False\nfor x in map(int,input().split()):\n    print(\"YES\" if find(root,x) else \"NO\", end=\" \")","BST 满足“左子树\u003C根\u003C右子树”，中序为升序；查找从根比较、小于走左大于走右，平衡时平均 O(log n)，退化成链时 O(n)。","14基础树-A2-4","BST 插入",{"id":10412,"title":10413,"level":6178,"module":10252,"seq":770,"goal":10415,"keywords":10416,"explanation":10418,"examples":10429,"exercises":10437,"summary":10470,"next":10471,"nextTitle":10472},"掌握 BST 插入：沿查找路径下行，遇空位插入新节点，保持 BST 性质。",[10363,9601,10417],"保持性质",[10419,10424],{"title":10420,"content":10421,"codePy":10422,"codeCpp":10423},"1. 插入位置","从根比较，小于走左、大于走右，直到某一孩子为空，把新节点挂上。","def insert(u,x):\n    if not u: return Node(x)\n    if x\u003Cu.v: u.l=insert(u.l,x)\n    else: u.r=insert(u.r,x)\n    return u","\u002F\u002F 递归到空位挂新节点",{"title":10425,"content":10426,"codePy":10427,"codeCpp":10428},"2. 注意","插入不会改变已有结构，仅新增叶子；相同值可规定放左或放右（本项目放右）。","print(\"插入=沿查找路到空位\")","\u002F\u002F 新节点总是叶子",[10430],{"title":10431,"prompt":10432,"analysis":10433,"codePy":10434,"codeCpp":10435,"run":10436},"例 1：插入","BST 根 4 左 2，插入 3 落于？","4→左2→右(空)挂 3。","print(\"2 的右\")","\u002F\u002F 2.right","2.right",{"choice":10438,"coding":10457},[10439,10444,10449],{"id":38,"stem":10440,"options":10441,"answer":9,"explanation":10443},"BST 插入落点？",[9833,10442,1568,10279],"沿查找路到空位","保持性质。",{"id":47,"stem":10445,"options":10446,"answer":9,"explanation":10448},"新插入节点是？",[9833,9866,10447,9867],"内部","总挂为叶子。",{"id":56,"stem":10450,"options":10451,"answer":9,"explanation":10456},"插入是否会破坏已有 BST？",[10452,10453,10454,10455],"会","不会","可能","看值","只新增不改旧。",[10458],{"id":67,"title":10459,"prompt":10460,"inputFormat":1801,"outputFormat":10461,"samples":10462,"starterPy":10466,"starterCpp":10467,"hint":10468,"referencePy":10469,"referenceCpp":10467},"BST 插入+中序","依次插入若干值建 BST，输出中序序列（应升序）。","一行升序序列，空格分隔。",[10463],{"input":10464,"output":10465},"4\n4 2 6 3","2 3 4 6","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef insert(u,x):\n    if not u: return Node(x)\n    if x\u003Cu.v: u.l=insert(u.l,x)\n    else: u.r=insert(u.r,x)\n    return u\ndef ino(u):\n    return (ino(u.l) if u.l else [])+[u.v]+(ino(u.r) if u.r else [])\nroot=None\nfor x in map(int,input().split()): root=insert(root,x)\nprint(*ino(root))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nNode* insert(Node* u,int x){ if(!u) return new Node(x); if(x\u003Cu->v) u->l=insert(u->l,x); else u->r=insert(u->r,x); return u; }\nvoid ino(Node* u){ if(!u) return; ino(u->l); cout\u003C\u003Cu->v\u003C\u003C' '; ino(u->r); }\nint main(){ int n,x; cin>>n; Node* root=0; while(n--){ cin>>x; root=insert(root,x); } ino(root); }","递归到空位挂新节点，中序输出。","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef insert(u,x):\n    if not u: return Node(x)\n    if x\u003Cu.v: u.l=insert(u.l,x)\n    else: u.r=insert(u.r,x)\n    return u\ndef ino(u):\n    return (ino(u.l) if u.l else [])+[u.v]+(ino(u.r) if u.r else [])\nroot=None\nfor x in map(int,input().split()): root=insert(root,x)\nprint(*ino(root))","BST 插入沿查找路径下行（小于走左、大于走右），到空位挂新节点；新节点总是叶子，保持 BST 性质，中序仍为升序。","14基础树-A2-5","BST 删除",{"id":10471,"title":10472,"level":6178,"module":10252,"seq":838,"goal":10474,"keywords":10475,"explanation":10478,"examples":10488,"exercises":10494,"summary":10530,"next":10531,"nextTitle":10532},"掌握 BST 删除的三种情况：删叶子直接去、删单孩子接上、删双孩子用“前驱\u002F后继”替换后递归删除。",[10363,9602,10476,10477],"前驱","后继",[10479,10484],{"title":10480,"content":10481,"codePy":10482,"codeCpp":10483},"1. 三种情况","叶子：直接删。单孩子：用孩子替代。双孩子：用左子树最大（前驱）或右子树最小（后继）替换值，再删那个被替换节点。","def delete(u,x):\n    if not u: return None\n    if x\u003Cu.v: u.l=delete(u.l,x)\n    elif x>u.v: u.r=delete(u.r,x)\n    else:\n        if not u.l: return u.r\n        if not u.r: return u.l\n        # 用右子树最小替换\n        t=u.r\n        while t.l: t=t.l\n        u.v=t.v; u.r=delete(u.r,t.v)\n    return u","\u002F\u002F 双孩子：后继替换再删后继",{"title":6191,"content":10485,"codePy":10486,"codeCpp":10487},"查找待删 O(h)，替换节点也是 O(h)，总 O(h)。","print(\"删除维持 BST 性质\")","\u002F\u002F 前驱\u002F后继保证有序",[10489],{"title":10490,"prompt":10491,"analysis":10492,"codePy":10493,"codeCpp":4432,"run":624},"例 1：删根","BST 根 4 左 2 右 6，删 4 用后继？","后继 6 替上（或前驱 2）。","print(\"用6替根\")",{"choice":10495,"coding":10517},[10496,10503,10509],{"id":38,"stem":10497,"options":10498,"answer":9,"explanation":10502},"双孩子节点删除用？",[10499,10500,10501,9833],"直接删","前驱\u002F后继替换","留空","保持有序。",{"id":47,"stem":10504,"options":10505,"answer":9,"explanation":10508},"后继是？",[10506,10507,9867,3952],"左最大","右最小","右子树最左。",{"id":56,"stem":10510,"options":10511,"answer":9,"explanation":10516},"删叶子节点？",[10512,10513,10514,10515],"需替换","直接删除","不可删","变根","无孩子直接去。",[10518],{"id":67,"title":10519,"prompt":10520,"inputFormat":10521,"outputFormat":10349,"samples":10522,"starterPy":10526,"starterCpp":10527,"hint":10528,"referencePy":10529,"referenceCpp":10527},"BST 删除+中序","插入若干值建 BST，删除指定值，输出中序序列。","第一行 n 个插入值；第二行待删值。",[10523],{"input":10524,"output":10525},"4 2 6 3\n2","3 4 6","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef insert(u,x):\n    if not u: return Node(x)\n    if x\u003Cu.v: u.l=insert(u.l,x)\n    else: u.r=insert(u.r,x)\n    return u\ndef delete(u,x):\n    if not u: return None\n    if x\u003Cu.v: u.l=delete(u.l,x)\n    elif x>u.v: u.r=delete(u.r,x)\n    else:\n        if not u.l: return u.r\n        if not u.r: return u.l\n        t=u.r\n        while t.l: t=t.l\n        u.v=t.v; u.r=delete(u.r,t.v)\n    return u\ndef ino(u):\n    return (ino(u.l) if u.l else [])+[u.v]+(ino(u.r) if u.r else [])\nroot=None\nfor x in map(int,input().split()): root=insert(root,x)\nd=int(input()); root=delete(root,d)\nprint(*ino(root))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nNode* insert(Node* u,int x){ if(!u) return new Node(x); if(x\u003Cu->v) u->l=insert(u->l,x); else u->r=insert(u->r,x); return u; }\nNode* del(Node* u,int x){ if(!u) return 0; if(x\u003Cu->v) u->l=del(u->l,x); else if(x>u->v) u->r=del(u->r,x); else { if(!u->l) return u->r; if(!u->r) return u->l; Node* t=u->r; while(t->l) t=t->l; u->v=t->v; u->r=del(u->r,t->v);} return u; }\nvoid ino(Node* u){ if(!u) return; ino(u->l); cout\u003C\u003Cu->v\u003C\u003C' '; ino(u->r); }\nint main(){ int n,x; cin>>n; Node* root=0; while(n--){ cin>>x; root=insert(root,x);} cin>>x; root=del(root,x); ino(root); }","双孩子用右子树最小替换。","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef insert(u,x):\n    if not u: return Node(x)\n    if x\u003Cu.v: u.l=insert(u.l,x)\n    else: u.r=insert(u.r,x)\n    return u\ndef delete(u,x):\n    if not u: return None\n    if x\u003Cu.v: u.l=delete(u.l,x)\n    elif x>u.v: u.r=delete(u.r,x)\n    else:\n        if not u.l: return u.r\n        if not u.r: return u.l\n        t=u.r\n        while t.l: t=t.l\n        u.v=t.v; u.r=delete(u.r,t.v)\n    return u\ndef ino(u):\n    return (ino(u.l) if u.l else [])+[u.v]+(ino(u.r) if u.r else [])\nroot=None\nfor x in map(int,input().split()): root=insert(root,x)\nd=int(input()); root=delete(root,d)\nprint(*ino(root))","BST 删除：叶子直接去；单孩子用孩子替代；双孩子用右子树最小（后继）或左子树最大（前驱）替换其值，再递归删除被替换节点，始终保持 BST 性质。","14基础树-A2-6","哈夫曼树与哈夫曼编码",{"id":10531,"title":10532,"level":6178,"module":10252,"seq":936,"goal":10534,"keywords":10535,"explanation":10538,"examples":10548,"exercises":10553,"summary":10584,"next":10585,"nextTitle":10586},"掌握哈夫曼树：每次合并权值最小的两节点构造带权路径长度最小的二叉树，并生成前缀编码。",[10536,10537,7564,7562],"哈夫曼树","WPL",[10539,10543],{"title":4474,"content":10540,"codePy":10541,"codeCpp":10542},"把 n 个叶子（权值）放入最小堆，反复取两个最小合并成新节点（权为二者之和），直到剩一个根。","import heapq\ndef huff(weights):\n    h=list(weights); heapq.heapify(h)\n    while len(h)>1:\n        a=heapq.heappop(h); b=heapq.heappop(h)\n        heapq.heappush(h, a+b)\n    return h[0]  # 根权=WPL","\u002F\u002F 合并最小两堆",{"title":10544,"content":10545,"codePy":10546,"codeCpp":10547},"2. 编码","从根出发，左走记 0、右走记 1，到叶子得到该字符的码；高频字符码短。任一码不是另一码前缀。","print(\"频率高的码更短\")","\u002F\u002F 前缀码便于无歧义解码",[10549],{"title":10550,"prompt":10551,"analysis":10552,"codePy":4786,"codeCpp":4432,"run":624},"例 1：WPL","叶子权值 [1,2,3]，哈夫曼树根权（总代价）？","1+2=3, 3+3=6。",{"choice":10554,"coding":10575},[10555,10561,10568],{"id":38,"stem":10556,"options":10557,"answer":9,"explanation":10560},"哈夫曼每次合并？",[10558,10559,7589,1568],"最大两","最小两","最小两权。",{"id":47,"stem":10562,"options":10563,"answer":9,"explanation":10567},"哈夫曼树目标是？",[10279,10564,10565,10566],"WPL 最小","最满","最矮","带权路径最短。",{"id":56,"stem":10569,"options":10570,"answer":9,"explanation":10574},"哈夫曼编码特点？",[10571,10572,1568,10573],"等长","前缀码（高频短）","可歧义","无前缀歧义。",[10576],{"id":67,"title":10577,"prompt":10578,"inputFormat":1801,"outputFormat":6275,"samples":10579,"starterPy":10581,"starterCpp":7612,"hint":10582,"referencePy":10583,"referenceCpp":7612},"哈夫曼 WPL","给定 n 个叶子权值，求哈夫曼树的带权路径长度（根权之和，即合并代价总和）。",[10580],{"input":9591,"output":624},"import heapq\nn=int(input()); a=list(map(int,input().split())); heapq.heapify(a); s=0\nwhile len(a)>1:\n    x=heapq.heappop(a); y=heapq.heappop(a); s+=x+y; heapq.heappush(a,x+y)\nprint(s)\n","最小堆合并，累加每次和。","import heapq\nn=int(input()); a=list(map(int,input().split())); heapq.heapify(a); s=0\nwhile len(a)>1:\n    x=heapq.heappop(a); y=heapq.heappop(a); s+=x+y; heapq.heappush(a,x+y)\nprint(s)","哈夫曼树每次合并权值最小的两节点，使带权路径长度（WPL）最小；对应前缀编码高频字符更短且无歧义，用最小堆 O(n log n) 构造。","14基础树-A2-7","表达式树",{"id":10585,"title":10586,"level":6178,"module":10252,"seq":998,"goal":10588,"keywords":10589,"explanation":10592,"examples":10603,"exercises":10608,"summary":10639,"next":10640,"nextTitle":10641},"理解表达式树：把算式建成二叉树（叶子为操作数、内部为运算符），通过后序\u002F中序遍历求值与还原。",[10586,10590,4797,10591],"中缀","求值",[10593,10598],{"title":10594,"content":10595,"codePy":10596,"codeCpp":10597},"1. 建树","由后缀表达式建树：遇操作数建叶子入栈，遇运算符弹两操作数作左右孩子、自身为根入栈。","def build(post):\n    st=[]\n    for tok in post:\n        if tok.isdigit():\n            st.append(Node(int(tok)))\n        else:\n            b=st.pop(); a=st.pop(); r=Node(tok); r.l=a; r.r=b; st.append(r)\n    return st[0]","\u002F\u002F 后缀建树最自然",{"title":10599,"content":10600,"codePy":10601,"codeCpp":10602},"2. 求值","后序遍历递归：叶子返回数值，内部节点对左右结果施加运算。","def eval(u):\n    if isinstance(u.v,int): return u.v\n    l=eval(u.l); r=eval(u.r)\n    return {\"+\":l+r,\"*\":l*r}[u.v]","\u002F\u002F 后序求值",[10604],{"title":10605,"prompt":10606,"analysis":10607,"codePy":7705,"codeCpp":7706,"run":290},"例 1：求值","后缀 \"2 3 +\" 的表达式树求值？","2+3=5。",{"choice":10609,"coding":10625},[10610,10615,10621],{"id":38,"stem":10611,"options":10612,"answer":9,"explanation":10614},"表达式树叶子是？",[841,10613,9833,9185],"操作数","数。",{"id":47,"stem":10616,"options":10617,"answer":9,"explanation":10620},"由后缀建树遇运算符？",[9133,10618,3560,10619],"弹两作孩子","置根","弹栈顶两为左右。",{"id":56,"stem":10622,"options":10623,"answer":9,"explanation":10624},"表达式树求值用？",[10133,10135,8029,10134],"后序递归算。",[10626],{"id":67,"title":10627,"prompt":10628,"inputFormat":10629,"outputFormat":10630,"samples":10631,"starterPy":10635,"starterCpp":10636,"hint":10637,"referencePy":10638,"referenceCpp":10636},"后缀求值","给定后缀表达式（空格分隔），求值。","一行后缀表达式。","一行整数结果。",[10632],{"input":10633,"output":10634},"2 3 4 * +","14","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build(post):\n    st=[]\n    for tok in post:\n        if tok.isdigit(): st.append(Node(int(tok)))\n        else:\n            b=st.pop(); a=st.pop(); r=Node(tok); r.l=a; r.r=b; st.append(r)\n    return st[0]\ndef ev(u):\n    if isinstance(u.v,int): return u.v\n    l=ev(u.l); r=ev(u.r); return {\"+\":l+r,\"-\":l-r,\"*\":l*r,\"\u002F\":l\u002F\u002Fr}[u.v]\npost=input().split()\nprint(ev(build(post)))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstack>\n#include \u003Cstring>\nusing namespace std;\nstruct Node{ string v; Node*l,*r; Node(string x):v(x),l(0),r(0){} };\nbool isdig(string s){ for(char c:s) if(!isdigit(c)) return 0; return 1; }\nint ev(Node* u){ if(isdig(u->v)) return stoi(u->v); int a=ev(u->l),b=ev(u->r); if(u->v==\"+\") return a+b; if(u->v==\"-\") return a-b; if(u->v==\"*\") return a*b; return a\u002Fb; }\nint main(){ string t; vector\u003Cstring> post; while(cin>>t) post.push_back(t); stack\u003CNode*> st; for(string tok:post){ if(isdig(tok)) st.push(new Node(tok)); else { Node* b=st.top(); st.pop(); Node* a=st.top(); st.pop(); Node* r=new Node(tok); r->l=a; r->r=b; st.push(r);} } cout\u003C\u003Cev(st.top()); }","后缀建树，后序求值。","class Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build(post):\n    st=[]\n    for tok in post:\n        if tok.isdigit(): st.append(Node(int(tok)))\n        else:\n            b=st.pop(); a=st.pop(); r=Node(tok); r.l=a; r.r=b; st.append(r)\n    return st[0]\ndef ev(u):\n    if isinstance(u.v,int): return u.v\n    l=ev(u.l); r=ev(u.r); return {\"+\":l+r,\"-\":l-r,\"*\":l*r,\"\u002F\":l\u002F\u002Fr}[u.v]\npost=input().split()\nprint(ev(build(post)))","表达式树叶子为操作数、内部为运算符；由后缀表达式可自然建树（运算符弹两操作数作孩子），后序遍历递归求值。","14基础树-A2-8","二叉树遍历的应用",{"id":10640,"title":10641,"level":6178,"module":10252,"seq":1066,"goal":10643,"keywords":10644,"explanation":10647,"examples":10659,"exercises":10664,"summary":10695,"next":10696,"nextTitle":10697},"综合运用遍历：求树高、节点数、直径，以及“由中序+后序重建”，巩固遍历在树问题中的核心地位。",[10645,10646,5537,4099],"树高","直径",[10648,10653],{"title":10649,"content":10650,"codePy":10651,"codeCpp":10652},"1. 树高与计数","树高=1+max(左高,右高)；节点数=1+左子数+右子数，均为递归后序思想。","def height(u):\n    if not u: return 0\n    return 1+max(height(u.l), height(u.r))\ndef size(u):\n    if not u: return 0\n    return 1+size(u.l)+size(u.r)","\u002F\u002F 后序自底向上",{"title":10654,"content":10655,"codePy":10656,"codeCpp":10657,"tip":10658},"2. 直径","直径=某节点左右子树高度之和的最大值；递归时维护全局最大。","def diameter(u):\n    best=[0]\n    def h(u):\n        if not u: return 0\n        l=h(u.l); r=h(u.r); best[0]=max(best[0], l+r)\n        return 1+max(l,r)\n    h(u); return best[0]","\u002F\u002F 经过某节点的最长路径","tip: 树的许多问题都是“后序+维护全局量”。",[10660],{"title":10661,"prompt":10662,"analysis":10663,"codePy":6253,"codeCpp":5851,"run":759},"例 1：树高","根 1 左 2 右 3，树高？","max(1,1)+1=2。",{"choice":10665,"coding":10683},[10666,10671,10677],{"id":38,"stem":10667,"options":10668,"answer":9,"explanation":10670},"树高计算用？",[10133,10669,10134,8029],"后序（1+max）","自底向上。",{"id":47,"stem":10672,"options":10673,"answer":9,"explanation":10676},"直径涉及？",[10674,10675,9833,9866],"单子树","左右高之和最大","过某节点最长路。",{"id":56,"stem":10678,"options":10679,"answer":9,"explanation":10682},"节点数递归？",[10680,10681,8029,10134],"前序计数","1+左+右","后序累加。",[10684],{"id":67,"title":10685,"prompt":10686,"inputFormat":10176,"outputFormat":10687,"samples":10688,"starterPy":10691,"starterCpp":10692,"hint":10693,"referencePy":10694,"referenceCpp":10692},"树高与直径","按先序（0 空）建树，输出树高与直径。","一行：height diameter。",[10689],{"input":10122,"output":10690},"2 2","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\ndef height(u):\n    return 0 if not u else 1+max(height(u.l),height(u.r))\nbest=[0]\ndef diam(u):\n    if not u: return 0\n    l=diam(u.l); r=diam(u.r); best[0]=max(best[0],l+r); return 1+max(l,r)\ndiam(build())\nprint(height(root) if (root:=build()) else 0, best[0])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Node{ int v; Node*l,*r; Node(int x):v(x),l(0),r(0){} };\nvector\u003Cint> a; int p=0; int best=0;\nNode* build(){ int v=a[p++]; if(v==0) return 0; Node* n=new Node(v); n->l=build(); n->r=build(); return n; }\nint height(Node* u){ return u? 1+max(height(u->l),height(u->r)) : 0; }\nint diam(Node* u){ if(!u) return 0; int l=diam(u->l),r=diam(u->r); best=max(best,l+r); return 1+max(l,r); }\nint main(){ int x; while(cin>>x) a.push_back(x); Node* root=build(); int h=height(root); diam(root); cout\u003C\u003Ch\u003C\u003C\" \"\u003C\u003Cbest; }","高度 1+max；直径维护全局左右和。","vals=list(map(int,input().split())); it=[0]\nclass Node:\n    def __init__(s,v): s.v=v; s.l=None; s.r=None\ndef build():\n    v=vals[it[0]]; it[0]+=1\n    if v==0: return None\n    n=Node(v); n.l=build(); n.r=build(); return n\nroot=build()\ndef height(u):\n    return 0 if not u else 1+max(height(u.l),height(u.r))\nbest=[0]\ndef diam(u):\n    if not u: return 0\n    l=diam(u.l); r=diam(u.r); best[0]=max(best[0],l+r); return 1+max(l,r)\ndiam(root)\nprint(height(root), best[0])","树高=1+max(左高,右高)、节点数=1+左+右，均为后序思想；直径是“左右子树高度和的最大值”。树的多数问题都可借遍历+维护全局量解决。","15-A1-1","堆的定义与性质",{"id":10696,"title":10697,"level":6178,"module":10699,"seq":9,"goal":10700,"keywords":10701,"explanation":10703,"examples":10714,"exercises":10722,"summary":10746,"next":10747,"nextTitle":10748},"堆与优先队列","理解堆：一种完全二叉树，且父节点“不小于\u002F不大于”孩子（大根\u002F小根），根即最值，支持 O(log n) 取最值。",[6210,9956,10702,4231],"堆序",[10704,10709],{"title":10705,"content":10706,"codePy":10707,"codeCpp":10708},"1. 堆序性质","大根堆：每个节点 ≥ 孩子；小根堆：每个节点 ≤ 孩子。因此根始终是最大\u002F最小值。","print(\"大根堆：父>=子；根最大\")","\u002F\u002F 堆是优先队列的底层",{"title":10710,"content":10711,"codePy":10712,"codeCpp":10713},"2. 存储","用数组按下标表示（i 的父 ⌊i\u002F2⌋，左 2i，右 2i+1），无需指针。","print(\"堆用数组紧凑存储\")","\u002F\u002F 同完全二叉树",[10715],{"title":10716,"prompt":10717,"analysis":10718,"codePy":10719,"codeCpp":10720,"run":10721},"例 1：根","大根堆中根节点是？","最大值。","print(\"最大\")","\u002F\u002F max","max",{"choice":10723,"coding":10735},[10724,10728,10731],{"id":38,"stem":10725,"options":10726,"answer":9,"explanation":10727},"大根堆根节点是？",[5983,5984,8470,1568],"父>=子。",{"id":47,"stem":10729,"options":10730,"answer":9,"explanation":9999},"堆是哪种树形？",[3952,9956,9998,9996],{"id":56,"stem":10732,"options":10733,"answer":63,"explanation":10734},"取最值复杂度？",[2338,1246,1245,2339],"根即最值 O(1) 访问。",[10736],{"id":67,"title":10737,"prompt":10738,"inputFormat":1801,"outputFormat":9140,"samples":10739,"starterPy":10742,"starterCpp":10743,"hint":10744,"referencePy":10745,"referenceCpp":10743},"判断大根堆","给定按层序（从 1 编号）的堆数组，判断是否是大根堆（每个父≥子）。",[10740],{"input":10741,"output":8418},"3\n5 3 4","n=int(input()); a=[0]+list(map(int,input().split()))\nok=True\nfor i in range(1,n+1):\n    if 2*i\u003C=n and a[i]\u003Ca[2*i]: ok=False\n    if 2*i+1\u003C=n and a[i]\u003Ca[2*i+1]: ok=False\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n+1); for(int i=1;i\u003C=n;i++) cin>>a[i]; bool ok=1; for(int i=1;i\u003C=n;i++){ if(2*i\u003C=n&&a[i]\u003Ca[2*i]) ok=0; if(2*i+1\u003C=n&&a[i]\u003Ca[2*i+1]) ok=0; } cout\u003C\u003C(ok?\"YES\":\"NO\"); }","检查每个父≥左右孩子。","n=int(input()); a=[0]+list(map(int,input().split()))\nok=True\nfor i in range(1,n+1):\n    if 2*i\u003C=n and a[i]\u003Ca[2*i]: ok=False\n    if 2*i+1\u003C=n and a[i]\u003Ca[2*i+1]: ok=False\nprint(\"YES\" if ok else \"NO\")","堆是完全二叉树且满足堆序（大根：父≥子；小根：父≤子），根即最值，用数组按下标紧凑存储，是优先队列的底层结构。","15-A1-2","大根堆与小根堆",{"id":10747,"title":10748,"level":6178,"module":10699,"seq":63,"goal":10750,"keywords":10751,"explanation":10752,"examples":10763,"exercises":10768,"summary":10801,"next":10802,"nextTitle":10803},"区分大根堆（取最大）与小根堆（取最小），并理解在 STL\u002FPython 中如何用优先队列实现两者。",[7600,7601,7772,939],[10753,10758],{"title":10754,"content":10755,"codePy":10756,"codeCpp":10757},"1. 选用","求 TopK 最大用大根堆保留；求最小用大根堆淘汰大值（或维护大小为 K 的小根堆）。Dijkstra 用最小堆取最近点。","import heapq\nh=[]; heapq.heappush(h, 3); heapq.heappush(h, 1)\nprint(h[0])  # 小根堆取最小","\u002F\u002F priority_queue\u003Cint> 默认大根；小根用 greater\u003Cint>",{"title":10759,"content":10760,"codePy":10761,"codeCpp":10762},"2. 标准库","Python heapq 是小根堆；C++ priority_queue 默认大根堆，声明 greater\u003CT> 得小根堆。","print(\"heapq 小根；pq 默认大根\")","\u002F\u002F 注意语言默认差异",[10764],{"title":10765,"prompt":10766,"analysis":10767,"codePy":7878,"codeCpp":7879,"run":892},"例 1：最小","小根堆 [3,1,2]，堆顶？","1。",{"choice":10769,"coding":10787},[10770,10775,10782],{"id":38,"stem":10771,"options":10772,"answer":9,"explanation":10774},"Python heapq 默认是？",[7600,7601,10773,5735],"二叉","取最小。",{"id":47,"stem":10776,"options":10777,"answer":9,"explanation":10781},"C++ priority_queue 默认？",[10778,10779,10780,1568],"小根","大根","按插入","默认大根。",{"id":56,"stem":10783,"options":10784,"answer":607,"explanation":10786},"求第 K 大可用？",[10785,6565,4617,1434],"大小为K的小根堆","堆顶即第K大。",[10788],{"id":67,"title":10789,"prompt":10790,"inputFormat":10791,"outputFormat":10792,"samples":10793,"starterPy":10797,"starterCpp":10798,"hint":10799,"referencePy":10800,"referenceCpp":10798},"TopK 最大","n 个数中取最大的 K 个，输出（任意顺序）。","第一行 n K；第二行 n 个整数。","一行 K 个整数。",[10794],{"input":10795,"output":10796},"5 2\n3 1 4 1 5","5 4","import heapq\nn,k=map(int,input().split()); a=list(map(int,input().split()))\nh=[]\nfor x in a:\n    heapq.heappush(h,x)\n    if len(h)>k: heapq.heappop(h)\nprint(*h)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; priority_queue\u003Cint,vector\u003Cint>,greater\u003Cint>> pq; for(int i=0,x;i\u003Cn;i++){ cin>>x; pq.push(x); if(pq.size()>k) pq.pop(); } while(!pq.empty()){ cout\u003C\u003Cpq.top()\u003C\u003C' '; pq.pop(); } }","小根堆维护 K 个最大。","import heapq\nn,k=map(int,input().split()); a=list(map(int,input().split()))\nh=[]\nfor x in a:\n    heapq.heappush(h,x)\n    if len(h)>k: heapq.heappop(h)\nprint(*h)","大根堆取最大、小根堆取最小；Python heapq 默认小根堆，C++ priority_queue 默认大根堆。维护大小为 K 的小根堆即可得到 TopK 最大。","15-A1-3","堆的插入与删除（上浮\u002F下沉）",{"id":10802,"title":10803,"level":6178,"module":10699,"seq":703,"goal":10805,"keywords":10806,"explanation":10811,"examples":10823,"exercises":10827,"summary":10856,"next":10857,"nextTitle":10858},"掌握堆的核心操作：插入时“上浮”（与父比较交换），删除堆顶时“下沉”（与较小\u002F较大孩子比较交换），均 O(log n)。",[10807,10808,10809,10810],"上浮","下沉","sift","调整",[10812,10817],{"title":10813,"content":10814,"codePy":10815,"codeCpp":10816},"1. 插入上浮","新元素放末尾，若比父更“优”就与父交换，直到满足堆序。","def push(h,x):\n    h.append(x); i=len(h)-1\n    while i>0 and h[i]>h[(i-1)\u002F\u002F2]:\n        h[i],h[(i-1)\u002F\u002F2]=h[(i-1)\u002F\u002F2],h[i]; i=(i-1)\u002F\u002F2","\u002F\u002F 向上调整",{"title":10818,"content":10819,"codePy":10820,"codeCpp":10821,"tip":10822},"2. 删除下沉","用末尾元素替补堆顶，再与更优的孩子比较下滤到正确位置。","def pop(h):\n    h[0]=h[-1]; h.pop()\n    i=0; n=len(h)\n    while 2*i+1\u003Cn:\n        c=2*i+1\n        if c+1\u003Cn and h[c+1]>h[c]: c+=1\n        if h[i]>=h[c]: break\n        h[i],h[c]=h[c],h[i]; i=c","\u002F\u002F 向下调整","tip: 上浮\u002F下沉保证 O(log n)。",[10824],{"title":10431,"prompt":10825,"analysis":10826,"codePy":4786,"codeCpp":4432,"run":624},"大根堆 [5,3,4] 插入 6，堆顶？","6 上浮成根，堆顶 6。",{"choice":10828,"coding":10842},[10829,10833,10838],{"id":38,"stem":10830,"options":10831,"answer":9,"explanation":10832},"插入用什么调整？",[10808,10807,3117,10191],"与父比较。",{"id":47,"stem":10834,"options":10835,"answer":9,"explanation":10837},"删除堆顶用什么？",[10807,10808,10836,4166],"出队","与孩子比较下滤。",{"id":56,"stem":10839,"options":10840,"answer":9,"explanation":10841},"单次操作复杂度？",[2338,1246,1245,2339],"树高 log n。",[10843],{"id":67,"title":10844,"prompt":10845,"inputFormat":10846,"outputFormat":10847,"samples":10848,"starterPy":10852,"starterCpp":10853,"hint":10854,"referencePy":10855,"referenceCpp":10853},"手写大根堆","实现大根堆：支持插入若干数，并依次弹出堆顶，输出弹出序列。","第一行 n；第二行 n 个整数（依次插入）。","一行 n 个整数（弹出顺序，降序）。",[10849],{"input":10850,"output":10851},"4\n3 1 4 2","4 3 2 1","n=int(input()); a=list(map(int,input().split()))\nh=[]\ndef push(x):\n    h.append(x); i=len(h)-1\n    while i>0 and h[i]>h[(i-1)\u002F\u002F2]:\n        p=(i-1)\u002F\u002F2; h[i],h[p]=h[p],h[i]; i=p\ndef pop():\n    h[0]=h[-1]; h.pop(); i=0; m=len(h)\n    while 2*i+1\u003Cm:\n        c=2*i+1\n        if c+1\u003Cm and h[c+1]>h[c]: c+=1\n        if h[i]>=h[c]: break\n        h[i],h[c]=h[c],h[i]; i=c\nfor x in a: push(x)\nres=[]\nwhile h: res.append(h[0]); pop()\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> h;\nvoid push(int x){ h.push_back(x); int i=h.size()-1; while(i>0&&h[i]>h[(i-1)\u002F2]){ int p=(i-1)\u002F2; swap(h[i],h[p]); i=p; } }\nvoid pop(){ h[0]=h.back(); h.pop_back(); int i=0,m=h.size(); while(2*i+1\u003Cm){ int c=2*i+1; if(c+1\u003Cm&&h[c+1]>h[c]) c++; if(h[i]>=h[c]) break; swap(h[i],h[c]); i=c; } }\nint main(){ int n,x; cin>>n; for(int i=0;i\u003Cn;i++){ cin>>x; push(x);} while(!h.empty()){ cout\u003C\u003Ch[0]\u003C\u003C' '; pop(); } }","插入上浮、删除下沉。","n=int(input()); a=list(map(int,input().split()))\nh=[]\ndef push(x):\n    h.append(x); i=len(h)-1\n    while i>0 and h[i]>h[(i-1)\u002F\u002F2]:\n        p=(i-1)\u002F\u002F2; h[i],h[p]=h[p],h[i]; i=p\ndef pop():\n    h[0]=h[-1]; h.pop(); i=0; m=len(h)\n    while 2*i+1\u003Cm:\n        c=2*i+1\n        if c+1\u003Cm and h[c+1]>h[c]: c+=1\n        if h[i]>=h[c]: break\n        h[i],h[c]=h[c],h[i]; i=c\nfor x in a: push(x)\nres=[]\nwhile h: res.append(h[0]); pop()\nprint(*res)","堆插入把新元素放末尾并“上浮”（与父比较交换）到正确位置；删除堆顶用末尾替补并“下沉”（与更优孩子比较）调整；二者均 O(log n)。","15-A1-4","堆排序与优先队列应用",{"id":10857,"title":10858,"level":6178,"module":10699,"seq":770,"goal":10860,"keywords":10861,"explanation":10863,"examples":10873,"exercises":10878,"summary":10902,"next":10903,"nextTitle":10904},"掌握堆排序（原地建堆后反复取堆顶放末尾，O(n log n)）及优先队列在 TopK、中位数、Dijkstra 等场景的应用。",[5106,7772,10862,5537],"TopK",[10864,10869],{"title":10865,"content":10866,"codePy":10867,"codeCpp":10868},"1. 堆排序","先自底向上建大根堆，再反复把堆顶（最大）与末尾交换并下沉，得到升序。","def heap_sort(a):\n    n=len(a)\n    for i in range(n\u002F\u002F2-1,-1,-1): sift(a,i,n)\n    for i in range(n-1,0,-1):\n        a[0],a[i]=a[i],a[0]; sift(a,0,i)\n    return a","\u002F\u002F 建堆 O(n)，每次下沉 O(log n)",{"title":4421,"content":10870,"codePy":10871,"codeCpp":10872},"优先队列=动态取最值：TopK、滑动窗口中位数、Dijkstra 取最近未访问点。","print(\"优先队列=动态最值结构\")","\u002F\u002F 比每次扫快",[10874],{"title":10875,"prompt":10876,"analysis":10877,"codePy":8222,"codeCpp":5315,"run":74},"例 1：排序","[3,1,2] 堆排序结果？","升序 1 2 3。",{"choice":10879,"coding":10893},[10880,10884,10889],{"id":38,"stem":10881,"options":10882,"answer":9,"explanation":10883},"堆排序复杂度？",[2338,2564,2339,1246],"建堆+下沉。",{"id":47,"stem":10885,"options":10886,"answer":9,"explanation":10888},"堆排序是否原地？",[5046,5045,10887,4931],"需额外数组","原地交换。",{"id":56,"stem":10890,"options":10891,"answer":9,"explanation":10892},"Dijkstra 用哪种堆？",[10779,10778,6565,4617],"取最近点。",[10894],{"id":67,"title":5106,"prompt":10895,"inputFormat":1801,"outputFormat":10461,"samples":10896,"starterPy":10898,"starterCpp":10899,"hint":10900,"referencePy":10901,"referenceCpp":10899},"对 n 个整数做升序堆排序。",[10897],{"input":6278,"output":74},"def sift(a,i,n):\n    while 2*i+1\u003Cn:\n        c=2*i+1\n        if c+1\u003Cn and a[c+1]>a[c]: c+=1\n        if a[i]>=a[c]: break\n        a[i],a[c]=a[c],a[i]; i=c\ndef hs(a):\n    n=len(a)\n    for i in range(n\u002F\u002F2-1,-1,-1): sift(a,i,n)\n    for i in range(n-1,0,-1):\n        a[0],a[i]=a[i],a[0]; sift(a,0,i)\n    return a\nn=int(input()); a=list(map(int,input().split())); print(*hs(a))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvoid sift(vector\u003Cint>& a,int i,int n){ while(2*i+1\u003Cn){ int c=2*i+1; if(c+1\u003Cn&&a[c+1]>a[c]) c++; if(a[i]>=a[c]) break; swap(a[i],a[c]); i=c; } }\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int i=n\u002F2-1;i>=0;i--) sift(a,i,n); for(int i=n-1;i>0;i--){ swap(a[0],a[i]); sift(a,0,i);} for(int x:a) cout\u003C\u003Cx\u003C\u003C' '; }","自底建堆再下沉排序。","def sift(a,i,n):\n    while 2*i+1\u003Cn:\n        c=2*i+1\n        if c+1\u003Cn and a[c+1]>a[c]: c+=1\n        if a[i]>=a[c]: break\n        a[i],a[c]=a[c],a[i]; i=c\ndef hs(a):\n    n=len(a)\n    for i in range(n\u002F\u002F2-1,-1,-1): sift(a,i,n)\n    for i in range(n-1,0,-1):\n        a[0],a[i]=a[i],a[0]; sift(a,0,i)\n    return a\nn=int(input()); a=list(map(int,input().split())); print(*hs(a))","堆排序先自底向上建大根堆，再反复交换堆顶到末尾并下沉，原地得到升序，复杂度 O(n log n)；优先队列（堆）是动态取最值的通用结构（TopK、Dijkstra 等）。","22DP基础-A1-1","动态规划基本思想",{"id":9827,"title":9828,"level":6350,"module":10699,"seq":9,"goal":10906,"keywords":10907,"explanation":10910,"examples":10921,"exercises":10927,"summary":10952,"next":10953,"nextTitle":5106},"掌握二叉堆（优先队列）：Python 用 heapq（默认最小堆），C++ 用 priority_queue；理解 push\u002Fpop\u002Ftop 与“取最小\u002F最大”的语义。",[6210,7772,10908,10909],"heapq","priority_queue",[10911,10916],{"title":10912,"content":10913,"codePy":10914,"codeCpp":10915},"1. 最小堆与最大堆","Python heapq 是最小堆；要最大堆可存入负值。C++ priority_queue 默认最大堆（顶部最大）。","import heapq\nh=[]\nheapq.heappush(h, 3); heapq.heappush(h, 1)\nprint(heapq.heappop(h))  # 1","\u002F\u002F C++: priority_queue\u003Cint> pq; pq.top() 最大",{"title":4421,"content":10917,"codePy":10918,"codeCpp":10919,"tip":10920},"堆适合“反复取最值”的场景：合并果子、TopK、Dijkstra 的贪心选点。","print(\"取最值的利器\")","\u002F\u002F 入 O(log n) 出 O(log n)","tip: Python 取堆顶用 h[0]，弹出用 heappop。",[10922],{"title":10923,"prompt":10924,"analysis":10925,"codePy":10926,"codeCpp":5315,"run":74},"例 1：最小堆","依次入 3,1,2，弹出顺序？","1,2,3（最小优先）。","print(1,2,3)",{"choice":10928,"coding":10941},[10929,10932,10935],{"id":38,"stem":10771,"options":10930,"answer":9,"explanation":10931},[7724,7562,10364,6565],"heapq 最小堆。",{"id":47,"stem":10776,"options":10933,"answer":9,"explanation":10934},[7562,7724,4617,6565],"默认顶部最大。",{"id":56,"stem":10936,"options":10937,"answer":9,"explanation":10940},"Python 实现最大堆方法？",[6810,10938,10939,3117],"存负值","改库","取负=最大堆。",[10942],{"id":67,"title":10943,"prompt":10944,"inputFormat":10945,"outputFormat":6275,"samples":10946,"starterPy":10948,"starterCpp":10949,"hint":10950,"referencePy":10951,"referenceCpp":10949},"合并果子","n 堆果子，每次合并两堆，代价为两堆重量之和；求把所有果子合成一堆的最小总代价。","第一行 n；第二行 n 个整数（每堆重量）。",[10947],{"input":7610,"output":291},"import heapq\nn=int(input()); a=list(map(int,input().split())); heapq.heapify(a)\nans=0\nwhile len(a)>1:\n    x=heapq.heappop(a); y=heapq.heappop(a); s=x+y; ans+=s; heapq.heappush(a,s)\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nint main(){ int n; cin>>n; priority_queue\u003Clong long, vector\u003Clong long>, greater\u003Clong long>> pq; for(int i=0,x;i\u003Cn;i++){ cin>>x; pq.push(x);} long long ans=0; while(pq.size()>1){ long long x=pq.top(); pq.pop(); long long y=pq.top(); pq.pop(); long long s=x+y; ans+=s; pq.push(s);} cout\u003C\u003Cans; }","每次取最小两堆合并。","import heapq\nn=int(input()); a=list(map(int,input().split())); heapq.heapify(a)\nans=0\nwhile len(a)>1:\n    x=heapq.heappop(a); y=heapq.heappop(a); s=x+y; ans+=s; heapq.heappush(a,s)\nprint(ans)","堆（优先队列）支持 O(log n) 的插入与取最值：Python heapq 默认最小堆（最大堆存负值），C++ priority_queue 默认最大堆；是合并果子、TopK、Dijkstra 等贪心算法的核心。","15-A2-2",{"id":10953,"title":5106,"level":6350,"module":10699,"seq":63,"goal":10955,"keywords":10956,"explanation":10959,"examples":10971,"exercises":10976,"summary":11004,"next":11005,"nextTitle":11006},"理解堆排序：先建堆（heapify），再反复取堆顶与末尾交换并下沉，得到升序序列；理解原地排序与 O(n log n)。",[5106,10957,10808,10958],"heapify","原地排序",[10960,10965],{"title":10961,"content":10962,"codePy":10963,"codeCpp":10964},"1. 自底向上建堆","从最后一个非叶节点向上调整，使整棵完全二叉树满足堆性质，O(n)。","def heapify(a, n, i):\n    l=2*i+1; r=2*i+2; mx=i\n    if l\u003Cn and a[l]>a[mx]: mx=l\n    if r\u003Cn and a[r]>a[mx]: mx=r\n    if mx!=i: a[i],a[mx]=a[mx],a[i]; heapify(a,n,mx)","\u002F\u002F 下沉调整",{"title":10966,"content":10967,"codePy":10968,"codeCpp":10969,"tip":10970},"2. 排序","建大顶堆后，每次把堆顶（最大）换到末尾，堆大小减一并下沉堆顶，重复得升序。","def heapsort(a):\n    n=len(a)\n    for i in range(n\u002F\u002F2-1,-1,-1): heapify(a,n,i)\n    for i in range(n-1,0,-1):\n        a[0],a[i]=a[i],a[0]; heapify(a,i,0)\n    return a","\u002F\u002F 取顶换尾再下沉","tip: 大顶堆 + 末交换 = 升序；小顶堆则降序。",[10972],{"title":10973,"prompt":10974,"analysis":10975,"codePy":5263,"codeCpp":5264,"run":2212},"例 1：堆排","[4,1,3,2] 排序结果？","升序 1,2,3,4。",{"choice":10977,"coding":10994},[10978,10982,10986],{"id":38,"stem":10979,"options":10980,"answer":9,"explanation":10981},"建堆复杂度？",[2564,2338,2339,1245],"自底向上 O(n)。",{"id":47,"stem":10983,"options":10984,"answer":9,"explanation":10985},"堆排序总复杂度？",[2338,2564,2339,1246],"n 次 O(log n) 下沉。",{"id":56,"stem":10987,"options":10988,"answer":9,"explanation":10993},"升序用？",[10989,10990,10991,10992],"小顶堆","大顶堆+换尾","普通堆","双堆","大顶堆取最大放末尾。",[10995],{"id":67,"title":5106,"prompt":10996,"inputFormat":1801,"outputFormat":5404,"samples":10997,"starterPy":11000,"starterCpp":11001,"hint":11002,"referencePy":11003,"referenceCpp":11001},"对 n 个整数做升序堆排序并输出。",[10998],{"input":10999,"output":2212},"4\n4 1 3 2","n=int(input()); a=list(map(int,input().split()))\ndef down(a,n,i):\n    while True:\n        l=2*i+1; r=2*i+2; mx=i\n        if l\u003Cn and a[l]>a[mx]: mx=l\n        if r\u003Cn and a[r]>a[mx]: mx=r\n        if mx==i: break\n        a[i],a[mx]=a[mx],a[i]; i=mx\nfor i in range(n\u002F\u002F2-1,-1,-1): down(a,n,i)\nfor i in range(n-1,0,-1):\n    a[0],a[i]=a[i],a[0]; down(a,i,0)\nprint(*a)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvoid down(vector\u003Cint>& a,int n,int i){ while(1){ int l=2*i+1,r=2*i+2,mx=i; if(l\u003Cn&&a[l]>a[mx])mx=l; if(r\u003Cn&&a[r]>a[mx])mx=r; if(mx==i)break; swap(a[i],a[mx]); i=mx; } }\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int i=n\u002F2-1;i>=0;i--) down(a,n,i); for(int i=n-1;i>0;i--){ swap(a[0],a[i]); down(a,i,0);} for(int x:a) cout\u003C\u003Cx\u003C\u003C\" \"; }","建大顶堆后取顶换尾下沉。","n=int(input()); a=list(map(int,input().split()))\ndef down(a,n,i):\n    while True:\n        l=2*i+1; r=2*i+2; mx=i\n        if l\u003Cn and a[l]>a[mx]: mx=l\n        if r\u003Cn and a[r]>a[mx]: mx=r\n        if mx==i: break\n        a[i],a[mx]=a[mx],a[i]; i=mx\nfor i in range(n\u002F\u002F2-1,-1,-1): down(a,n,i)\nfor i in range(n-1,0,-1):\n    a[0],a[i]=a[i],a[0]; down(a,i,0)\nprint(*a)","堆排序先自底向上 O(n) 建大顶堆，再 n-1 次“堆顶与末尾交换并下沉”得到升序；整体 O(n log n) 且原地，无需额外空间。","15-A2-3","第 k 大 \u002F 小元素",{"id":11005,"title":11006,"level":6350,"module":10699,"seq":703,"goal":11008,"keywords":11009,"explanation":11012,"examples":11024,"exercises":11029,"summary":11055,"next":11056,"nextTitle":11057},"用堆在 O(n log k) 求第 k 大\u002F小元素：维护一个大小为 k 的堆，或基于快速选择；理解堆法比全排序更省。",[11010,10862,6210,11011],"第k大","快速选择",[11013,11018],{"title":11014,"content":11015,"codePy":11016,"codeCpp":11017},"1. 堆法","求第 k 大：维护大小为 k 的最小堆，遍历数组，堆满且当前更大则替换堆顶；最终堆顶即第 k 大。","def kth(a, k):\n    import heapq\n    h=[]\n    for x in a:\n        heapq.heappush(h, x)\n        if len(h)>k: heapq.heappop(h)\n    return h[0]","\u002F\u002F 堆顶=第 k 大",{"title":11019,"content":11020,"codePy":11021,"codeCpp":11022,"tip":11023},"2. 快速选择","类似快排 partition，只递归含第 k 的那一侧，平均 O(n)。","print(\"均摊更快但要写partition\")","\u002F\u002F 最坏 O(n²)","tip: 数据流式或 k 较小时堆法更稳。",[11025],{"title":11026,"prompt":11027,"analysis":11028,"codePy":6544,"codeCpp":6545,"run":890},"例 1：第2大","[3,1,4,2]，第 2 大？","4 最大，3 第 2 大。",{"choice":11030,"coding":11044},[11031,11035,11040],{"id":38,"stem":11032,"options":11033,"answer":9,"explanation":11034},"第 k 大用大小为 k 的？",[7724,7562,10992,6565],"最小堆顶=第k大。",{"id":47,"stem":11036,"options":11037,"answer":607,"explanation":11039},"堆法求第k大复杂度？",[11038,2564,2338,2339],"O(n log k)","每次 log k。",{"id":56,"stem":11041,"options":11042,"answer":607,"explanation":11043},"快速选择平均复杂度？",[2338,2564,2339,1246],"均摊线性。",[11045],{"id":67,"title":11046,"prompt":11047,"inputFormat":9083,"outputFormat":6275,"samples":11048,"starterPy":11051,"starterCpp":11052,"hint":11053,"referencePy":11054,"referenceCpp":11052},"数组中的第 K 个最大元素","给定 n 个整数与 k（1≤k≤n），返回第 k 个最大的元素（不去重）。",[11049],{"input":11050,"output":890},"4 2\n3 1 4 2","import heapq\nn,k=map(int,input().split()); a=list(map(int,input().split())); h=[]\nfor x in a:\n    heapq.heappush(h,x)\n    if len(h)>k: heapq.heappop(h)\nprint(h[0])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; priority_queue\u003Cint,vector\u003Cint>,greater\u003Cint>> pq; for(int i=0,x;i\u003Cn;i++){ cin>>x; pq.push(x); if((int)pq.size()>k) pq.pop(); } cout\u003C\u003Cpq.top(); }","维护大小为 k 的最小堆。","import heapq\nn,k=map(int,input().split()); a=list(map(int,input().split())); h=[]\nfor x in a:\n    heapq.heappush(h,x)\n    if len(h)>k: heapq.heappop(h)\nprint(h[0])","求第 k 大元素：维护大小为 k 的最小堆，遍历数组时堆满且当前值更大则替换堆顶，最终堆顶即第 k 大，复杂度 O(n log k)；也可用快速选择做到平均 O(n)。","15-A2-4","多路归并与 TopK 综合",{"id":11056,"title":11057,"level":6350,"module":10699,"seq":770,"goal":11059,"keywords":11060,"explanation":11063,"examples":11075,"exercises":11078,"summary":11105,"next":11106,"nextTitle":11107},"掌握用堆合并多个有序序列（如 K 个有序链表、数据流 TopK 汇总），理解“每个序列一个指针 + 最小堆选最小”的模式。",[11061,11062,7562,1435],"多路归并","K路",[11064,11069],{"title":11065,"content":11066,"codePy":11067,"codeCpp":11068},"1. 合并 K 个有序链表","用最小堆存 (值, 队列编号, 节点)，每次取最小接到结果，并把该队列的下一节点入堆。","import heapq\ndef mergeKLists(heads):\n    h=[]\n    for i,nd in enumerate(heads):\n        if nd: heapq.heappush(h,(nd.v,i,nd))\n    res=[]\n    while h:\n        v,i,nd=heapq.heappop(h); res.append(v)\n        if nd.next: heapq.heappush(h,(nd.next.v,i,nd.next))\n    return res","\u002F\u002F 堆中选最小 + 补入下一节点",{"title":11070,"content":11071,"codePy":11072,"codeCpp":11073,"tip":11074},"2. 数据流 TopK","维护大小为 K 的最小堆，新元素更大则替换堆顶，结束时堆中即 TopK。","print(\"多路归并也是同一套路\")","\u002F\u002F 指针+堆","tip: 堆元素需可比较，元组 (值, 编号) 避免值相等时比较节点。",[11076],{"title":5260,"prompt":11077,"analysis":9793,"codePy":5263,"codeCpp":5264,"run":2212},"链表 1->4 与 2->3 合并？",{"choice":11079,"coding":11093},[11080,11084,11089],{"id":38,"stem":11081,"options":11082,"answer":9,"explanation":11083},"合并K个有序链表用？",[5459,7562,3259,6565],"堆选最小并补下一节点。",{"id":47,"stem":11085,"options":11086,"answer":9,"explanation":11088},"堆中为何存(值,编号)？",[8799,11087,3117,4166],"值相等时按编号比较避免比节点","节点不可直接比较。",{"id":56,"stem":11090,"options":11091,"answer":9,"explanation":10786},"数据流TopK维护？",[7724,11092,6565,4617],"大小为K的最小堆",[11094],{"id":67,"title":11095,"prompt":11096,"inputFormat":11097,"outputFormat":5404,"samples":11098,"starterPy":11101,"starterCpp":11102,"hint":11103,"referencePy":11104,"referenceCpp":11102},"合并 K 个有序链表","给定 K 个升序单链表，合并为一个升序链表返回新头（K≤20，各长≤1000）。","第一行 K 与各自长度；随后 K 行每行一个升序链表。",[11099],{"input":11100,"output":2212},"2 2 2\n1 4\n2 3","import heapq,sys\ninp=sys.stdin.read().split(); it=iter(inp)\nK=int(next(it)); lens=[int(next(it)) for _ in range(K)]\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nheads=[]\nfor li in range(K):\n    h=None\n    for _ in range(lens[li]):\n        h=Node(int(next(it)),h)\n    heads.append(h)\nh=[]\nfor i,nd in enumerate(heads):\n    if nd: heapq.heappush(h,(nd.v,i,nd))\nres=[]\nwhile h:\n    v,i,nd=heapq.heappop(h); res.append(v)\n    if nd.next: heapq.heappush(h,(nd.next.v,i,nd.next))\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nstruct Node{ int v; Node* next; Node(int v,Node* n=NULL):v(v),next(n){} };\nint main(){ int K; cin>>K; vector\u003Cint> lens(K); for(int i=0;i\u003CK;i++) cin>>lens[i]; vector\u003CNode*> heads(K,NULL); for(int i=0;i\u003CK;i++){ Node* h=NULL; for(int j=0;j\u003Clens[i];j++){ int x; cin>>x; h=new Node(x,h);} heads[i]=h; } priority_queue\u003Cpair\u003Cint,int>,vector\u003Cpair\u003Cint,int>>,greater\u003Cpair\u003Cint,int>>> pq; for(int i=0;i\u003CK;i++) if(heads[i]) pq.push({heads[i]->v,i}); vector\u003Cint> res; while(!pq.empty()){ auto [v,i]=pq.top(); pq.pop(); res.push_back(v); heads[i]=heads[i]->next; if(heads[i]) pq.push({heads[i]->v,i}); } for(int x:res) cout\u003C\u003Cx\u003C\u003C\" \"; }","堆存(值,编号,节点)，取最小补下一节点。","import heapq,sys\ninp=sys.stdin.read().split(); it=iter(inp)\nK=int(next(it)); lens=[int(next(it)) for _ in range(K)]\nclass Node:\n    def __init__(self,v,nx=None): self.v=v; self.next=nx\nheads=[]\nfor li in range(K):\n    h=None\n    for _ in range(lens[li]):\n        h=Node(int(next(it)),h)\n    heads.append(h)\nh=[]\nfor i,nd in enumerate(heads):\n    if nd: heapq.heappush(h,(nd.v,i,nd))\nres=[]\nwhile h:\n    v,i,nd=heapq.heappop(h); res.append(v)\n    if nd.next: heapq.heappush(h,(nd.next.v,i,nd.next))\nprint(*res)","多路归并（K 个有序链表、数据流 TopK）统一用最小堆：堆中保存“值+来源编号+下一节点指针”，每次取最小接到结果并补入该来源的下一个节点，复杂度 O(N log K)。","18图基础-A1-1","图的存储（邻接表\u002F矩阵）",{"id":11106,"title":11107,"level":6350,"module":11109,"seq":838,"goal":11110,"keywords":11111,"explanation":11116,"examples":11128,"exercises":11135,"summary":11162,"next":11163,"nextTitle":11164},"图论","掌握图的两种存储：邻接表（vector\u003Cvector\u003Cint>>，省空间、遍历快）与邻接矩阵（二维数组，查边 O(1) 但费空间）；按稠密\u002F稀疏选结构。",[11112,11113,11114,11115],"邻接表","邻接矩阵","图存储","稀疏图",[11117,11122],{"title":11118,"content":11119,"codePy":11120,"codeCpp":11121},"1. 邻接表","每个点维护其邻接点列表；无向图加边要双向；适合稀疏图，空间 O(V+E)。","g=[[] for _ in range(n+1)]\ndef add_edge(u,v):\n    g[u].append(v); g[v].append(u)  # 无向","\u002F\u002F vector\u003Cvector\u003Cint>> g(n+1)",{"title":11123,"content":11124,"codePy":11125,"codeCpp":11126,"tip":11127},"2. 邻接矩阵","mat[u][v] 表是否有边\u002F权值；适合稠密图、需频繁查边；空间 O(V²)。","print(\"稠密图用矩阵\")","\u002F\u002F mat[u][v]=w","tip: 点数大（>5000）且稀疏时用邻接表，否则矩阵会爆内存。",[11129],{"title":11130,"prompt":11131,"analysis":11132,"codePy":11133,"codeCpp":11134,"run":1942},"例 1：邻接表","3 点，边 1-2,2-3，点 2 的邻居？","1 和 3。","print([1,3])","\u002F\u002F 1 3",{"choice":11136,"coding":11152},[11137,11142,11147],{"id":38,"stem":11138,"options":11139,"answer":9,"explanation":11141},"稀疏图首选？",[11113,11112,11140,7901],"边集数组","空间 O(V+E)。",{"id":47,"stem":11143,"options":11144,"answer":9,"explanation":11146},"查边最快的是？",[11112,11113,11145,3259],"邻接表也行","矩阵 O(1)。",{"id":56,"stem":11148,"options":11149,"answer":9,"explanation":11151},"无向图加边要？",[9413,9412,1568,11150],"不处理","u,v 都要加。",[11153],{"id":67,"title":11154,"prompt":11155,"inputFormat":7791,"outputFormat":4573,"samples":11156,"starterPy":11158,"starterCpp":11159,"hint":11160,"referencePy":11161,"referenceCpp":11159},"邻接表存储","n 点 m 边无向图，输出每个点的度（邻居数），按点 1..n。",[11157],{"input":7794,"output":3360},"n,m=map(int,input().split()); deg=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); deg[u]+=1; deg[v]+=1\nprint(*deg[1:])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; vector\u003Cint> deg(n+1,0); for(int i=0,u,v;i\u003Cm;i++){ cin>>u>>v; deg[u]++; deg[v]++; } for(int i=1;i\u003C=n;i++) cout\u003C\u003Cdeg[i]\u003C\u003C\" \"; }","无向边双向加度。","n,m=map(int,input().split()); deg=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); deg[u]+=1; deg[v]+=1\nprint(*deg[1:])","图可用邻接表（vector of lists，省空间、适合稀疏图，O(V+E)）或邻接矩阵（二维数组，查边 O(1)、适合稠密图，O(V²)）；无向图加边需双向。按规模与查询需求选择。","18图基础-A1-2","图的遍历（DFS\u002FBFS）",{"id":11163,"title":11164,"level":6350,"module":11109,"seq":936,"goal":11166,"keywords":11167,"explanation":11169,"examples":11181,"exercises":11186,"summary":11217,"next":11218,"nextTitle":8429},"掌握在一般图上做 DFS\u002FBFS 遍历：用 visited 数组防止重复访问（图可能成环），得到从某点出发能到达的所有节点与遍历序。",[11168,4355,7111,7747],"图遍历",[11170,11175],{"title":11171,"content":11172,"codePy":11173,"codeCpp":11174},"1. DFS 遍历","递归\u002F栈从起点出发，访问未访问邻居并标记，避免环导致无限递归。","def dfs(u):\n    vis[u]=True; order.append(u)\n    for v in g[u]:\n        if not vis[v]: dfs(v)","\u002F\u002F 必须 visited 防环",{"title":11176,"content":11177,"codePy":11178,"codeCpp":11179,"tip":11180},"2. BFS 遍历","队列层序访问；同样依赖 visited；常用于求无权图最短距离。","print(\"图遍历离不开 visited\")","\u002F\u002F 环会让无标记遍历死循环","tip: 图遍历与树不同，必须显式 visited 去重。",[11182],{"title":11183,"prompt":11184,"analysis":11185,"codePy":6544,"codeCpp":6545,"run":890},"例 1：可达","链 1-2-3，从 1 出发可达几？","1,2,3 三个。",{"choice":11187,"coding":11206},[11188,11192,11198],{"id":38,"stem":11189,"options":11190,"answer":9,"explanation":11191},"图 DFS 必须？",[3117,8116,6565,4617],"否则环致死循环。",{"id":47,"stem":11193,"options":11194,"answer":9,"explanation":11197},"图 BFS 常用于？",[11195,11196,3117,6040],"建堆","无权最短路","层序即距离。",{"id":56,"stem":11199,"options":11200,"answer":9,"explanation":11205},"树遍历要不要 visited？",[11201,11202,11203,11204],"要","不要(无环)","随便","都行","树无环无需标记。",[11207],{"id":67,"title":11208,"prompt":11209,"inputFormat":7791,"outputFormat":6275,"samples":11210,"starterPy":11213,"starterCpp":11214,"hint":11215,"referencePy":11216,"referenceCpp":11214},"可达节点数","n 点 m 边无向图，求从点 1 出发能到达的节点个数（含点 1）。",[11211],{"input":11212,"output":890},"4 2\n1 2\n2 3","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True\n    for v in g[u]:\n        if not vis[v]: dfs(v)\ndfs(1)\nprint(sum(vis))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; bool vis[100005];\nvoid dfs(int u){ vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs(v); }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} dfs(1); int c=0; for(int i=1;i\u003C=n;i++) c+=vis[i]; cout\u003C\u003Cc; }","从 1 起 DFS 计数。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nvis=[False]*(n+1)\ndef dfs(u):\n    vis[u]=True\n    for v in g[u]:\n        if not vis[v]: dfs(v)\ndfs(1)\nprint(sum(vis))","一般图遍历（DFS\u002FBFS）必须配合 visited 数组去重，否则遇到环会无限递归\u002F循环；DFS 用递归或栈，BFS 用队列且天然给出无权图最短距离。","18图基础-A1-3",{"id":11218,"title":8429,"level":6350,"module":11109,"seq":998,"goal":11220,"keywords":11221,"explanation":11224,"examples":11236,"exercises":11241,"summary":11272,"next":11273,"nextTitle":11274},"掌握拓扑排序：对有向无环图（DAG）排出顶点顺序，使每条边 u→v 中 u 在 v 前；用 Kahn 算法（入度队列）或 DFS 后序。",[8429,11222,11223,8431],"Kahn","入度",[11225,11230],{"title":11226,"content":11227,"codePy":11228,"codeCpp":11229},"1. Kahn 算法","不断取出入度为 0 的点加入序列并删其出边（邻居入度减一），若最后序列不足 V 则说明有环。","from collections import deque\ndef topo():\n    q=deque([i for i in range(1,n+1) if ind[i]==0]); res=[]\n    while q:\n        u=q.popleft(); res.append(u)\n        for v in g[u]:\n            ind[v]-=1\n            if ind[v]==0: q.append(v)\n    return res","\u002F\u002F 入度0入队",{"title":11231,"content":11232,"codePy":11233,"codeCpp":11234,"tip":11235},"2. 判环","若拓扑序列长度 \u003C 点数，则图含环，无法排序（如课程依赖成环）。","print(\"有环则排不完\")","\u002F\u002F 依赖成环即无解","tip: 拓扑排序是“依赖先后”类问题的基础。",[11237],{"title":11238,"prompt":11239,"analysis":11240,"codePy":8222,"codeCpp":5315,"run":74},"例 1：课程先后","边 1->2, 1->3, 2->3，一个拓扑序？","1,2,3 合法。",{"choice":11242,"coding":11260},[11243,11247,11253],{"id":38,"stem":11244,"options":11245,"answer":9,"explanation":11246},"拓扑排序对象？",[8455,8431,9491,7901],"有向无环图。",{"id":47,"stem":11248,"options":11249,"answer":9,"explanation":11252},"Kahn 起点是？",[3952,11250,11251,9833],"入度0的点","出度0","入度为0入队。",{"id":56,"stem":11254,"options":11255,"answer":9,"explanation":11259},"序列数\u003C点数说明？",[11256,11257,11258,1568],"完成","有环","错","存在环。",[11261],{"id":67,"title":11262,"prompt":11263,"inputFormat":11264,"outputFormat":8414,"samples":11265,"starterPy":11268,"starterCpp":11269,"hint":11270,"referencePy":11271,"referenceCpp":11269},"课程表（拓扑）","n 门课，给定 m 个先修依赖 a→b（先修 a 才能选 b）。判断能否全部修完（即拓扑排序是否存在）。能输出 YES 否则 NO。","第一行 n m；接下来 m 行 a b。",[11266],{"input":11267,"output":8418},"3 3\n1 2\n1 3\n2 3","n,m=map(int,input().split()); g=[[] for _ in range(n+1)]; ind=[0]*(n+1)\nfor _ in range(m):\n    a,b=map(int,input().split()); g[a].append(b); ind[b]+=1\nfrom collections import deque\nq=deque([i for i in range(1,n+1) if ind[i]==0]); cnt=0\nwhile q:\n    u=q.popleft(); cnt+=1\n    for v in g[u]:\n        ind[v]-=1\n        if ind[v]==0: q.append(v)\nprint(\"YES\" if cnt==n else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; int ind[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,a,b;i\u003Cm;i++){cin>>a>>b; g[a].push_back(b); ind[b]++;} queue\u003Cint> q; for(int i=1;i\u003C=n;i++) if(ind[i]==0) q.push(i); int c=0; while(!q.empty()){ int u=q.front(); q.pop(); c++; for(int v:g[u]) if(--ind[v]==0) q.push(v); } cout\u003C\u003C(c==n?\"YES\":\"NO\"); }","Kahn 入度队列。","n,m=map(int,input().split()); g=[[] for _ in range(n+1)]; ind=[0]*(n+1)\nfor _ in range(m):\n    a,b=map(int,input().split()); g[a].append(b); ind[b]+=1\nfrom collections import deque\nq=deque([i for i in range(1,n+1) if ind[i]==0]); cnt=0\nwhile q:\n    u=q.popleft(); cnt+=1\n    for v in g[u]:\n        ind[v]-=1\n        if ind[v]==0: q.append(v)\nprint(\"YES\" if cnt==n else \"NO\")","拓扑排序把 DAG 的顶点排成“依赖在前”的顺序：Kahn 算法用入度队列，反复取入度为 0 的点并删其出边；若最终序列点数不足则说明有环，依赖无法全部满足。","18图基础-A1-4","Dijkstra 最短路",{"id":11273,"title":11274,"level":6350,"module":11109,"seq":1066,"goal":11276,"keywords":11277,"explanation":11280,"examples":11292,"exercises":11297,"summary":11332,"next":11333,"nextTitle":11334},"掌握 Dijkstra：在非负权图上求单源最短路，用最小堆每次取距离最小的点松弛其邻边；复杂度 O((V+E) log V)。",[11278,7749,7562,11279],"Dijkstra","松弛",[11281,11286],{"title":11282,"content":11283,"codePy":11284,"codeCpp":11285},"1. 堆优化","dist[s]=0，其余∞；堆存 (距离, 点)，取最小点 u 松弛邻居 v：若 d[u]+w\u003Cd[v] 则更新并入堆。","import heapq\ndef dijkstra(s):\n    d=[inf]*(n+1); d[s]=0; h=[(0,s)]\n    while h:\n        du,u=heapq.heappop(h)\n        if du>d[u]: continue\n        for v,w in g[u]:\n            if d[u]+w\u003Cd[v]: d[v]=d[u]+w; heapq.heappush(h,(d[v],v))","\u002F\u002F 取最小距离点松弛",{"title":11287,"content":11288,"codePy":11289,"codeCpp":11290,"tip":11291},"2. 为何不能处理负权","若边权为负，“已确定最短”可能被后续更短路径推翻；负权要用 Bellman-Ford\u002FSPFA。","print(\"负权交给 Bellman-Ford\")","\u002F\u002F 非负权才保证贪心","tip: 用 vis\u002F跳过过时堆项避免重复。",[11293],{"title":11294,"prompt":11295,"analysis":11296,"codePy":6253,"codeCpp":5851,"run":759},"例 1：最短路","点 1-2 权 1，1-3 权 5，2-3 权 1，1 到 3 最短？","1->2->3 = 2。",{"choice":11298,"coding":11319},[11299,11306,11311],{"id":38,"stem":11300,"options":11301,"answer":9,"explanation":11305},"Dijkstra 边权要求？",[11302,11303,11304,773],"可负","非负","无要求","负权不适用。",{"id":47,"stem":11307,"options":11308,"answer":9,"explanation":11310},"Dijkstra 核心结构？",[6565,7562,4617,11309],"并查集","取距离最小点。",{"id":56,"stem":11312,"options":11313,"answer":9,"explanation":11318},"复杂度(堆优化)？",[11314,11315,11316,11317],"O(V²)","O((V+E)log V)","O(E)","O(V)","每条边入堆 log。",[11320],{"id":67,"title":11321,"prompt":11322,"inputFormat":11323,"outputFormat":8018,"samples":11324,"starterPy":11328,"starterCpp":11329,"hint":11330,"referencePy":11331,"referenceCpp":11329},"单源最短路","n 点 m 边（边权非负）有向\u002F无向图，求 1 号点到各点最短距离；不可达输出 -1。","第一行 n m；接下来 m 行 u v w。",[11325],{"input":11326,"output":11327},"3 3\n1 2 1\n1 3 5\n2 3 1","0 1 2","import heapq\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; h=[(0,1)]\nwhile h:\n    du,u=heapq.heappop(h)\n    if du>d[u]: continue\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w; heapq.heappush(h,(d[v],v))\nprint(*[d[i] if d[i]\u003CINF else -1 for i in range(1,n+1)])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nconst long long INF=1e18; vector\u003Cpair\u003Cint,long long>> g[100005]; long long d[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v,w;i\u003Cm;i++){cin>>u>>v>>w; g[u].push_back({v,w});} for(int i=1;i\u003C=n;i++) d[i]=INF; d[1]=0; priority_queue\u003Cpair\u003Clong long,int>,vector\u003Cpair\u003Clong long,int>>,greater\u003Cpair\u003Clong long,int>>> pq; pq.push({0,1}); while(!pq.empty()){ auto [du,u]=pq.top(); pq.pop(); if(du>d[u]) continue; for(auto& e:g[u]){ int v=e.first; long long w=e.second; if(d[u]+w\u003Cd[v]){ d[v]=d[u]+w; pq.push({d[v],v}); } } } for(int i=1;i\u003C=n;i++) cout\u003C\u003C(d[i]==INF?-1:d[i])\u003C\u003C\" \"; }","堆优化 Dijkstra。","import heapq\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; h=[(0,1)]\nwhile h:\n    du,u=heapq.heappop(h)\n    if du>d[u]: continue\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w; heapq.heappush(h,(d[v],v))\nprint(*[d[i] if d[i]\u003CINF else -1 for i in range(1,n+1)])","Dijkstra 在非负权图上求单源最短路：用最小堆反复取出距离最小的点并松弛其出边（d[u]+w\u003Cd[v] 则更新入堆），复杂度 O((V+E) log V)；负权边需改用 Bellman-Ford\u002FSPFA。","18图基础-A1-5","Bellman-Ford \u002F SPFA",{"id":11333,"title":11334,"level":6350,"module":11109,"seq":1131,"goal":11336,"keywords":11337,"explanation":11342,"examples":11354,"exercises":11359,"summary":11392,"next":11393,"nextTitle":11394},"掌握可处理负权的单源最短路：Bellman-Ford 对所有边松弛 V-1 轮；SPFA 用队列优化，并在入队次数≥V 时判定负环。",[11338,11339,11340,11341],"Bellman-Ford","SPFA","负权","负环",[11343,11348],{"title":11344,"content":11345,"codePy":11346,"codeCpp":11347},"1. Bellman-Ford","对每条边松弛 V-1 轮；第 V 轮仍可被松弛则说明存在负环。","def bf(s):\n    d=[inf]*(n+1); d[s]=0\n    for _ in range(n-1):\n        for u,v,w in edges:\n            if d[u]+w\u003Cd[v]: d[v]=d[u]+w\n    for u,v,w in edges:\n        if d[u]+w\u003Cd[v]: return None  # 负环\n    return d","\u002F\u002F V-1 轮松弛",{"title":11349,"content":11350,"codePy":11351,"codeCpp":11352,"tip":11353},"2. SPFA","队列版：只有被更新的点才入队松弛；若某点入队次数≥V 则有负环，平均更快但最坏仍 O(VE)。","print(\"SPFA 用队列优化\")","\u002F\u002F 负环检测靠入队计数","tip: 数据随机时 SPFA 快；最坏退化成 O(VE)，慎用。",[11355],{"title":11356,"prompt":11357,"analysis":11358,"codePy":9230,"codeCpp":9231,"run":891},"例 1：负权","边 1->2 权 1，2->3 权 -1，1->3 权 5，1 到 3 最短？","1->2->3 = 0。",{"choice":11360,"coding":11380},[11361,11368,11373],{"id":38,"stem":11362,"options":11363,"answer":9,"explanation":11367},"Bellman-Ford 可处理？",[11364,11340,11365,11366],"仅正权","无向","稠密","支持负权边。",{"id":47,"stem":11369,"options":11370,"answer":9,"explanation":11372},"判断负环用？",[11278,11371,7778,6210],"BF\u002FSPFA 第V轮","仍可松弛即有负环。",{"id":56,"stem":11374,"options":11375,"answer":9,"explanation":11379},"SPFA 负环判定？",[1568,11376,11377,11378],"入队次数≥V","距离","边数","点被松弛过 V 次以上。",[11381],{"id":67,"title":11382,"prompt":11383,"inputFormat":11323,"outputFormat":4573,"samples":11384,"starterPy":11388,"starterCpp":11389,"hint":11390,"referencePy":11391,"referenceCpp":11389},"含负权的最短路","n 点 m 边（可含负权，但无负环）有向图，求 1 号点到各点最短距离；不可达输出 -1。",[11385],{"input":11386,"output":11387},"3 3\n1 2 1\n2 3 -1\n1 3 5","0 1 0","n,m=map(int,input().split()); edges=[]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); edges.append((u,v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0\nfor _ in range(n-1):\n    for u,v,w in edges:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w\nprint(*[d[i] if d[i]\u003CINF else -1 for i in range(1,n+1)])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst long long INF=1e18; struct E{int v;long long w;}; vector\u003CE> g[100005]; long long d[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v,w;i\u003Cm;i++){cin>>u>>v>>w; g[u].push_back({v,w});} for(int i=1;i\u003C=n;i++) d[i]=INF; d[1]=0; for(int it=0;it\u003Cn-1;it++){ for(int u=1;u\u003C=n;u++) for(E& e:g[u]){ int v=e.v; long long w=e.w; if(d[u]+w\u003Cd[v]) d[v]=d[u]+w; } } for(int i=1;i\u003C=n;i++) cout\u003C\u003C(d[i]==INF?-1:d[i])\u003C\u003C\" \"; }","Bellman-Ford 松弛 V-1 轮。","n,m=map(int,input().split()); edges=[]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); edges.append((u,v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0\nfor _ in range(n-1):\n    for u,v,w in edges:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w\nprint(*[d[i] if d[i]\u003CINF else -1 for i in range(1,n+1)])","Bellman-Ford 对所有边松弛 V-1 轮即可求含负权的最短路，再检查一遍若仍可松弛则存在负环，复杂度 O(VE)；SPFA 用队列只把被更新的点入队，平均更快但最坏仍为 O(VE)。","18图基础-A1-6","最小生成树（Kruskal）",{"id":11393,"title":11394,"level":6350,"module":11109,"seq":89,"goal":11396,"keywords":11397,"explanation":11400,"examples":11411,"exercises":11416,"summary":11445,"next":11446,"nextTitle":11447},"掌握 Kruskal 求最小生成树：把所有边按权升序，用并查集依次加入不形成环的边，直到选够 V-1 条。",[11398,11399,11309,6039],"最小生成树","Kruskal",[11401,11406],{"title":11402,"content":11403,"codePy":11404,"codeCpp":11405},"1. 按权贪心","边排序后逐条考虑：若两端不在同一连通块（并查集 find 不同）则合并并选入，权值累加。","def kruskal():\n    sort edges by w\n    uf=UnionFind(n); ans=0; cnt=0\n    for u,v,w in edges:\n        if uf.find(u)!=uf.find(v):\n            uf.union(u,v); ans+=w; cnt+=1\n    return ans if cnt==n-1 else inf","\u002F\u002F 贪心选最小可用边",{"title":7815,"content":11407,"codePy":11408,"codeCpp":11409,"tip":11410},"贪心选当前最小且不形成环的边，等价于 MST 的“安全边”性质；复杂度 O(E log E)。","print(\"边排序+并查集\")","\u002F\u002F 选 V-1 条","tip: 图不连通则选不 V-1 条边，无 MST。",[11412],{"title":11413,"prompt":11414,"analysis":11415,"codePy":6544,"codeCpp":6545,"run":890},"例 1：MST","3 点，边 1-2 权1, 2-3 权2, 1-3 权3，MST 权？","选 1-2,2-3，权 1+2=3。",{"choice":11417,"coding":11435},[11418,11423,11427],{"id":38,"stem":11419,"options":11420,"answer":607,"explanation":11422},"Kruskal 先做什么？",[11421,7111,7778,11195],"排序边","边按权升序。",{"id":47,"stem":11424,"options":11425,"answer":9,"explanation":11426},"Kruskal 判环用？",[7111,11309,6565,4617],"查两端是否同块。",{"id":56,"stem":11428,"options":11429,"answer":9,"explanation":11434},"MST 边数？",[11430,11431,11432,11433],"V","V-1","E","V+1","树有 V-1 条边。",[11436],{"id":67,"title":11398,"prompt":11437,"inputFormat":11323,"outputFormat":6275,"samples":11438,"starterPy":11441,"starterCpp":11442,"hint":11443,"referencePy":11444,"referenceCpp":11442},"n 点 m 边无向连通图，求最小生成树边权和；若不连通输出 -1。",[11439],{"input":11440,"output":890},"3 3\n1 2 1\n2 3 2\n1 3 3","n,m=map(int,input().split()); edges=[]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); edges.append((w,u,v))\nedges.sort(); fa=list(range(n+1))\ndef find(x):\n    while fa[x]!=x: fa[x]=fa[fa[x]]; x=fa[x]\n    return x\nans=cnt=0\nfor w,u,v in edges:\n    ru,rv=find(u),find(v)\n    if ru!=rv: fa[ru]=rv; ans+=w; cnt+=1\nprint(ans if cnt==n-1 else -1)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct E{int u,v,w;}; bool cmp(E a,E b){return a.w\u003Cb.w;}\nint fa[100005]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}\nint main(){ int n,m; cin>>n>>m; vector\u003CE> e(m); for(int i=0;i\u003Cm;i++) cin>>e[i].u>>e[i].v>>e[i].w; sort(e.begin(),e.end(),cmp); for(int i=1;i\u003C=n;i++) fa[i]=i; long long ans=0; int cnt=0; for(int i=0;i\u003Cm;i++){ int ru=find(e[i].u),rv=find(e[i].v); if(ru!=rv){ fa[ru]=rv; ans+=e[i].w; cnt++; } } cout\u003C\u003C(cnt==n-1?ans:-1); }","边排序 + 并查集选不环的边。","n,m=map(int,input().split()); edges=[]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); edges.append((w,u,v))\nedges.sort(); fa=list(range(n+1))\ndef find(x):\n    while fa[x]!=x: fa[x]=fa[fa[x]]; x=fa[x]\n    return x\nans=cnt=0\nfor w,u,v in edges:\n    ru,rv=find(u),find(v)\n    if ru!=rv: fa[ru]=rv; ans+=w; cnt+=1\nprint(ans if cnt==n-1 else -1)","Kruskal 求最小生成树：把所有边按权升序，用并查集依次加入不形成环（两端不在同一连通块）的边，累加权值直到选满 V-1 条；图不连通则无 MST。复杂度 O(E log E)。","18图基础-A1-7","最小生成树（Prim）",{"id":11446,"title":11447,"level":6350,"module":11109,"seq":158,"goal":11449,"keywords":11450,"explanation":11453,"examples":11465,"exercises":11470,"summary":11501,"next":11502,"nextTitle":11503},"掌握 Prim 求最小生成树：从一点出发，用最小堆不断把“未访问点中离已选集合最近的边”对应的点加入集合，直到覆盖所有点。",[11451,11398,7562,11452],"Prim","集合扩张",[11454,11459],{"title":11455,"content":11456,"codePy":11457,"codeCpp":11458},"1. 堆优化 Prim","维护每个未访问点到已选集合的最小边权；每次取最小者加入并松弛其邻居的估价。","import heapq\ndef prim(s):\n    d=[inf]*(n+1); d[s]=0; h=[(0,s)]; vis=[False]*(n+1); ans=0\n    while h:\n        w,u=heapq.heappop(h)\n        if vis[u]: continue\n        vis[u]=True; ans+=w\n        for v,ww in g[u]:\n            if not vis[v] and ww\u003Cd[v]: d[v]=ww; heapq.heappush(h,(d[v],v))","\u002F\u002F 取最近点加入集合",{"title":11460,"content":11461,"codePy":11462,"codeCpp":11463,"tip":11464},"2. 与 Kruskal 比较","Prim 适合稠密图（O(V²) 数组版或 O((V+E)log V) 堆版）；Kruskal 适合稀疏图。","print(\"稠密图用 Prim\")","\u002F\u002F 都是 MST","tip: Prim 与 Dijkstra 结构几乎相同，只是 d 的含义由“到源距离”改为“到集合距离”。",[11466],{"title":11467,"prompt":11468,"analysis":11469,"codePy":6544,"codeCpp":6545,"run":890},"例 1：Prim","3 点，边 1-2 权1, 1-3 权3, 2-3 权2，Prim 从 1 起 MST 权？","1-2(1)+2-3(2)=3。",{"choice":11471,"coding":11490},[11472,11478,11484],{"id":38,"stem":11473,"options":11474,"answer":9,"explanation":11477},"Prim 与 Dijkstra 结构？",[9929,11475,6159,11476],"几乎相同","都错","堆取最小+松弛。",{"id":47,"stem":11479,"options":11480,"answer":9,"explanation":11483},"Prim 适合？",[11481,11366,11482,11340],"稀疏","有向","稠密图友好。",{"id":56,"stem":11485,"options":11486,"answer":9,"explanation":11489},"Prim 每次加入？",[11487,11488,1568,9833],"最大边","最近点","离集合最近的点。",[11491],{"id":67,"title":11492,"prompt":11493,"inputFormat":11323,"outputFormat":6275,"samples":11494,"starterPy":11497,"starterCpp":11498,"hint":11499,"referencePy":11500,"referenceCpp":11498},"Prim 最小生成树","n 点 m 边无向图，求最小生成树边权和（图连通）；否则输出 -1。",[11495],{"input":11496,"output":890},"3 3\n1 2 1\n1 3 3\n2 3 2","import heapq\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w)); g[v].append((u,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; h=[(0,1)]; vis=[False]*(n+1); ans=cnt=0\nwhile h:\n    w,u=heapq.heappop(h)\n    if vis[u]: continue\n    vis[u]=True; ans+=w; cnt+=1\n    for v,ww in g[u]:\n        if not vis[v] and ww\u003Cd[v]: d[v]=ww; heapq.heappush(h,(d[v],v))\nprint(ans if cnt==n else -1)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nconst long long INF=1e18; vector\u003Cpair\u003Cint,long long>> g[100005]; long long d[100005]; bool vis[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v,w;i\u003Cm;i++){cin>>u>>v>>w; g[u].push_back({v,w}); g[v].push_back({u,w});} for(int i=1;i\u003C=n;i++) d[i]=INF; d[1]=0; priority_queue\u003Cpair\u003Clong long,int>,vector\u003Cpair\u003Clong long,int>>,greater\u003Cpair\u003Clong long,int>>> pq; pq.push({0,1}); long long ans=0; int cnt=0; while(!pq.empty()){ auto [w,u]=pq.top(); pq.pop(); if(vis[u]) continue; vis[u]=1; ans+=w; cnt++; for(auto& e:g[u]){ int v=e.first; long long ww=e.second; if(!vis[v]&&ww\u003Cd[v]){ d[v]=ww; pq.push({d[v],v}); } } } cout\u003C\u003C(cnt==n?ans:-1); }","堆优化 Prim，加入最近点。","import heapq\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w)); g[v].append((u,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; h=[(0,1)]; vis=[False]*(n+1); ans=cnt=0\nwhile h:\n    w,u=heapq.heappop(h)\n    if vis[u]: continue\n    vis[u]=True; ans+=w; cnt+=1\n    for v,ww in g[u]:\n        if not vis[v] and ww\u003Cd[v]: d[v]=ww; heapq.heappush(h,(d[v],v))\nprint(ans if cnt==n else -1)","Prim 从一点开始，用最小堆每次把“离已选集合最近的未访问点”加入集合并松弛邻居估价（d 为到集合的最小边权），直到覆盖所有点；适合稠密图，与 Dijkstra 结构几乎相同。","18图基础-A1-8","并查集（Union-Find）",{"id":11502,"title":11503,"level":6350,"module":11109,"seq":230,"goal":11505,"keywords":11506,"explanation":11509,"examples":11520,"exercises":11525,"summary":11550,"next":11551,"nextTitle":11552},"掌握并查集：find（路径压缩）与 union（按秩\u002F大小合并），用于动态连通性、Kruskal、集合计数；近乎 O(1) 每操作。",[11309,11507,11508,8258],"路径压缩","按秩合并",[11510,11515],{"title":11511,"content":11512,"codePy":11513,"codeCpp":11514},"1. 两操作","find 沿父指针上溯并压缩路径；union 找根，把小树挂到大树下（按秩）以防退化。","fa=[*range(n+1)]; rk=[0]*(n+1)\ndef find(x):\n    while fa[x]!=x: fa[x]=fa[fa[x]]; x=fa[x]\n    return x\ndef union(a,b):\n    ra,rb=find(a),find(b)\n    if ra==rb: return False\n    if rk[ra]\u003Crk[rb]: ra,rb=rb,ra\n    fa[rb]=ra\n    if rk[ra]==rk[rb]: rk[ra]+=1\n    return True","\u002F\u002F 路径压缩+按秩合并",{"title":4421,"content":11516,"codePy":11517,"codeCpp":11518,"tip":11519},"判定两点是否连通、统计连通块数、Kruskal 判环、社交网络圈子。","print(\"动态连通性利器\")","\u002F\u002F 几乎 O(1)","tip: 路径压缩后 find 近似常数，是图论高频工具。",[11521],{"title":11522,"prompt":11523,"analysis":11524,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：连通","并查集合并 1-2、2-3，1 与 3 连通？","是（同根）。",{"choice":11526,"coding":11541},[11527,11531,11536],{"id":38,"stem":11528,"options":11529,"answer":9,"explanation":11530},"并查集 find 优化？",[3117,11507,6565,4617],"压缩路径到根。",{"id":47,"stem":11532,"options":11533,"answer":9,"explanation":11535},"union 防退化用？",[1568,11534,1405,3259],"按秩\u002F大小合并","小树挂大树。",{"id":56,"stem":11537,"options":11538,"answer":9,"explanation":11540},"并查集常用于？",[3117,11539,7749,7778],"连通性\u002FKruskal","动态连通与 MST。",[11542],{"id":67,"title":8297,"prompt":11543,"inputFormat":7791,"outputFormat":6275,"samples":11544,"starterPy":11546,"starterCpp":11547,"hint":11548,"referencePy":11549,"referenceCpp":11547},"n 点，依次执行 m 个“合并 u v”操作，最后输出连通块数。",[11545],{"input":8528,"output":759},"n,m=map(int,input().split()); fa=list(range(n+1)); rk=[0]*(n+1)\ndef find(x):\n    while fa[x]!=x: fa[x]=fa[fa[x]]; x=fa[x]\n    return x\ndef union(a,b):\n    ra,rb=find(a),find(b)\n    if ra==rb: return\n    if rk[ra]\u003Crk[rb]: ra,rb=rb,ra\n    fa[rb]=ra\n    if rk[ra]==rk[rb]: rk[ra]+=1\nfor _ in range(m):\n    u,v=map(int,input().split()); union(u,v)\nblocks=sum(1 for i in range(1,n+1) if find(i)==i)\nprint(blocks)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint fa[100005],rk[100005];\nint find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}\nint main(){ int n,m; cin>>n>>m; for(int i=1;i\u003C=n;i++) fa[i]=i; for(int i=0,u,v;i\u003Cm;i++){ cin>>u>>v; int ru=find(u),rv=find(v); if(ru!=rv){ if(rk[ru]\u003Crk[rv]) swap(ru,rv); fa[rv]=ru; if(rk[ru]==rk[rv]) rk[ru]++; } } int c=0; for(int i=1;i\u003C=n;i++) if(find(i)==i) c++; cout\u003C\u003Cc; }","合并后数根节点。","n,m=map(int,input().split()); fa=list(range(n+1)); rk=[0]*(n+1)\ndef find(x):\n    while fa[x]!=x: fa[x]=fa[fa[x]]; x=fa[x]\n    return x\ndef union(a,b):\n    ra,rb=find(a),find(b)\n    if ra==rb: return\n    if rk[ra]\u003Crk[rb]: ra,rb=rb,ra\n    fa[rb]=ra\n    if rk[ra]==rk[rb]: rk[ra]+=1\nfor _ in range(m):\n    u,v=map(int,input().split()); union(u,v)\nblocks=sum(1 for i in range(1,n+1) if find(i)==i)\nprint(blocks)","并查集用 find（路径压缩）与 union（按秩\u002F大小合并）维护动态连通性，单次操作近似 O(1)；广泛用于 Kruskal 判环、连通块计数、朋友圈等场景。","18图基础-A2-1","二分图判定（染色）",{"id":11551,"title":11552,"level":6350,"module":11109,"seq":301,"goal":11554,"keywords":11555,"explanation":11559,"examples":11571,"exercises":11576,"summary":11602,"next":11603,"nextTitle":11604},"掌握二分图判定：用两种颜色对图染色，相邻点异色；DFS\u002FBFS 染色时若遇同色相邻则非二分图。",[11556,11557,11558,7111],"二分图","染色法"," bipartition",[11560,11565],{"title":11561,"content":11562,"codePy":11563,"codeCpp":11564},"1. 染色判定","从某点染 0，邻居染 1，递归\u002F队列扩散；若某点已被染且与应染色相同则不是二分图。","def is_bip():\n    color=[-1]*(n+1)\n    def dfs(u,c):\n        color[u]=c\n        for v in g[u]:\n            if color[v]==c: return False\n            if color[v]==-1 and not dfs(v,1-c): return False\n        return True\n    for i in range(1,n+1):\n        if color[i]==-1 and not dfs(i,0): return False\n    return True","\u002F\u002F 相邻必异色",{"title":11566,"content":11567,"codePy":11568,"codeCpp":11569,"tip":11570},"2. 意义","二分图等价于无奇数长度环；很多匹配\u002F分配问题基于二分图。","print(\"无奇环即二分图\")","\u002F\u002F 染色冲突即非二分","tip: 每个连通块独立染色，注意孤立点。",[11572],{"title":11573,"prompt":11574,"analysis":11575,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：二分","链 1-2-3 是二分图？","是（可 0,1,0 染色）。",{"choice":11577,"coding":11592},[11578,11582,11587],{"id":38,"stem":11579,"options":11580,"answer":9,"explanation":11581},"二分图判定用？",[7778,11557,11278,11309],"相邻异色。",{"id":47,"stem":11583,"options":11584,"answer":9,"explanation":11586},"二分图等价于？",[11257,11585,7924,7901],"无奇环","无奇数长度环。",{"id":56,"stem":11588,"options":11589,"answer":9,"explanation":11591},"染色冲突说明？",[2509,11590,7924,11258],"非二分","存在奇环。",[11593],{"id":67,"title":11594,"prompt":11595,"inputFormat":7791,"outputFormat":8414,"samples":11596,"starterPy":11598,"starterCpp":11599,"hint":11600,"referencePy":11601,"referenceCpp":11599},"判断二分图","n 点 m 边无向图，判断是否二分图（可染两色使相邻异色）。是输出 YES 否则 NO。",[11597],{"input":7794,"output":8418},"import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ncolor=[-1]*(n+1)\ndef dfs(u,c):\n    color[u]=c\n    for v in g[u]:\n        if color[v]==c: return False\n        if color[v]==-1 and not dfs(v,1-c): return False\n    return True\nok=True\nfor i in range(1,n+1):\n    if color[i]==-1 and not dfs(i,0): ok=False; break\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; int color[100005]; bool dfs(int u,int c){ color[u]=c; for(int v:g[u]){ if(color[v]==c) return false; if(color[v]==-1 && !dfs(v,1-c)) return false; } return true; }\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} for(int i=1;i\u003C=n;i++) color[i]=-1; bool ok=true; for(int i=1;i\u003C=n;i++) if(color[i]==-1 && !dfs(i,0)){ ok=false; break; } cout\u003C\u003C(ok?\"YES\":\"NO\"); }","染色冲突即非二分。","import sys\nsys.setrecursionlimit(100000)\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ncolor=[-1]*(n+1)\ndef dfs(u,c):\n    color[u]=c\n    for v in g[u]:\n        if color[v]==c: return False\n        if color[v]==-1 and not dfs(v,1-c): return False\n    return True\nok=True\nfor i in range(1,n+1):\n    if color[i]==-1 and not dfs(i,0): ok=False; break\nprint(\"YES\" if ok else \"NO\")","二分图判定用染色法：从任意点染一种色，BFS\u002FDFS 把邻居染相反色，若遇到已染色且与当前应染色相同则非二分图；一个图是二分图当且仅当不含奇数长度的环。","18图基础-A2-2","欧拉路 \u002F 欧拉回路",{"id":11603,"title":11604,"level":6350,"module":11109,"seq":368,"goal":11606,"keywords":11607,"explanation":11612,"examples":11624,"exercises":11629,"summary":11660,"next":11661,"nextTitle":11662},"掌握欧拉路判定：无向图中每个点度数均为偶则有欧拉回路；恰两个奇度点则有欧拉路（起自奇度点）；用 Hierholzer 输出路径。",[11608,11609,11610,11611],"欧拉路","欧拉回路","奇度点","Hierholzer",[11613,11618],{"title":11614,"content":11615,"codePy":11616,"codeCpp":11617},"1. 判定","无向图：连通且奇度点数为 0（回路）或 2（通路）；有向图：入度=出度的点数为 0 或恰一多一少。","def has_euler():\n    odd=sum(1 for i in range(1,n+1) if deg[i]%2)\n    return odd in (0,2)","\u002F\u002F 奇度点 0 或 2",{"title":11619,"content":11620,"codePy":11621,"codeCpp":11622,"tip":11623},"2. Hierholzer","从起点出发贪心走未用边，回溯时把点加入结果，逆序输出即欧拉路。","print(\"回路需全偶度\")","\u002F\u002F 连通+度数条件","tip: 先判连通与度数，再考虑输出路径。",[11625],{"title":11626,"prompt":11627,"analysis":11628,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：欧拉路","3 点链 1-2-3，奇度点几个？有欧拉路？","点1、3 奇度（2个），有欧拉路。",{"choice":11630,"coding":11650},[11631,11637,11644],{"id":38,"stem":11632,"options":11633,"answer":607,"explanation":11636},"无向图欧拉回路条件？",[11634,11635,3952,7901],"连通+全偶度","连通+2奇度","全偶度才能回路。",{"id":47,"stem":11638,"options":11639,"answer":9,"explanation":11643},"无向图欧拉路条件？",[11640,11641,3952,11642],"全偶度","恰2奇度","1奇度","恰两个奇度点。",{"id":56,"stem":11645,"options":11646,"answer":9,"explanation":11649},"Hierholzer 输出？",[11647,11648,1568,3117],"正序","逆序","回溯逆序得路径。",[11651],{"id":67,"title":11652,"prompt":11653,"inputFormat":7791,"outputFormat":8414,"samples":11654,"starterPy":11656,"starterCpp":11657,"hint":11658,"referencePy":11659,"referenceCpp":11657},"欧拉路判定","n 点 m 边无向连通图，判断是否存在欧拉路或欧拉回路（即一笔画出）。能输出 YES 否则 NO。",[11655],{"input":7794,"output":8418},"n,m=map(int,input().split()); deg=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); deg[u]+=1; deg[v]+=1\nodd=sum(1 for i in range(1,n+1) if deg[i]%2)\nprint(\"YES\" if odd in (0,2) else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint deg[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v;i\u003Cm;i++){ cin>>u>>v; deg[u]++; deg[v]++; } int odd=0; for(int i=1;i\u003C=n;i++) if(deg[i]%2) odd++; cout\u003C\u003C(odd==0||odd==2?\"YES\":\"NO\"); }","奇度点 0 或 2。","n,m=map(int,input().split()); deg=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); deg[u]+=1; deg[v]+=1\nodd=sum(1 for i in range(1,n+1) if deg[i]%2)\nprint(\"YES\" if odd in (0,2) else \"NO\")","无向图存在欧拉回路当且仅当连通且所有点度数为偶；存在欧拉路当且仅当连通且恰有两个奇度点（路的两个端点）。有向图相应要求入度=出度（回路）或恰一点入=出+1、一点出=入+1（路）。","18图基础-A2-3","树的直径",{"id":11661,"title":11662,"level":6350,"module":11109,"seq":435,"goal":11664,"keywords":11665,"explanation":11668,"examples":11680,"exercises":11685,"summary":11712,"next":11713,"nextTitle":11714},"掌握求树（或无权图）直径：两次 BFS\u002FDFS——任选点求最远点 u，再从 u 求最远点 v，u 到 v 距离即直径。",[11662,11666,11667,7901],"两次BFS","最远点",[11669,11674],{"title":11670,"content":11671,"codePy":11672,"codeCpp":11673},"1. 两次搜索","第一次从任意点 a 找最远点 u；第二次从 u 找最远点 v；dist(u,v) 即为直径。","def bfs(s):\n    dist=[-1]*(n+1); dist[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\n    return max(range(1,n+1), key=lambda i: dist[i])\ndef diameter():\n    u=bfs(1); v=bfs(u); return dist[v]  # 需 bfs 返回距离数组","\u002F\u002F 两次 BFS 取最远",{"title":11675,"content":11676,"codePy":11677,"codeCpp":11678,"tip":11679},"2. 原理","树上任一点的最远点必为直径的一端；再从其出发的最远点即另一端。","print(\"两次最远即得直径\")","\u002F\u002F 树无环保证成立","tip: 无权用 BFS，带权树用两次 DFS 记距离。",[11681],{"title":11682,"prompt":11683,"analysis":11684,"codePy":6544,"codeCpp":6545,"run":890},"例 1：直径","链 1-2-3-4，直径？","1 到 4 距 3（边数）。",{"choice":11686,"coding":11701},[11687,11691,11697],{"id":38,"stem":11688,"options":11689,"answer":9,"explanation":11690},"求直径需几次搜索？",[892,759,890,3054],"两次最远。",{"id":47,"stem":11692,"options":11693,"answer":9,"explanation":11696},"第一次从任意点找最远点 u，u 是？",[9833,11694,5709,11695],"直径一端","叶","必为直径端点。",{"id":56,"stem":11698,"options":11699,"answer":9,"explanation":11700},"链 1-2-3-4 直径(边数)？",[759,890,1663,892],"1-4 跨 3 边。",[11702],{"id":67,"title":11662,"prompt":11703,"inputFormat":11704,"outputFormat":6275,"samples":11705,"starterPy":11708,"starterCpp":11709,"hint":11710,"referencePy":11711,"referenceCpp":11709},"n 点 n-1 边构成一棵树，求直径（最长简单路径的边数）。","第一行 n；接下来 n-1 行 u v。",[11706],{"input":11707,"output":890},"4\n1 2\n2 3\n3 4","from collections import deque\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndef bfs(s):\n    dist=[-1]*(n+1); dist[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\n    far=max(range(1,n+1), key=lambda i: dist[i])\n    return far,dist\nu,_=bfs(1); v,dist=bfs(u)\nprint(dist[v])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cint> g[100005]; int dist[100005];\npair\u003Cint,int> bfs(int s,int n){ for(int i=1;i\u003C=n;i++) dist[i]=-1; dist[s]=0; queue\u003Cint> q; q.push(s); int far=s; while(!q.empty()){ int u=q.front(); q.pop(); far=u; for(int v:g[u]) if(dist[v]==-1){ dist[v]=dist[u]+1; q.push(v);} } return {far,dist[far]}; }\nint main(){ int n; cin>>n; for(int i=0,u,v;i\u003Cn-1;i++){ cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} int u=bfs(1,n).first; int d=bfs(u,n).second; cout\u003C\u003Cd; }","两次 BFS 取最远。","from collections import deque\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndef bfs(s):\n    dist=[-1]*(n+1); dist[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v in g[u]:\n            if dist[v]==-1: dist[v]=dist[u]+1; q.append(v)\n    far=max(range(1,n+1), key=lambda i: dist[i])\n    return far,dist\nu,_=bfs(1); v,dist=bfs(u)\nprint(dist[v])","树的直径可用两次搜索求得：先从任意点找最远点 u（必为直径一端），再从 u 找最远点 v，u 到 v 的距离即为直径；无权用 BFS，带权树用 DFS 记录距离。","18图基础-A2-4","最近公共祖先 LCA（倍增）",{"id":11713,"title":11714,"level":6350,"module":11109,"seq":502,"goal":11716,"keywords":11717,"explanation":11722,"examples":11733,"exercises":11738,"summary":11767,"next":11768,"nextTitle":11769},"掌握倍增法求 LCA：预处理每个点向上 2^k 步的祖先 up[u][k]，查询时先把两点提到同一深度，再同步上跳找分叉点上方。",[11718,11719,11720,11721],"LCA","倍增","最近公共祖先","树上查询",[11723,11728],{"title":11724,"content":11725,"codePy":11726,"codeCpp":11727},"1. 预处理","BFS\u002FDFS 得到深度与 up[u][0]=父；up[u][k]=up[up[u][k-1]][k-1]。","def build():\n    up=[[0]*18 for _ in range(n+1)]\n    def dfs(u,f):\n        up[u][0]=f\n        for k in range(1,18): up[u][k]=up[up[u][k-1]][k-1]\n        for v in g[u]:\n            if v!=f: dep[v]=dep[u]+1; dfs(v,u)","\u002F\u002F 2^k 祖先递推",{"title":4479,"content":11729,"codePy":11730,"codeCpp":11731,"tip":11732},"先让深的点上跳到同深；若两点重合则为其一；否则从大到小尝试上跳，最后父亲的父为 LCA。","print(\"先齐深度再同步上跳\")","\u002F\u002F 至多 log n 步","tip: 倍增 LCA 预处理 O(n log n)，查询 O(log n)。",[11734],{"title":11735,"prompt":11736,"analysis":11737,"codePy":6544,"codeCpp":6545,"run":890},"例 1：LCA","链 1-2-3-4，LCA(3,4)=？","3（3 是 4 的祖先）。",{"choice":11739,"coding":11754},[11740,11744,11750],{"id":38,"stem":11741,"options":11742,"answer":9,"explanation":11743},"倍增 LCA 预处理复杂度？",[2338,2564,1246,2339],"每点 log n 祖先。",{"id":47,"stem":11745,"options":11746,"answer":9,"explanation":11749},"查询第一步？",[11747,11748,3117,11195],"上跳","对齐深度","深者先提到同深。",{"id":56,"stem":11751,"options":11752,"answer":9,"explanation":11753},"LCA(3,4) 在链 1-2-3-4？",[1663,890,759,892],"3 是 4 祖先。",[11755],{"id":67,"title":11756,"prompt":11757,"inputFormat":11758,"outputFormat":11759,"samples":11760,"starterPy":11763,"starterCpp":11764,"hint":11765,"referencePy":11766,"referenceCpp":11764},"LCA 倍增","n 点 n-1 边树，根固定为 1，给定 q 次查询 (a,b)，输出每对的最近公共祖先。","第一行 n q；接下来 n-1 行 u v；接下来 q 行 a b。","q 行，每行一个整数。",[11761],{"input":11762,"output":759},"4 1\n1 2\n2 3\n2 4\n3 4","import sys\nsys.setrecursionlimit(100000)\nn,q=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nLOG=17; up=[[0]*(LOG+1) for _ in range(n+1)]; dep=[0]*(n+1)\ndef dfs(u,f):\n    up[u][0]=f\n    for k in range(1,LOG+1): up[u][k]=up[up[u][k-1]][k-1]\n    for v in g[u]:\n        if v!=f: dep[v]=dep[u]+1; dfs(v,u)\ndfs(1,0)\ndef lca(a,b):\n    if dep[a]\u003Cdep[b]: a,b=b,a\n    for k in range(LOG,-1,-1):\n        if dep[a]-(1\u003C\u003Ck)>=dep[b]: a=up[a][k]\n    if a==b: return a\n    for k in range(LOG,-1,-1):\n        if up[a][k]!=up[b][k]: a=up[a][k]; b=up[b][k]\n    return up[a][0]\nfor _ in range(q):\n    a,b=map(int,input().split()); print(lca(a,b))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst int LOG=17; vector\u003Cint> g[100005]; int up[100005][LOG+1],dep[100005];\nvoid dfs(int u,int f){ up[u][0]=f; for(int k=1;k\u003C=LOG;k++) up[u][k]=up[up[u][k-1]][k-1]; for(int v:g[u]) if(v!=f){ dep[v]=dep[u]+1; dfs(v,u); } }\nint lca(int a,int b){ if(dep[a]\u003Cdep[b]) swap(a,b); for(int k=LOG;k>=0;k--) if(dep[a]-(1\u003C\u003Ck)>=dep[b]) a=up[a][k]; if(a==b) return a; for(int k=LOG;k>=0;k--) if(up[a][k]!=up[b][k]){ a=up[a][k]; b=up[b][k]; } return up[a][0]; }\nint main(){ int n,Q; cin>>n>>Q; for(int i=0,u,v;i\u003Cn-1;i++){ cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} dfs(1,0); while(Q--){ int a,b; cin>>a>>b; cout\u003C\u003Clca(a,b)\u003C\u003Cendl; } }","倍增预处理 + 对齐深度 + 同步上跳。","import sys\nsys.setrecursionlimit(100000)\nn,q=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nLOG=17; up=[[0]*(LOG+1) for _ in range(n+1)]; dep=[0]*(n+1)\ndef dfs(u,f):\n    up[u][0]=f\n    for k in range(1,LOG+1): up[u][k]=up[up[u][k-1]][k-1]\n    for v in g[u]:\n        if v!=f: dep[v]=dep[u]+1; dfs(v,u)\ndfs(1,0)\ndef lca(a,b):\n    if dep[a]\u003Cdep[b]: a,b=b,a\n    for k in range(LOG,-1,-1):\n        if dep[a]-(1\u003C\u003Ck)>=dep[b]: a=up[a][k]\n    if a==b: return a\n    for k in range(LOG,-1,-1):\n        if up[a][k]!=up[b][k]: a=up[a][k]; b=up[b][k]\n    return up[a][0]\nfor _ in range(q):\n    a,b=map(int,input().split()); print(lca(a,b))","倍增 LCA 预处理每个点向上 2^k 步的祖先（up[u][k]=up[up[u][k-1]][k-1]），查询时先把较深点对齐深度，再从高到低尝试同步上跳至分叉点上方，父节点即 LCA；预处理 O(n log n)，查询 O(log n)。","18图基础-A2-5","负环检测",{"id":11768,"title":11769,"level":6350,"module":11109,"seq":563,"goal":11771,"keywords":11772,"explanation":11775,"examples":11787,"exercises":11792,"summary":11821,"next":11822,"nextTitle":11823},"掌握负环检测：在 SPFA\u002FBellman-Ford 中，若某点被松弛（入队）次数达到 n 次以上，说明存在可无限缩短的负权环。",[11341,11339,11773,11774],"入队计数","负权回路",[11776,11781],{"title":11777,"content":11778,"codePy":11779,"codeCpp":11780},"1. SPFA 入队计数","对每个点记录入队次数；若某点入队次数 > n，则存在负环（可无限绕圈减小距离）。","def has_neg_cycle():\n    cnt=[0]*(n+1); inq=[False]*(n+1); d=[0]*(n+1)\n    q=deque(range(1,n+1))  # 全入队以覆盖各连通块\n    while q:\n        u=q.popleft(); inq[u]=False\n        for v,w in g[u]:\n            if d[u]+w\u003Cd[v]:\n                d[v]=d[u]+w; cnt[v]+=1\n                if cnt[v]>n: return True\n                if not inq[v]: q.append(v); inq[v]=True\n    return False","\u002F\u002F 入队>n 即负环",{"title":11782,"content":11783,"codePy":11784,"codeCpp":11785,"tip":11786},"2. 与最短路关系","存在负环时“最短路”无定义（可无限小）；很多题目先判负环再求最短路。","print(\"负环使最短路无意义\")","\u002F\u002F Bellman-Ford 第 n 轮松弛也判定","tip: 为覆盖所有连通块，初始把所有点入队并将 d 置 0。",[11788],{"title":11789,"prompt":11790,"analysis":11791,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：负环","1->2 权 1，2->1 权 -2，有负环？","是（绕圈可无限减）。",{"choice":11793,"coding":11811},[11794,11799,11806],{"id":38,"stem":11795,"options":11796,"answer":9,"explanation":11798},"负环检测依据？",[11377,11797,11378,1568],"某点入队>n","松弛次数超点数。",{"id":47,"stem":11800,"options":11801,"answer":9,"explanation":11805},"有负环时最短路？",[11802,11803,11804,891],"正常","无定义","无穷","可无限减小。",{"id":56,"stem":11807,"options":11808,"answer":9,"explanation":11810},"初始全入队目的？",[6446,11809,3117,6810],"覆盖各连通块","确保每个块都被检测。",[11812],{"id":67,"title":11769,"prompt":11813,"inputFormat":11323,"outputFormat":8414,"samples":11814,"starterPy":11817,"starterCpp":11818,"hint":11819,"referencePy":11820,"referenceCpp":11818},"n 点 m 边有向图（可含负权），判断是否存在从任意点可达的负权环。存在输出 YES 否则 NO。",[11815],{"input":11816,"output":8418},"2 2\n1 2 1\n2 1 -2","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\ncnt=[0]*(n+1); inq=[False]*(n+1); d=[0]*(n+1)\nq=deque(range(1,n+1))\nfor i in range(1,n+1): inq[i]=True\nok=False\nwhile q:\n    u=q.popleft(); inq[u]=False\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]:\n            d[v]=d[u]+w; cnt[v]+=1\n            if cnt[v]>n: ok=True; break\n            if not inq[v]: q.append(v); inq[v]=True\n    if ok: break\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nvector\u003Cpair\u003Cint,long long>> g[100005]; int cnt[100005]; bool inq[100005]; long long d[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v,w;i\u003Cm;i++){cin>>u>>v>>w; g[u].push_back({v,w});} queue\u003Cint> q; for(int i=1;i\u003C=n;i++){ inq[i]=true; q.push(i);} bool neg=false; while(!q.empty()){ int u=q.front(); q.pop(); inq[u]=false; for(auto& e:g[u]){ int v=e.first; long long w=e.second; if(d[u]+w\u003Cd[v]){ d[v]=d[u]+w; cnt[v]++; if(cnt[v]>n){ neg=true; break;} if(!inq[v]){ inq[v]=true; q.push(v);} } } if(neg) break; } cout\u003C\u003C(neg?\"YES\":\"NO\"); }","SPFA 入队计数 >n 判负环。","from collections import deque\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\ncnt=[0]*(n+1); inq=[False]*(n+1); d=[0]*(n+1)\nq=deque(range(1,n+1))\nfor i in range(1,n+1): inq[i]=True\nok=False\nwhile q:\n    u=q.popleft(); inq[u]=False\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]:\n            d[v]=d[u]+w; cnt[v]+=1\n            if cnt[v]>n: ok=True; break\n            if not inq[v]: q.append(v); inq[v]=True\n    if ok: break\nprint(\"YES\" if ok else \"NO\")","负环检测用 SPFA：初始把所有点入队（覆盖各连通块），当某点被松弛（入队）次数超过 n 时即存在负权环——因为无负环时每点最多被松弛 n-1 次；有负环则最短路无定义。","18图基础-A2-6","最短路径条数",{"id":11822,"title":11823,"level":6350,"module":11109,"seq":9715,"goal":11825,"keywords":11826,"explanation":11830,"examples":11841,"exercises":11844,"summary":11874,"next":11875,"nextTitle":11876},"掌握在 Dijkstra\u002FBFS 过程中统计最短路径条数：当找到更短路径时刷新条数，当找到等长路径时累加条数（取模）。",[11827,11278,11828,11829],"最短路计数","路径条数","DP on graph",[11831,11836],{"title":11832,"content":11833,"codePy":11834,"codeCpp":11835},"1. 计数伴随最短路","dist 更小时 ways=前驱 ways；dist 相等时 ways+=前驱 ways（对前驱取模）。","ways=[0]*(n+1); ways[s]=1\ndef relax(u,v,w):\n    if d[u]+w\u003Cd[v]: d[v]=d[u]+w; ways[v]=ways[u]\n    elif d[u]+w==d[v]: ways[v]=(ways[v]+ways[u])%MOD","\u002F\u002F 更短刷新、相等累加",{"title":10425,"content":11837,"codePy":11838,"codeCpp":11839,"tip":11840},"必须用严格比较区分“更短”与“相等”；负权图改用 Bellman-Ford 动态规划计数。","print(\"更短更新、等长相加\")","\u002F\u002F 记得取模","tip: 起点 ways=1，其余 0；不可用堆里过时项更新计数。",[11842],{"title":5847,"prompt":11843,"analysis":10094,"codePy":6253,"codeCpp":5851,"run":759},"s 到 t 有两条等长最短路，ways[t]=？",{"choice":11845,"coding":11863},[11846,11853,11859],{"id":38,"stem":11847,"options":11848,"answer":9,"explanation":11852},"找到更短路径时 ways？",[11849,11850,1307,11851],"加","刷新为前驱","清零","改为前驱条数。",{"id":47,"stem":11854,"options":11855,"answer":9,"explanation":11858},"找到等长路径时 ways？",[11856,11857,1307,2478],"刷新","累加前驱","加前驱条数。",{"id":56,"stem":11860,"options":11861,"answer":9,"explanation":11862},"起点 ways[s]=？",[891,892,3054,11804],"自身 1 条。",[11864],{"id":67,"title":11823,"prompt":11865,"inputFormat":11323,"outputFormat":11866,"samples":11867,"starterPy":11870,"starterCpp":11871,"hint":11872,"referencePy":11873,"referenceCpp":11871},"n 点 m 边非负权图，求 1 号点到 n 号点的最短距离与最短路条数（条数对 10^9+7 取模）。","一行两个整数：最短距离与条数。",[11868],{"input":11869,"output":10690},"4 4\n1 2 1\n1 3 2\n2 4 1\n3 4 1","import heapq\nMOD=10**9+7\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; ways=[0]*(n+1); ways[1]=1; h=[(0,1)]\nwhile h:\n    du,u=heapq.heappop(h)\n    if du>d[u]: continue\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w; ways[v]=ways[u]; heapq.heappush(h,(d[v],v))\n        elif d[u]+w==d[v]: ways[v]=(ways[v]+ways[u])%MOD\nprint(d[n], ways[n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nconst long long INF=1e18; const int MOD=1000000007; vector\u003Cpair\u003Cint,long long>> g[100005]; long long d[100005]; int ways[100005];\nint main(){ int n,m; cin>>n>>m; for(int i=0,u,v,w;i\u003Cm;i++){cin>>u>>v>>w; g[u].push_back({v,w});} for(int i=1;i\u003C=n;i++) d[i]=INF; d[1]=0; ways[1]=1; priority_queue\u003Cpair\u003Clong long,int>,vector\u003Cpair\u003Clong long,int>>,greater\u003Cpair\u003Clong long,int>>> pq; pq.push({0,1}); while(!pq.empty()){ auto [du,u]=pq.top(); pq.pop(); if(du>d[u]) continue; for(auto& e:g[u]){ int v=e.first; long long w=e.second; if(d[u]+w\u003Cd[v]){ d[v]=d[u]+w; ways[v]=ways[u]; pq.push({d[v],v}); } else if(d[u]+w==d[v]){ ways[v]=(ways[v]+ways[u])%MOD; } } } cout\u003C\u003Cd[n]\u003C\u003C\" \"\u003C\u003Cways[n]; }","更短刷新、等长累加，取模。","import heapq\nMOD=10**9+7\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,w=map(int,input().split()); g[u].append((v,w))\nINF=float(\"inf\"); d=[INF]*(n+1); d[1]=0; ways=[0]*(n+1); ways[1]=1; h=[(0,1)]\nwhile h:\n    du,u=heapq.heappop(h)\n    if du>d[u]: continue\n    for v,w in g[u]:\n        if d[u]+w\u003Cd[v]: d[v]=d[u]+w; ways[v]=ways[u]; heapq.heappush(h,(d[v],v))\n        elif d[u]+w==d[v]: ways[v]=(ways[v]+ways[u])%MOD\nprint(d[n], ways[n])","最短路径条数随最短路一起维护：用 Dijkstra 时，若 d[u]+w\u003Cd[v] 则刷新距离并把 ways[v] 设为 ways[u]；若 d[u]+w==d[v] 则 ways[v] 累加 ways[u]（取模）；起点 ways=1。注意区分“更短”与“等长”两种情况。","22DP基础-A3-1","线性 DP：LIS 与 LCS",{"id":10903,"title":10904,"level":6178,"module":11878,"seq":9,"goal":11879,"keywords":11880,"explanation":11884,"examples":11896,"exercises":11901,"summary":11929,"next":11930,"nextTitle":11931},"动态规划基础","理解 DP 三要素：状态定义、状态转移方程、边界\u002F初始；用“大问题=小问题组合”避免重复计算。",[11881,11882,11883,3907],"动态规划","状态","转移",[11885,11890],{"title":11886,"content":11887,"codePy":11888,"codeCpp":11889},"1. 三要素","①定义 dp[i] 表示什么；②写出 dp[i] 与更小 dp 的关系（转移）；③确定初值。","print(\"状态 + 转移 + 边界\")","\u002F\u002F 自底向上填表",{"title":11891,"content":11892,"codePy":11893,"codeCpp":11894,"tip":11895},"2. 与贪心\u002F搜索","DP 处理有重叠子问题、最优子结构的问题；比爆搜快（记忆化），比贪心适用范围更广。","print(\"DP 记结果避免重复递归\")","\u002F\u002F 斐波那契就是最简 DP","tip: 先想“最后一步”怎么决策，常能写出转移。",[11897],{"title":11898,"prompt":11899,"analysis":11900,"codePy":6544,"codeCpp":6545,"run":890},"例 1：爬楼梯","每次 1 或 2 阶，n=3 种走法？","dp[3]=dp[2]+dp[1]=2+1=3。",{"choice":11902,"coding":11919},[11903,11907,11913],{"id":38,"stem":11904,"options":11905,"answer":703,"explanation":11906},"DP 三要素不含？",[11882,11883,3907,1568],"状态\u002F转移\u002F边界。",{"id":47,"stem":11908,"options":11909,"answer":9,"explanation":11912},"DP 适合哪类问题？",[11910,11911,3117,4624],"无重叠","重叠子问题+最优子结构","可分解复用。",{"id":56,"stem":11914,"options":11915,"answer":607,"explanation":11918},"“最后一步”思考用于？",[11916,3117,11917,4166],"写转移","建图","常能导出转移。",[11920],{"id":67,"title":11921,"prompt":11922,"inputFormat":1456,"outputFormat":6275,"samples":11923,"starterPy":11925,"starterCpp":11926,"hint":11927,"referencePy":11928,"referenceCpp":11926},"爬楼梯","每次可走 1 或 2 级台阶，求到第 n 级的方法数（对 1e9+7 取模）。",[11924],{"input":890,"output":890},"n=int(input()); dp=[0]*(n+1); dp[0]=1\nif n>=1: dp[1]=1\nfor i in range(2,n+1): dp[i]=(dp[i-1]+dp[i-2])%1000000007\nprint(dp[n])\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; long long a=1,b=1; for(int i=2;i\u003C=n;i++){ long long c=(a+b)%1000000007; a=b; b=c; } cout\u003C\u003C(n==0?1:b); }","dp[i]=dp[i-1]+dp[i-2]。","n=int(input()); dp=[0]*(n+1); dp[0]=1\nif n>=1: dp[1]=1\nfor i in range(2,n+1): dp[i]=(dp[i-1]+dp[i-2])%1000000007\nprint(dp[n])","动态规划三要素：状态定义、转移方程、边界初值；它用“大问题=小问题组合”并记住结果，适合带重叠子问题与最优子结构的问题。","22DP基础-A1-2","记忆化搜索（自顶向下）",{"id":11930,"title":11931,"level":6178,"module":11878,"seq":63,"goal":11933,"keywords":11934,"explanation":11938,"examples":11950,"exercises":11955,"summary":11985,"next":11986,"nextTitle":11987},"掌握自顶向下 DP：用递归 + 缓存（记忆化）避免重复子问题，与自底向上填表等价且更易写出。",[11935,1405,11936,11937],"记忆化","缓存","自顶向下",[11939,11944],{"title":11940,"content":11941,"codePy":11942,"codeCpp":11943},"1. 写法","先写暴力递归，再用字典\u002F数组缓存已算状态，第二次直接返回。","from functools import lru_cache\n@lru_cache(None)\ndef f(n):\n    if n\u003C2: return 1\n    return f(n-1)+f(n-2)","\u002F\u002F int memo[N]; if(memo[n]!=-1) return memo[n];",{"title":11945,"content":11946,"codePy":11947,"codeCpp":11948,"tip":11949},"2. 与填表","记忆化是自顶向下、按需计算；填表是自底向上、全算。二者复杂度相同，记忆化可跳过无用状态。","print(\"记忆化=带缓存的搜索\")","\u002F\u002F 注意递归深度限制","tip: 写不出转移时，先写暴搜再加缓存最稳。",[11951],{"title":11952,"prompt":11953,"analysis":11954,"codePy":8332,"codeCpp":8333,"run":148},"例 1：斐波","记忆化 f(5)（f(0)=f(1)=1）？","f(5)=f(4)+f(3)=5+3=8。",{"choice":11956,"coding":11974},[11957,11962,11968],{"id":38,"stem":11958,"options":11959,"answer":9,"explanation":11961},"记忆化本质是？",[6039,11960,3117,7747],"带缓存的递归","避免重复。",{"id":47,"stem":11963,"options":11964,"answer":9,"explanation":11967},"记忆化与填表复杂度？",[11965,11966,9512,1448],"不同","相同","都算每状态一次。",{"id":56,"stem":11969,"options":11970,"answer":9,"explanation":11973},"缓存未命中时？",[11971,11972,567,1568],"返回0","计算并存入","算后缓存。",[11975],{"id":67,"title":11976,"prompt":11977,"inputFormat":11978,"outputFormat":6275,"samples":11979,"starterPy":11981,"starterCpp":11982,"hint":11983,"referencePy":11984,"referenceCpp":11982},"记忆化爬楼梯","用记忆化递归求到第 n 级的方法数（%1e9+7）。","一行整数 n（n≤1e5）。",[11980],{"input":290,"output":148},"import sys\nsys.setrecursionlimit(1000000)\nn=int(input()); memo={0:1,1:1}\ndef f(k):\n    if k in memo: return memo[k]\n    memo[k]=(f(k-1)+f(k-2))%1000000007; return memo[k]\nprint(f(n))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Clong long> memo;\nlong long f(int k){ if(k\u003C2) return 1; if(memo[k]!=-1) return memo[k]; return memo[k]=(f(k-1)+f(k-2))%1000000007; }\nint main(){ int n; cin>>n; memo.assign(n+1,-1); cout\u003C\u003Cf(n); }","字典\u002F数组缓存已算状态。","import sys\nsys.setrecursionlimit(1000000)\nn=int(input()); memo={0:1,1:1}\ndef f(k):\n    if k in memo: return memo[k]\n    memo[k]=(f(k-1)+f(k-2))%1000000007; return memo[k]\nprint(f(n))","记忆化 = 递归 + 缓存：先写暴力递归，用字典\u002F数组存已算状态，第二次直接返回；与自底向上填表复杂度相同，且更易从搜索改写。","22DP基础-A1-3","最长上升子序列 LIS",{"id":11986,"title":11987,"level":6178,"module":11878,"seq":703,"goal":11989,"keywords":11990,"explanation":11994,"examples":12006,"exercises":12011,"summary":12037,"next":12038,"nextTitle":12039},"掌握 LIS：dp[i]=以 a[i] 结尾的最长上升子序列长度，O(n²)；以及用“贪心+二分”的 O(n log n) 优化。",[11991,11992,11993,2564],"LIS","上升子序列","二分优化",[11995,12000],{"title":11996,"content":11997,"codePy":11998,"codeCpp":11999},"1. 基础 DP","dp[i]=max(dp[j]+1) 对所有 j\u003Ci 且 a[j]\u003Ca[i]；答案取 max dp[i]。","def lis(a):\n    dp=[1]*len(a)\n    for i in range(len(a)):\n        for j in range(i):\n            if a[j]\u003Ca[i]: dp[i]=max(dp[i],dp[j]+1)\n    return max(dp)","\u002F\u002F O(n^2)",{"title":12001,"content":12002,"codePy":12003,"codeCpp":12004,"tip":12005},"2. 二分优化","维护“长度=k 的最小结尾”数组 tails，对每个 a[i] 二分找位置替换，tails 长度即 LIS。","def lis2(a):\n    t=[]\n    for x in a:\n        import bisect; i=bisect.bisect_left(t,x)\n        if i==len(t): t.append(x)\n        else: t[i]=x\n    return len(t)","\u002F\u002F O(n log n)","tip: 竞赛常用二分优化版。",[12007],{"title":12008,"prompt":12009,"analysis":12010,"codePy":6253,"codeCpp":5851,"run":759},"例 1：LIS","[2,1,3] 的 LIS 长度？","1 3 或 2 3，长度 2。",{"choice":12012,"coding":12026},[12013,12017,12022],{"id":38,"stem":12014,"options":12015,"answer":9,"explanation":12016},"LIS 基础 DP 复杂度？",[2338,2339,2564,1245],"双重循环。",{"id":47,"stem":12018,"options":12019,"answer":9,"explanation":12021},"二分优化依赖？",[3117,12020,6210,6565],"tails 数组+二分","维护最小结尾。",{"id":56,"stem":12023,"options":12024,"answer":9,"explanation":12025},"[1,3,2,4] 的 LIS？",[759,890,1663,892],"1 3 4 或 1 2 4，长度3。",[12027],{"id":67,"title":12028,"prompt":12029,"inputFormat":1801,"outputFormat":6275,"samples":12030,"starterPy":12033,"starterCpp":12034,"hint":12035,"referencePy":12036,"referenceCpp":12034},"LIS 长度","求序列的最长严格上升子序列长度。",[12031],{"input":12032,"output":890},"4\n1 3 2 4","import bisect\nn=int(input()); a=list(map(int,input().split())); t=[]\nfor x in a:\n    i=bisect.bisect_left(t,x)\n    if i==len(t): t.append(x)\n    else: t[i]=x\nprint(len(t))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n),t; for(int i=0;i\u003Cn;i++) cin>>a[i]; for(int x:a){ auto it=lower_bound(t.begin(),t.end(),x); if(it==t.end()) t.push_back(x); else *it=x; } cout\u003C\u003Ct.size(); }","tails 数组二分替换。","import bisect\nn=int(input()); a=list(map(int,input().split())); t=[]\nfor x in a:\n    i=bisect.bisect_left(t,x)\n    if i==len(t): t.append(x)\n    else: t[i]=x\nprint(len(t))","LIS：dp[i] 为以 a[i] 结尾的最长上升子序列长度（O(n²)）；用“维护各长度最小结尾 tails + 二分”可优化到 O(n log n)，tails 长度即答案。","22DP基础-A1-4","最长公共子序列 LCS",{"id":12038,"title":12039,"level":6178,"module":11878,"seq":770,"goal":12041,"keywords":12042,"explanation":12046,"examples":12056,"exercises":12061,"summary":12091,"next":12092,"nextTitle":12093},"掌握 LCS：二维 DP，dp[i][j] 为 a 前 i 与 b 前 j 的 LCS 长度，相等则 +1，否则取两侧最大。",[12043,12044,12045,11883],"LCS","二维DP","子序列",[12047,12052],{"title":12048,"content":12049,"codePy":12050,"codeCpp":12051},"1. 转移","若 a[i]==b[j]：dp[i][j]=dp[i-1][j-1]+1；否则 dp[i][j]=max(dp[i-1][j], dp[i][j-1])。","def lcs(a,b):\n    n,m=len(a),len(b); dp=[[0]*(m+1) for _ in range(n+1)]\n    for i in range(1,n+1):\n        for j in range(1,m+1):\n            if a[i-1]==b[j-1]: dp[i][j]=dp[i-1][j-1]+1\n            else: dp[i][j]=max(dp[i-1][j],dp[i][j-1])\n    return dp[n][m]","\u002F\u002F O(nm)",{"title":10425,"content":12053,"codePy":12054,"codeCpp":12055},"子序列不要求连续；若要求连续则是“最长公共子串”，转移不同。","print(\"子序列可不连续\")","\u002F\u002F 空间可滚动优化",[12057],{"title":12058,"prompt":12059,"analysis":12060,"codePy":6253,"codeCpp":5851,"run":759},"例 1：LCS","a=ABC, b=AC，LCS 长度？","A C，长度 2。",{"choice":12062,"coding":12079},[12063,12067,12073],{"id":38,"stem":12064,"options":12065,"answer":9,"explanation":12066},"LCS 复杂度？",[2338,4504,2339,1246],"二维。",{"id":47,"stem":12068,"options":12069,"answer":9,"explanation":12072},"a[i]==b[j] 时？",[12070,12071,891,505],"取max","dp[i-1][j-1]+1","匹配延伸。",{"id":56,"stem":12074,"options":12075,"answer":9,"explanation":12078},"子序列要求连续吗？",[12076,12077,10455,10330],"要求","不要求","可不连续。",[12080],{"id":67,"title":12081,"prompt":12082,"inputFormat":12083,"outputFormat":6275,"samples":12084,"starterPy":12087,"starterCpp":12088,"hint":12089,"referencePy":12090,"referenceCpp":12088},"LCS 长度","求两字符串的最长公共子序列长度。","两行字符串。",[12085],{"input":12086,"output":759},"ABC\nAC","a=input().strip(); b=input().strip(); n,m=len(a),len(b)\ndp=[[0]*(m+1) for _ in range(n+1)]\nfor i in range(1,n+1):\n    for j in range(1,m+1):\n        if a[i-1]==b[j-1]: dp[i][j]=dp[i-1][j-1]+1\n        else: dp[i][j]=max(dp[i-1][j],dp[i][j-1])\nprint(dp[n][m])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ string a,b; cin>>a>>b; int n=a.size(),m=b.size(); vector\u003Cvector\u003Cint>> dp(n+1,vector\u003Cint>(m+1,0)); for(int i=1;i\u003C=n;i++) for(int j=1;j\u003C=m;j++){ if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } cout\u003C\u003Cdp[n][m]; }","相等+1，否则取两侧最大。","a=input().strip(); b=input().strip(); n,m=len(a),len(b)\ndp=[[0]*(m+1) for _ in range(n+1)]\nfor i in range(1,n+1):\n    for j in range(1,m+1):\n        if a[i-1]==b[j-1]: dp[i][j]=dp[i-1][j-1]+1\n        else: dp[i][j]=max(dp[i-1][j],dp[i][j-1])\nprint(dp[n][m])","LCS 用二维 DP：a[i]==b[j] 时 dp[i][j]=dp[i-1][j-1]+1，否则取 max(上,左)；复杂度 O(nm)。子序列不要求连续（连续则是子串）。","22DP基础-A1-5","数字三角形（线性 DP）",{"id":12092,"title":12093,"level":6178,"module":11878,"seq":838,"goal":12095,"keywords":12096,"explanation":12101,"examples":12111,"exercises":12119,"summary":12146,"next":12147,"nextTitle":12148},"用数字三角形巩固线性 DP：自底向上，每个点取左下\u002F右下较大者累加，得到从顶到的最大路径和。",[12097,12098,12099,12100],"数字三角形","线性DP","自底向上","路径和",[12102,12106],{"title":12048,"content":12103,"codePy":12104,"codeCpp":12105},"dp[i][j]=a[i][j]+max(dp[i+1][j], dp[i+1][j+1])，自底向上推到顶点。","def tri(a):\n    n=len(a); dp=[row[:] for row in a]\n    for i in range(n-2,-1,-1):\n        for j in range(i+1):\n            dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1])\n    return dp[0][0]","\u002F\u002F 从倒数第二层往上",{"title":12107,"content":12108,"codePy":12109,"codeCpp":12110},"2. 思路","“从顶到底”等价于“从底到顶”；自底向上避免边界讨论，也利于空间优化为一维。","print(\"自底向上更简洁\")","\u002F\u002F 可滚动到一维",[12112],{"title":12113,"prompt":12114,"analysis":12115,"codePy":12116,"codeCpp":12117,"run":12118},"例 1：三角形","三层 7\u002F3 8\u002F8 1 0，最大路径和？","7+8(max(3,8))? 实际 7->8->1? 路径 7-3-8? 取 7+max(3,8)=15... 详细：底 8 1 0，上 3 8 选 max(下两组) 3+max(8,1)=11, 8+max(1,0)=9；顶 7+max(11,9)=18。","print(18)","\u002F\u002F 18","18",{"choice":12120,"coding":12135},[12121,12125,12129],{"id":38,"stem":12122,"options":12123,"answer":9,"explanation":12124},"数字三角形常用？",[6039,6040,7747,3117],"每步取最大。",{"id":47,"stem":12126,"options":12127,"answer":9,"explanation":12128},"推荐方向？",[11937,12099,8029,1568],"少边界讨论。",{"id":56,"stem":12130,"options":12131,"answer":9,"explanation":12134},"转移取？",[12132,12133,5985,1568],"min","max(左下,右下)","最大路径和。",[12136],{"id":67,"title":12097,"prompt":12137,"inputFormat":12138,"outputFormat":6275,"samples":12139,"starterPy":12142,"starterCpp":12143,"hint":12144,"referencePy":12145,"referenceCpp":12143},"n 层数字三角形，求从顶到底的最大路径和（只能走向正下或右下）。","第一行 n；接下来 n 行，第 i 行 i 个整数。",[12140],{"input":12141,"output":12118},"3\n7\n3 8\n8 1 0","n=int(input()); a=[list(map(int,input().split())) for _ in range(n)]\ndp=[row[:] for row in a]\nfor i in range(n-2,-1,-1):\n    for j in range(i+1):\n        dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1])\nprint(dp[0][0])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cvector\u003Cint>> a(n),dp(n); for(int i=0;i\u003Cn;i++){ a[i].resize(i+1); dp[i].resize(i+1); for(int j=0;j\u003C=i;j++){ cin>>a[i][j]; dp[i][j]=a[i][j]; } } for(int i=n-2;i>=0;i--) for(int j=0;j\u003C=i;j++) dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]); cout\u003C\u003Cdp[0][0]; }","自底向上，取左下右下较大。","n=int(input()); a=[list(map(int,input().split())) for _ in range(n)]\ndp=[row[:] for row in a]\nfor i in range(n-2,-1,-1):\n    for j in range(i+1):\n        dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1])\nprint(dp[0][0])","数字三角形是线性 DP 入门：自底向上 dp[i][j]=a[i][j]+max(左下,右下)，推到顶点即得最大路径和；也可滚动到一维节省空间。","22DP基础-A2-1","0-1 背包",{"id":12147,"title":12148,"level":6178,"module":12150,"seq":9,"goal":12151,"keywords":12152,"explanation":12156,"examples":12166,"exercises":12171,"summary":12203,"next":12204,"nextTitle":12205},"动态规划进阶（序列\u002F背包）","掌握 0-1 背包：n 件物品每种至多选一次，dp[i][j] 为前 i 件在容量 j 下的最大价值，转移“选\u002F不选”。",[12153,12154,11883,12155],"0-1背包","容量","最优",[12157,12161],{"title":12048,"content":12158,"codePy":12159,"codeCpp":12160},"dp[i][j]=max(dp[i-1][j], dp[i-1][j-w]+v)（当 j≥w）。","def knapsack(w,v,cap):\n    n=len(w); dp=[[0]*(cap+1) for _ in range(n+1)]\n    for i in range(1,n+1):\n        for j in range(cap+1):\n            dp[i][j]=dp[i-1][j]\n            if j>=w[i-1]: dp[i][j]=max(dp[i][j],dp[i-1][j-w[i-1]]+v[i-1])\n    return dp[n][cap]","\u002F\u002F O(n*cap)",{"title":12162,"content":12163,"codePy":12164,"codeCpp":12165},"2. 理解","“选”则占用容量 w 并获得 v；“不选”则沿用上一件；取较大。","print(\"每件选或不选\")","\u002F\u002F 0-1：每件最多一次",[12167],{"title":12168,"prompt":12169,"analysis":12170,"codePy":6021,"codeCpp":6022,"run":885},"例 1：背包","容量 4，物品 (w2,v3),(w3,v4)，最大价值？","选两件 2+3=5 容量够，价值 3+4=7。",{"choice":12172,"coding":12192},[12173,12180,12187],{"id":38,"stem":12174,"options":12175,"answer":9,"explanation":12179},"0-1 背包每件？",[12176,12177,12178,1568],"无限","至多一次","必须选","选或不选。",{"id":47,"stem":12181,"options":12182,"answer":63,"explanation":12186},"转移考虑？",[12183,12184,12185,3117],"仅选","仅不选","选\u002F不选取大","max 两者。",{"id":56,"stem":12188,"options":12189,"answer":9,"explanation":12191},"复杂度？",[2338,12190,2823,2339],"O(n*cap)","物品×容量。",[12193],{"id":67,"title":12148,"prompt":12194,"inputFormat":12195,"outputFormat":6275,"samples":12196,"starterPy":12199,"starterCpp":12200,"hint":12201,"referencePy":12202,"referenceCpp":12200},"n 件物品（重量 w、价值 v），背包容量 C，每件至多选一次，求最大价值。","第一行 n C；接下来 n 行 w v。",[12197],{"input":12198,"output":885},"2 4\n2 3\n3 4","n,C=map(int,input().split()); w=[]; v=[]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ndp=[0]*(C+1)\nfor i in range(n):\n    for j in range(C,w[i]-1,-1):\n        dp[j]=max(dp[j], dp[j-w[i]]+v[i])\nprint(dp[C])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,C; cin>>n; vector\u003Cint> w(n),v(n); for(int i=0;i\u003Cn;i++) cin>>w[i]>>v[i]; vector\u003Cint> dp(C+1,0); for(int i=0;i\u003Cn;i++) for(int j=C;j>=w[i];j--) dp[j]=max(dp[j],dp[j-w[i]]+v[i]); cout\u003C\u003Cdp[C]; }","用一维数组并倒序枚举容量。","n,C=map(int,input().split()); w=[]; v=[]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ndp=[0]*(C+1)\nfor i in range(n):\n    for j in range(C,w[i]-1,-1):\n        dp[j]=max(dp[j], dp[j-w[i]]+v[i])\nprint(dp[C])","0-1 背包：每件至多选一次，dp[j]=max(不选, 选 j-w[i]+v[i])；用一维数组并倒序枚举容量可正确且省空间，复杂度 O(n·cap)。","22DP基础-A2-2","完全背包",{"id":12204,"title":12205,"level":6178,"module":12150,"seq":63,"goal":12207,"keywords":12208,"explanation":12210,"examples":12220,"exercises":12225,"summary":12253,"next":12254,"nextTitle":12255},"掌握完全背包：每种物品可无限选，转移时容量正序枚举（同一物品可被重复选多次）。",[12205,12209,11647,11883],"无限选",[12211,12216],{"title":12212,"content":12213,"codePy":12214,"codeCpp":12215},"1. 与 0-1 区别","完全背包容量正序枚举：dp[j]=max(dp[j], dp[j-w]+v)，使每件可被多次选。","def complete(w,v,cap):\n    dp=[0]*(cap+1)\n    for i in range(n):\n        for j in range(w[i],cap+1):\n            dp[j]=max(dp[j], dp[j-w[i]]+v[i])\n    return dp[cap]","\u002F\u002F 正序=可重复选",{"title":12162,"content":12217,"codePy":12218,"codeCpp":12219},"正序时 dp[j-w] 已包含“当前物品被选过”的状态，于是可再选。","print(\"正序枚举=无限选\")","\u002F\u002F 与 0-1 仅枚举方向不同",[12221],{"title":12222,"prompt":12223,"analysis":12224,"codePy":4786,"codeCpp":4432,"run":624},"例 1：完全背包","容量 5，物品 (w2,v3)，最多价值？","选 2 次（4 容量）得 6，余 1 浪费。",{"choice":12226,"coding":12243},[12227,12232,12239],{"id":38,"stem":12228,"options":12229,"answer":9,"explanation":12231},"完全背包容量枚举方向？",[5452,11647,1568,12230],"不必","正序可重复选。",{"id":47,"stem":12233,"options":12234,"answer":9,"explanation":12238},"与 0-1 背包差别在？",[12235,12236,12237,12154],"物品数","枚举方向","价值","正序 vs 倒序。",{"id":56,"stem":12240,"options":12241,"answer":9,"explanation":12242},"容量 5 物品(2,3) 完全背包？",[890,624,2707,290],"选两次=6。",[12244],{"id":67,"title":12205,"prompt":12245,"inputFormat":12195,"outputFormat":6275,"samples":12246,"starterPy":12249,"starterCpp":12250,"hint":12251,"referencePy":12252,"referenceCpp":12250},"n 种物品（重量 w、价值 v，每种无限），背包容量 C，求最大价值。",[12247],{"input":12248,"output":624},"1 5\n2 3","n,C=map(int,input().split()); w=[]; v=[]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ndp=[0]*(C+1)\nfor i in range(n):\n    for j in range(w[i],C+1):\n        dp[j]=max(dp[j], dp[j-w[i]]+v[i])\nprint(dp[C])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,C; cin>>n; vector\u003Cint> w(n),v(n); for(int i=0;i\u003Cn;i++) cin>>w[i]>>v[i]; vector\u003Cint> dp(C+1,0); for(int i=0;i\u003Cn;i++) for(int j=w[i];j\u003C=C;j++) dp[j]=max(dp[j],dp[j-w[i]]+v[i]); cout\u003C\u003Cdp[C]; }","正序枚举容量。","n,C=map(int,input().split()); w=[]; v=[]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ndp=[0]*(C+1)\nfor i in range(n):\n    for j in range(w[i],C+1):\n        dp[j]=max(dp[j], dp[j-w[i]]+v[i])\nprint(dp[C])","完全背包每种物品可无限选；只需把 0-1 背包的“倒序枚举容量”改为正序，使同一物品被重复选取，其余转移相同。","22DP基础-A2-3","背包的空间优化（滚动数组）",{"id":12254,"title":12255,"level":6178,"module":12150,"seq":703,"goal":12257,"keywords":12258,"explanation":12262,"examples":12274,"exercises":12281,"summary":12310,"next":12311,"nextTitle":12312},"理解背包为何能滚动到一维：当前行只依赖上一行，故用一维数组并按特定顺序更新即可省去第一维。",[12259,12260,12261,8432],"滚动数组","空间优化","一维",[12263,12268],{"title":12264,"content":12265,"codePy":12266,"codeCpp":12267},"1. 依赖分析","dp[i][j] 只用到 dp[i-1][*]，故可只用一维 dp[j]；0-1 倒序避免同物品重复选，完全背包正序允许重复。","print(\"当前行只依赖上一行\")","\u002F\u002F 一维即滚动",{"title":12269,"content":12270,"codePy":12271,"codeCpp":12272,"tip":12273},"2. 顺序关键","0-1 必须倒序（否则前面已选的状态会再被用，变成完全背包）；完全背包正序。","print(\"倒序=0-1；正序=完全\")","\u002F\u002F 顺序决定能否重复选","tip: 枚举顺序是背包优化的核心。",[12275],{"title":12276,"prompt":12277,"analysis":12278,"codePy":12279,"codeCpp":12280,"run":9997},"例 1：顺序影响","0-1 背包若误用正序会怎样？","变成完全背包（同物可重复选）。","print(\"变成完全背包\")","\u002F\u002F 完全",{"choice":12282,"coding":12299},[12283,12288,12295],{"id":38,"stem":12284,"options":12285,"answer":9,"explanation":12287},"一维 0-1 背包为何倒序？",[1448,12286,1582,1568],"防止同物重复选","只引用旧状态。",{"id":47,"stem":12289,"options":12290,"answer":9,"explanation":12294},"滚动数组依据是？",[1568,12291,12292,12293],"只依赖上一行","全表","无依赖","当前行依赖前一行。",{"id":56,"stem":12296,"options":12297,"answer":9,"explanation":12298},"完全背包一维顺序？",[5452,11647,10330,12230],"正序可重复。",[12300],{"id":67,"title":12301,"prompt":12302,"inputFormat":12195,"outputFormat":6275,"samples":12303,"starterPy":12306,"starterCpp":12307,"hint":12308,"referencePy":12309,"referenceCpp":12307},"滚动 0-1 背包","用一维数组（倒序）实现 0-1 背包，输出最大价值。",[12304],{"input":12305,"output":885},"3 5\n2 3\n3 4\n4 5","n,C=map(int,input().split()); dp=[0]*(C+1)\nfor _ in range(n):\n    w,v=map(int,input().split())\n    for j in range(C,w-1,-1):\n        dp[j]=max(dp[j], dp[j-w]+v)\nprint(dp[C])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,C; cin>>n; vector\u003Cint> dp(C+1,0); for(int i=0;i\u003Cn;i++){ int w,v; cin>>w>>v; for(int j=C;j>=w;j--) dp[j]=max(dp[j],dp[j-w]+v); } cout\u003C\u003Cdp[C]; }","倒序更新一维数组。","n,C=map(int,input().split()); dp=[0]*(C+1)\nfor _ in range(n):\n    w,v=map(int,input().split())\n    for j in range(C,w-1,-1):\n        dp[j]=max(dp[j], dp[j-w]+v)\nprint(dp[C])","背包可滚动到一维，因为当前行只依赖上一行：0-1 背包倒序枚举容量（防止同物重复选），完全背包正序（允许重复）；枚举顺序是优化成败的关键。","22DP基础-A2-4","多重背包与分组背包",{"id":12311,"title":12312,"level":6178,"module":12150,"seq":770,"goal":12314,"keywords":12315,"explanation":12320,"examples":12331,"exercises":12339,"summary":12371,"next":12372,"nextTitle":12373},"掌握两类背包变体：多重背包（每种有数量上限，可二进制拆分转化为 0-1）与分组背包（每组至多选一件）。",[12316,12317,12318,12319],"多重背包","二进制拆分","分组背包","变体",[12321,12326],{"title":12322,"content":12323,"codePy":12324,"codeCpp":12325},"1. 多重背包","物品 i 有 s 件：把 s 拆成 1,2,4,… 若干份（二进制），每份当独立 0-1 物品，总量不变且覆盖所有 0..s 选法。","def split(s):\n    res=[]; k=1\n    while s>0:\n        res.append(min(k,s)); s-=min(k,s); k*=2\n    return res  # 再按 0-1 背包","\u002F\u002F 二进制拆分降为 O(log s) 件",{"title":12327,"content":12328,"codePy":12329,"codeCpp":12330},"2. 分组背包","每组物品至多选一件：外层枚举组，容量倒序，在组内做“选哪件”的最大值。","print(\"分组：每组至多一件\")","\u002F\u002F 组间独立，组内择优",[12332],{"title":12333,"prompt":12334,"analysis":12335,"codePy":12336,"codeCpp":12337,"run":12338},"例 1：拆分","数量 13 二进制拆分？","1,2,4,6 覆盖 0..13。","print([1,2,4,6])","\u002F\u002F 1 2 4 6","1 2 4 6",{"choice":12340,"coding":12360},[12341,12345,12353],{"id":38,"stem":12342,"options":12343,"answer":9,"explanation":12344},"多重背包常用优化？",[3117,12317,6039,7747],"降为 0-1。",{"id":47,"stem":12346,"options":12347,"answer":607,"explanation":12352},"数量 13 拆分含？",[12348,12349,12350,12351],"1,2,4,6","1,2,3,7","1,3,9","13","1+2+4+6=13。",{"id":56,"stem":12354,"options":12355,"answer":9,"explanation":12359},"分组背包每组？",[12356,12357,12358,1568],"全选","至多一件","不限","组内择一。",[12361],{"id":67,"title":12316,"prompt":12362,"inputFormat":12363,"outputFormat":6275,"samples":12364,"starterPy":12367,"starterCpp":12368,"hint":12369,"referencePy":12370,"referenceCpp":12368},"n 种物品，每种有数量 s、重量 w、价值 v，容量 C，求最大价值（二进制拆分法）。","第一行 n C；接下来 n 行 w v s。",[12365],{"input":12366,"output":624},"1 5\n2 3 2","n,C=map(int,input().split()); items=[]\nfor _ in range(n):\n    w,v,s=map(int,input().split()); k=1\n    while s>0:\n        t=min(k,s); items.append((w*t,v*t)); s-=t; k*=2\ndp=[0]*(C+1)\nfor w,v in items:\n    for j in range(C,w-1,-1):\n        dp[j]=max(dp[j], dp[j-w]+v)\nprint(dp[C])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,C; cin>>n; vector\u003Cpair\u003Cint,int>> items; for(int i=0;i\u003Cn;i++){ int w,v,s; cin>>w>>v>>s; int k=1; while(s>0){ int t=min(k,s); items.push_back({w*t,v*t}); s-=t; k*=2; } } vector\u003Cint> dp(C+1,0); for(auto& it:items) for(int j=C;j>=it.first;j--) dp[j]=max(dp[j],dp[j-it.first]+it.second); cout\u003C\u003Cdp[C]; }","二进制拆分后按 0-1 背包。","n,C=map(int,input().split()); items=[]\nfor _ in range(n):\n    w,v,s=map(int,input().split()); k=1\n    while s>0:\n        t=min(k,s); items.append((w*t,v*t)); s-=t; k*=2\ndp=[0]*(C+1)\nfor w,v in items:\n    for j in range(C,w-1,-1):\n        dp[j]=max(dp[j], dp[j-w]+v)\nprint(dp[C])","多重背包（每种有数量上限）可用二进制拆分（1,2,4,…）把数量拆成若干独立 0-1 物品，覆盖所有选法且降为 O(log s) 件；分组背包每组至多选一件，外层枚举组、组内择优。","22DP基础-A2-5","DP 综合应用（最小路径和\u002F编辑距离）",{"id":12372,"title":12373,"level":6178,"module":12150,"seq":838,"goal":12375,"keywords":12376,"explanation":12380,"examples":12391,"exercises":12396,"summary":12427,"next":12428,"nextTitle":12429},"用两个经典模型巩固 DP：网格最小路径和（只能右\u002F下）与编辑距离（增删改使两串相同），体会状态设计。",[12377,12378,12044,12379],"最小路径和","编辑距离","建模",[12381,12385],{"title":12382,"content":12383,"codePy":12384,"codeCpp":12051},"1. 最小路径和","dp[i][j]=grid[i][j]+min(dp[i-1][j], dp[i][j-1])，自左上到右下。","def minpath(g):\n    n,m=len(g),len(g[0]); dp=[[0]*m for _ in range(n)]\n    dp[0][0]=g[0][0]\n    for i in range(n):\n        for j in range(m):\n            if i+j==0: continue\n            dp[i][j]=g[i][j]+min(dp[i-1][j] if i else 1e18, dp[i][j-1] if j else 1e18)\n    return dp[n-1][m-1]",{"title":12386,"content":12387,"codePy":12388,"codeCpp":12389,"tip":12390},"2. 编辑距离","dp[i][j] 为 a 前 i 与 b 前 j 的最少操作（增\u002F删\u002F改）；相等则继承，否则取 插入\u002F删除\u002F替换 的最小值+1。","print(\"编辑距离=增删改最少步\")","\u002F\u002F 经典二维 DP","tip: 建模时先明确 dp 含义。",[12392],{"title":12393,"prompt":12394,"analysis":12395,"codePy":6544,"codeCpp":6545,"run":890},"例 1：路径和","2x2 全 1 网格最小路径和？","右+下 或 下+右，和 1+1+1=3。",{"choice":12397,"coding":12416},[12398,12403,12411],{"id":38,"stem":12399,"options":12400,"answer":9,"explanation":12402},"最小路径和转移取？",[10721,12401,5985,1568],"min(上,左)","取较小分支。",{"id":47,"stem":12404,"options":12405,"answer":9,"explanation":12410},"编辑距离操作含？",[12406,12407,12408,12409],"仅改","增\u002F删\u002F改","仅增","交换","三类。",{"id":56,"stem":12412,"options":12413,"answer":9,"explanation":12415},"两模型都是？",[12261,12414,6039,7747],"二维 DP","二维状态。",[12417],{"id":67,"title":12377,"prompt":12418,"inputFormat":12419,"outputFormat":6275,"samples":12420,"starterPy":12423,"starterCpp":12424,"hint":12425,"referencePy":12426,"referenceCpp":12424},"n×m 网格，只能向右或向下，求左上到右下的最小路径和。","第一行 n m；接下来 n 行 m 个整数。",[12421],{"input":12422,"output":890},"2 2\n1 1\n1 1","n,m=map(int,input().split()); g=[list(map(int,input().split())) for _ in range(n)]\ndp=[[0]*m for _ in range(n)]; dp[0][0]=g[0][0]\nfor i in range(n):\n    for j in range(m):\n        if i==0 and j==0: continue\n        up=dp[i-1][j] if i else 1e18; le=dp[i][j-1] if j else 1e18\n        dp[i][j]=g[i][j]+min(up,le)\nprint(dp[n-1][m-1])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; vector\u003Cvector\u003Cint>> g(n,vector\u003Cint>(m)); for(int i=0;i\u003Cn;i++) for(int j=0;j\u003Cm;j++) cin>>g[i][j]; vector\u003Cvector\u003Cint>> dp(n,vector\u003Cint>(m,0)); dp[0][0]=g[0][0]; for(int i=0;i\u003Cn;i++) for(int j=0;j\u003Cm;j++){ if(i==0&&j==0) continue; int up=i?dp[i-1][j]:1e9, le=j?dp[i][j-1]:1e9; dp[i][j]=g[i][j]+min(up,le); } cout\u003C\u003Cdp[n-1][m-1]; }","dp=格值+min(上,左)。","n,m=map(int,input().split()); g=[list(map(int,input().split())) for _ in range(n)]\ndp=[[0]*m for _ in range(n)]; dp[0][0]=g[0][0]\nfor i in range(n):\n    for j in range(m):\n        if i==0 and j==0: continue\n        up=dp[i-1][j] if i else 1e18; le=dp[i][j-1] if j else 1e18\n        dp[i][j]=g[i][j]+min(up,le)\nprint(dp[n-1][m-1])","网格最小路径和：dp[i][j]=格值+min(上,左)；编辑距离：dp[i][j] 为两前缀最少增删改步（相等继承，否则取三种操作最小+1）。二者都是“先明确状态再写转移”的二维 DP 典范。","26-A2-1","快速幂（模幂）",{"id":11875,"title":11876,"level":6350,"module":11881,"seq":9,"goal":12431,"keywords":12432,"explanation":12433,"examples":12445,"exercises":12449,"summary":12474,"next":12475,"nextTitle":12476},"掌握两类经典线性 DP：最长递增子序列（LIS，O(n log n)）与最长公共子序列（LCS，O(nm)），理解状态定义与转移。",[12098,11991,12043,12045],[12434,12439],{"title":12435,"content":12436,"codePy":12437,"codeCpp":12438},"1. LIS（二分）","维护 tails 数组，x 替换第一个 ≥x 的位置；长度即 LIS。","import bisect\ndef lis(a):\n    t=[]\n    for x in a:\n        i=bisect.bisect_left(t,x); \n        if i==len(t): t.append(x)\n        else: t[i]=x\n    return len(t)","\u002F\u002F 二分找下界",{"title":12440,"content":12441,"codePy":12442,"codeCpp":12443,"tip":12444},"2. LCS","dp[i][j]=LCS(a[:i],b[:j])；a[i]==b[j] 时 +1，否则取 max(上、左)。","def lcs(a,b):\n    m,n=len(a),len(b); dp=[[0]*(n+1) for _ in range(m+1)]\n    for i in range(m):\n        for j in range(n):\n            dp[i+1][j+1]=dp[i][j]+1 if a[i]==b[j] else max(dp[i][j+1],dp[i+1][j])\n    return dp[m][n]","\u002F\u002F 二维 DP","tip: LIS 也可 dp[i]=以 i 结尾最长，O(n²)；LCS 是二维经典。",[12446],{"title":12008,"prompt":12447,"analysis":12448,"codePy":6544,"codeCpp":6545,"run":890},"[3,1,2,4] 的 LIS 长度？","1,2,4 长 3。",{"choice":12450,"coding":12466},[12451,12455,12462],{"id":38,"stem":12452,"options":12453,"answer":9,"explanation":12454},"LIS 二分法复杂度？",[2339,2564,2338,2345],"每次二分。",{"id":47,"stem":12456,"options":12457,"answer":9,"explanation":12461},"LCS 状态维度？",[12261,12458,12459,12460],"二维","三维","零维","dp[i][j]。",{"id":56,"stem":12463,"options":12464,"answer":9,"explanation":12465},"LCS(ABC,BDC)=？",[892,759,890,891],"B、C 公共。",[12467],{"id":67,"title":12468,"prompt":12469,"inputFormat":1801,"outputFormat":6275,"samples":12470,"starterPy":12033,"starterCpp":12034,"hint":12473,"referencePy":12036,"referenceCpp":12034},"最长递增子序列","给定 n 个整数，求最长严格递增子序列长度。",[12471],{"input":12472,"output":890},"4\n3 1 2 4","tails 数组 + 二分。","LIS 用 tails 数组配合二分在 O(n log n) 求得最长严格递增子序列；LCS 用二维 DP，a[i]==b[j] 时 dp 增 1 否则继承左侧或上侧最大值，复杂度 O(nm)。","22DP基础-A3-2","背包进阶（二进制拆分 \u002F 单调队列）",{"id":12478,"title":12479,"level":6350,"module":11881,"seq":89,"goal":12480,"keywords":12481,"explanation":12486,"examples":12498,"exercises":12503,"summary":12535,"next":12536,"nextTitle":12537},"22DP基础-A3-10","博弈 DP（必胜态）","掌握博弈类 DP\u002F递推：用 win[i] 表示面对局面 i 是否必胜（存在一步到必败态即可胜），常用于 Nim 及组合游戏规则分析。",[12482,12483,12484,12485],"博弈DP","必胜态","必败态","Nim",[12487,12492],{"title":12488,"content":12489,"codePy":12490,"codeCpp":12491},"1. 胜负态定义","某个局面为必胜态当且仅当存在一步走到必败态；否则为必败态。自底向上或记忆化递推。","def solve():\n    win=[False]*(n+1)\n    for i in range(1,n+1):\n        # 可走步 m：若任一 win[i-m]==False 则 win[i]=True\n        win[i]=any(not win[i-m] for m in moves if i>=m)\n    return win[n]","\u002F\u002F 能到必败即必胜",{"title":12493,"content":12494,"codePy":12495,"codeCpp":12496,"tip":12497},"2. Nim","n 堆石子 Nim：异或和为 0 则必败，否则必胜；是博弈 DP 的简洁结论。","print(\"异或和判 Nim\")","\u002F\u002F xor=0 必败","tip: 博弈题常先手算小局面的 win 表，找规律或用 SG 函数。",[12499],{"title":12500,"prompt":12501,"analysis":12502,"codePy":9230,"codeCpp":9231,"run":891},"例 1：Nim","三堆 1,2,3，异或和？先手？","1^2^3=0，先手必败。",{"choice":12504,"coding":12523},[12505,12512,12519],{"id":38,"stem":12506,"options":12507,"answer":9,"explanation":12511},"某局面必胜当且仅当？",[12508,12509,1568,12510],"全胜","存在一步到必败","步数多","能到必败即胜。",{"id":47,"stem":12513,"options":12514,"answer":9,"explanation":12518},"Nim 异或和 0 表示？",[12515,12516,12517,1568],"必胜","必败","平","先手必败。",{"id":56,"stem":12520,"options":12521,"answer":607,"explanation":12522},"1^2^3=？",[891,624,890,892],"异或 0。",[12524],{"id":67,"title":12525,"prompt":12526,"inputFormat":12527,"outputFormat":12528,"samples":12529,"starterPy":12531,"starterCpp":12532,"hint":12533,"referencePy":12534,"referenceCpp":12532},"取石子博弈","n 颗石子，两人轮流取，每次可取 1、2 或 3 颗，取最后一颗者胜。给定 n，判断先手是否必胜。","一行整数 n（≤1000）。","一行 YES（先手必胜）或 NO。",[12530],{"input":1663,"output":1943},"n=int(input()); win=[False]*(n+1)\nfor i in range(1,n+1):\n    win[i]=any(not win[i-m] for m in (1,2,3) if i>=m)\nprint(\"YES\" if win[n] else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cbool> win(n+1,false); for(int i=1;i\u003C=n;i++){ for(int m: {1,2,3}) if(i>=m && !win[i-m]) win[i]=true; } cout\u003C\u003C(win[n]?\"YES\":\"NO\"); }","能到必败态即必胜；周期为 4。","n=int(input()); win=[False]*(n+1)\nfor i in range(1,n+1):\n    win[i]=any(not win[i-m] for m in (1,2,3) if i>=m)\nprint(\"YES\" if win[n] else \"NO\")","博弈 DP 用 win\u002Flose 状态：某局面必胜当且仅当存在一步走到必败态；自底向上递推即可。Nim 游戏（多堆取石）有简洁结论：各堆数量的异或和为 0 时先手必败，否则必胜。","22DP基础-A3-11","字符串 DP：编辑距离与回文",{"id":12536,"title":12537,"level":6350,"module":11881,"seq":158,"goal":12539,"keywords":12540,"explanation":12543,"examples":12555,"exercises":12560,"summary":12585,"next":12586,"nextTitle":12587},"掌握字符串类 DP：编辑距离（插入\u002F删除\u002F替换的最小操作数）与最长回文子序列，理解二维 DP 在字符串上的转移。",[12541,12378,12542,12044],"字符串DP","回文",[12544,12549],{"title":12545,"content":12546,"codePy":12547,"codeCpp":12548},"1. 编辑距离","dp[i][j]=a[:i] 到 b[:j] 最小操作；相等继承，否则取 插\u002F删\u002F替 的 min+1。","def edit(a,b):\n    m,n=len(a),len(b); dp=[[0]*(n+1) for _ in range(m+1)]\n    for i in range(m+1): dp[i][0]=i\n    for j in range(n+1): dp[0][j]=j\n    for i in range(m):\n        for j in range(n):\n            dp[i+1][j+1]=dp[i][j] if a[i]==b[j] else 1+min(dp[i][j+1],dp[i+1][j],dp[i][j])\n    return dp[m][n]","\u002F\u002F 增删改三种操作",{"title":12550,"content":12551,"codePy":12552,"codeCpp":12553,"tip":12554},"2. 最长回文子序列","dp[i][j]=区间最长回文子序列；两端相等+2，否则取子区间 max，与区间 DP 同构。","print(\"二维 DP 处理串\")","\u002F\u002F 经典转移","tip: 字符串 DP 常是二维，注意初始化边界（空串）。",[12556],{"title":12557,"prompt":12558,"analysis":12559,"codePy":7878,"codeCpp":7879,"run":892},"例 1：编辑距离","a=\"abc\", b=\"ab\" 编辑距离？","删 c，距离 1。",{"choice":12561,"coding":12574},[12562,12567,12570],{"id":38,"stem":12563,"options":12564,"answer":9,"explanation":12566},"编辑距离操作有几种？",[892,12565,759,1663],"3（增删改）","插入删除替换。",{"id":47,"stem":12568,"options":12569,"answer":9,"explanation":12461},"编辑距离状态维度？",[12261,12458,12459,12460],{"id":56,"stem":12571,"options":12572,"answer":9,"explanation":12573},"abc 到 ab 距离？",[891,892,759,890],"删一个字符。",[12575],{"id":67,"title":12378,"prompt":12576,"inputFormat":12577,"outputFormat":6275,"samples":12578,"starterPy":12581,"starterCpp":12582,"hint":12583,"referencePy":12584,"referenceCpp":12582},"给定两个字符串 a、b，求将 a 变成 b 所需的最少单字符编辑操作（插入\u002F删除\u002F替换）次数。","两行字符串（长度均≤1000）。",[12579],{"input":12580,"output":892},"abc\nab","a=input().strip(); b=input().strip(); m,n=len(a),len(b)\ndp=[[0]*(n+1) for _ in range(m+1)]\nfor i in range(m+1): dp[i][0]=i\nfor j in range(n+1): dp[0][j]=j\nfor i in range(m):\n    for j in range(n):\n        dp[i+1][j+1]=dp[i][j] if a[i]==b[j] else 1+min(dp[i][j+1],dp[i+1][j],dp[i][j])\nprint(dp[m][n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ string a,b; cin>>a>>b; int m=a.size(),n=b.size(); vector\u003Cvector\u003Cint>> dp(m+1,vector\u003Cint>(n+1,0)); for(int i=0;i\u003C=m;i++) dp[i][0]=i; for(int j=0;j\u003C=n;j++) dp[0][j]=j; for(int i=0;i\u003Cm;i++) for(int j=0;j\u003Cn;j++) dp[i+1][j+1]= a[i]==b[j]?dp[i][j]: 1+min({dp[i][j+1],dp[i+1][j],dp[i][j]}); cout\u003C\u003Cdp[m][n]; }","二维 DP，初始化空串边界。","a=input().strip(); b=input().strip(); m,n=len(a),len(b)\ndp=[[0]*(n+1) for _ in range(m+1)]\nfor i in range(m+1): dp[i][0]=i\nfor j in range(n+1): dp[0][j]=j\nfor i in range(m):\n    for j in range(n):\n        dp[i+1][j+1]=dp[i][j] if a[i]==b[j] else 1+min(dp[i][j+1],dp[i+1][j],dp[i][j])\nprint(dp[m][n])","字符串 DP 中编辑距离用二维 dp[i][j]：a[:i] 变 b[:j] 的最少操作，字符相等时继承 dp[i-1][j-1]，否则取插入\u002F删除\u002F替换的最小值 +1；最长回文子序列等也是二维，注意初始化空串边界。","22DP基础-A3-12","动态规划综合与建模",{"id":12586,"title":12587,"level":6350,"module":11881,"seq":230,"goal":12589,"keywords":12590,"explanation":12595,"examples":12607,"exercises":12615,"summary":12647,"next":12648,"nextTitle":12649},"掌握 DP 的通用建模思路：明确状态、转移、边界与答案位置；并能把复杂问题拆成上述经典 DP 的组合或变形。",[12591,12592,12593,12594],"DP建模","状态设计","综合","优化",[12596,12601],{"title":12597,"content":12598,"codePy":12599,"codeCpp":12600},"1. 四步法","①定义状态（代表什么）；②找转移（如何从小推大）；③定边界（最简子问题）；④取答案（哪个状态是所求）。","print(\"状态\u002F转移\u002F边界\u002F答案\")","\u002F\u002F 先想清楚再写",{"title":12602,"content":12603,"codePy":12604,"codeCpp":12605,"tip":12606},"2. 常见套路","线性、区间、树形、状压、数位、概率、字符串是七大类；综合题往往是它们的组合或加维。","print(\"七大类 + 组合\")","\u002F\u002F 多加一维常能化解","tip: 状态设计是 DP 的灵魂，先把“要决策什么、受什么影响”想清楚。",[12608],{"title":12609,"prompt":12610,"analysis":12611,"codePy":12612,"codeCpp":12613,"run":12614},"例 1：加维","要同时限制数量与体积，怎么做？","在背包状态加一维表示数量。","print(\"加一维\")","\u002F\u002F add dim","add dim",{"choice":12616,"coding":12635},[12617,12623,12629],{"id":38,"stem":12618,"options":12619,"answer":9,"explanation":12622},"DP 第一步？",[12620,12621,3117,11917],"写代码","定义状态","状态是灵魂。",{"id":47,"stem":12624,"options":12625,"answer":9,"explanation":12628},"状态不够用时？",[12626,12627,1568,2509],"放弃","加一维","增加约束维度。",{"id":56,"stem":12630,"options":12631,"answer":9,"explanation":12634},"DP 四类边界指？",[1568,12632,4202,12633],"最简子问题","起点","最小可直接求解者。",[12636],{"id":67,"title":12637,"prompt":12638,"inputFormat":12639,"outputFormat":6275,"samples":12640,"starterPy":12643,"starterCpp":12644,"hint":12645,"referencePy":12646,"referenceCpp":12644},"DP 综合：最大子段和","给定 n 个整数，求连续子段的最大和（经典线性 DP 收尾）。","第一行 n；第二行 n 个整数（可为负）。",[12641],{"input":12642,"output":1663},"5\n-2 1 -3 4 -1","n=int(input()); a=list(map(int,input().split()))\ncur=ans=a[0]\nfor x in a[1:]:\n    cur=max(x, cur+x); ans=max(ans,cur)\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; int cur=a[0],ans=a[0]; for(int i=1;i\u003Cn;i++){ cur=max(a[i],cur+a[i]); ans=max(ans,cur);} cout\u003C\u003Cans; }","dp[i]=max(a[i], dp[i-1]+a[i])。","n=int(input()); a=list(map(int,input().split()))\ncur=ans=a[0]\nfor x in a[1:]:\n    cur=max(x, cur+x); ans=max(ans,cur)\nprint(ans)","动态规划建模遵循四步：定义状态（它代表什么子问题）、设计转移（如何由更小规模推出）、确定边界（最简子问题直接赋值）、选取答案所在状态。七大类（线性\u002F区间\u002F树形\u002F状压\u002F数位\u002F概率\u002F字符串）常通过加维或组合来化解综合题。","28-A1-1","哈希表原理与冲突",{"id":12475,"title":12476,"level":6350,"module":11881,"seq":63,"goal":12651,"keywords":12652,"explanation":12654,"examples":12666,"exercises":12674,"summary":12704,"next":12705,"nextTitle":12706},"掌握多重背包的两种优化：二进制拆分将数量拆成 2 的幂转化为 0\u002F1 背包；单调队列优化做到 O(nV)。",[12316,12317,9319,12653],"背包优化",[12655,12660],{"title":12656,"content":12657,"codePy":12658,"codeCpp":12659},"1. 二进制拆分","把件数 c 拆成 1,2,4,...,剩余，每组作为独立 0\u002F1 背包物品，把 c 件压缩成 log c 件。","def split_multi(c, w, v):\n    res=[]; k=1\n    while c>=k:\n        res.append((k*w, k*v)); c-=k; k*=2\n    if c>0: res.append((c*w, c*v))\n    return res","\u002F\u002F 拆成 2 的幂",{"title":12661,"content":12662,"codePy":12663,"codeCpp":12664,"tip":12665},"2. 单调队列优化","对 (j mod w) 的同余类分别做滑动窗口最大\u002F最小，使多重背包降到 O(nV)。","print(\"同余类滑动窗口\")","\u002F\u002F 适合大 V 多物品","tip: 二进制拆分实现简单、足够应对多数题；单调队列用于卡常场景。",[12667],{"title":12668,"prompt":12669,"analysis":12670,"codePy":12671,"codeCpp":12672,"run":12673},"例 1：二进制","c=10 拆成哪些系数？","1,2,4,3（覆盖 0..10 所有数）。","print([1,2,4,3])","\u002F\u002F 1 2 4 3","1 2 4 3",{"choice":12675,"coding":12692},[12676,12682,12688],{"id":38,"stem":12677,"options":12678,"answer":9,"explanation":12681},"多重背包二进制拆分意义？",[12679,12680,4356,3117],"提速排序","把 c 件压缩为 log c 件","任意数量可由幂和表示。",{"id":47,"stem":12683,"options":12684,"answer":9,"explanation":12687},"单调队列优化针对？",[12685,12686,12205,12318],"01背包","多重背包同余类","按模 w 分组滑窗。",{"id":56,"stem":12689,"options":12690,"answer":9,"explanation":12691},"c=10 拆分余下？",[891,890,759,883],"1+2+4=7，余3。",[12693],{"id":67,"title":12694,"prompt":12695,"inputFormat":12696,"outputFormat":6275,"samples":12697,"starterPy":12700,"starterCpp":12701,"hint":12702,"referencePy":12703,"referenceCpp":12701},"多重背包（二进制拆分）","n 种物品，每种有数量 c_i、体积 w_i、价值 v_i，背包容量 V，求最大价值。","第一行 n V；接下来 n 行 w c v。",[12698],{"input":12699,"output":12351},"2 10\n2 3 4\n3 2 5","n,V=map(int,input().split()); items=[]\nfor _ in range(n):\n    w,c,v=map(int,input().split()); k=1\n    while c>=k: items.append((k*w,k*v)); c-=k; k*=2\n    if c>0: items.append((c*w,c*v))\ndp=[0]*(V+1)\nfor w,v in items:\n    for j in range(V,w-1,-1):\n        if dp[j-w]+v>dp[j]: dp[j]=dp[j-w]+v\nprint(max(dp))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,V; cin>>n>>V; vector\u003Cpair\u003Cint,int>> items; for(int i=0,w,c,v;i\u003Cn;i++){ cin>>w>>c>>v; int k=1; while(c>=k){ items.push_back({k*w,k*v}); c-=k; k*=2;} if(c>0) items.push_back({c*w,c*v}); } vector\u003Cint> dp(V+1,0); for(auto& it:items){ int w=it.first,v=it.second; for(int j=V;j>=w;j--) dp[j]=max(dp[j],dp[j-w]+v);} int ans=0; for(int x:dp) ans=max(ans,x); cout\u003C\u003Cans; }","二进制拆分 + 01 背包逆序。","n,V=map(int,input().split()); items=[]\nfor _ in range(n):\n    w,c,v=map(int,input().split()); k=1\n    while c>=k: items.append((k*w,k*v)); c-=k; k*=2\n    if c>0: items.append((c*w,c*v))\ndp=[0]*(V+1)\nfor w,v in items:\n    for j in range(V,w-1,-1):\n        if dp[j-w]+v>dp[j]: dp[j]=dp[j-w]+v\nprint(max(dp))","多重背包的二进制拆分把每种 c 件物品拆成系数 1,2,4,…,剩余 的若干组，每组视为独立 0\u002F1 背包物品，将 c 件压缩为约 log c 件；单调队列优化则按体积模 w 的同余类分别滑窗，做到 O(nV)。","22DP基础-A3-3","树形 DP",{"id":12705,"title":12706,"level":6350,"module":11881,"seq":703,"goal":12708,"keywords":12709,"explanation":12713,"examples":12725,"exercises":12730,"summary":12760,"next":12761,"nextTitle":12762},"掌握在树上做 DP：选\u002F不选类（最大独立集）、树上背包（子树合并），用 DFS 后序在子树状态上转移。",[12710,12711,12712,10135],"树形DP","最大独立集","树上背包",[12714,12719],{"title":12715,"content":12716,"codePy":12717,"codeCpp":12718},"1. 最大独立集","dp[u][0\u002F1] 表示不选\u002F选 u 时子树最大值；选 u 则子不能选，不选则子可选可不选。","def dfs(u,f):\n    dp0,dp1=0,val[u]\n    for v in g[u]:\n        if v==f: continue\n        a,b=dfs(v,u)\n        dp0+=max(a,b); dp1+=a\n    return dp0,dp1","\u002F\u002F 选\u002F不选子树合并",{"title":12720,"content":12721,"codePy":12722,"codeCpp":12723,"tip":12724},"2. 树上背包","子树内选 k 个节点的方案\u002F价值，对每个孩子做分组背包合并。","print(\"子树状态向上合并\")","\u002F\u002F DFS 后序","tip: 树形 DP 通常 DFS 后序，把子树结果合并到父节点。",[12726],{"title":12727,"prompt":12728,"analysis":12729,"codePy":6253,"codeCpp":5851,"run":759},"例 1：独立集","链 1-2-3，点权都 1，最大独立集？","选 1,3 = 2。",{"choice":12731,"coding":12748},[12732,12737,12744],{"id":38,"stem":12733,"options":12734,"answer":9,"explanation":12736},"树形 DP 通常？",[10133,12735,7747,7778],"DFS 后序合并子树","先算子树再合并父。",{"id":47,"stem":12738,"options":12739,"answer":9,"explanation":12743},"最大独立集选 u 时子节点？",[12740,12741,11203,12742],"必选","不可选","都选","相邻不可同选。",{"id":56,"stem":12745,"options":12746,"answer":9,"explanation":12747},"链 1-2-3 点权1 独立集？",[892,759,890,891],"选 1,3。",[12749],{"id":67,"title":12750,"prompt":12751,"inputFormat":12752,"outputFormat":6275,"samples":12753,"starterPy":12756,"starterCpp":12757,"hint":12758,"referencePy":12759,"referenceCpp":12757},"没有上司的舞会","n 个点的树，每个点有权值，选若干点使无父子同时被选且权值和最大。","第一行 n；第二行 n 个整数权值；接下来 n-1 行 u v。",[12754],{"input":12755,"output":759},"3\n1 1 1\n1 2\n2 3","import sys\nsys.setrecursionlimit(100000)\nn=int(input()); val=list(map(int,input().split())); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndef dfs(u,f):\n    dp0,dp1=0,val[u-1]\n    for v in g[u]:\n        if v==f: continue\n        a,b=dfs(v,u); dp0+=max(a,b); dp1+=a\n    return dp0,dp1\nprint(max(dfs(1,0)))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nvector\u003Cint> g[100005]; int val[100005];\npair\u003Cint,int> dfs(int u,int f){ int dp0=0,dp1=val[u]; for(int v:g[u]){ if(v==f) continue; auto [a,b]=dfs(v,u); dp0+=max(a,b); dp1+=a; } return {dp0,dp1}; }\nint main(){ int n; cin>>n; for(int i=1;i\u003C=n;i++) cin>>val[i]; for(int i=0,u,v;i\u003Cn-1;i++){ cin>>u>>v; g[u].push_back(v); g[v].push_back(u);} auto [a,b]=dfs(1,0); cout\u003C\u003Cmax(a,b); }","DFS 后序，选\u002F不选合并。","import sys\nsys.setrecursionlimit(100000)\nn=int(input()); val=list(map(int,input().split())); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\ndef dfs(u,f):\n    dp0,dp1=0,val[u-1]\n    for v in g[u]:\n        if v==f: continue\n        a,b=dfs(v,u); dp0+=max(a,b); dp1+=a\n    return dp0,dp1\nprint(max(dfs(1,0)))","树形 DP 在树上后序遍历：以 dp[u][0\u002F1] 表示不\u002F选 u 时子树的最优值，选 u 则其子不可选、不选则子可选可不选，自底向上合并；树上背包则把每个孩子的状态作为一组做分组背包合并。","22DP基础-A3-4","区间 DP",{"id":12761,"title":12762,"level":6350,"module":11881,"seq":770,"goal":12764,"keywords":12765,"explanation":12770,"examples":12782,"exercises":12787,"summary":12815,"next":12816,"nextTitle":12817},"掌握区间 DP：状态 dp[l][r] 表示子区间 [l,r] 的最优值，长度从小到大枚举，由短区间合并成长区间。",[12766,12767,12768,12769],"区间DP","石子合并","矩阵连乘","枚举长度",[12771,12776],{"title":12772,"content":12773,"codePy":12774,"codeCpp":12775},"1. 枚举顺序","先枚举区间长度 len，再枚举左端点 l，右端点 r=l+len-1，由更短区间转移。","def solve():\n    for L in range(2,n+1):\n        for l in range(1,n-L+2):\n            r=l+L-1\n            for k in range(l,r):\n                dp[l][r]=min(dp[l][r], dp[l][k]+dp[k+1][r]+cost)","\u002F\u002F 短区间推长区间",{"title":12777,"content":12778,"codePy":12779,"codeCpp":12780,"tip":12781},"2. 典型题","石子合并（相邻合并代价为区间和）、矩阵连乘（乘法次数）都是区间 DP。","print(\"按长度递进\")","\u002F\u002F O(n³)","tip: 区间 DP 关键在正确的枚举顺序与转移。",[12783],{"title":12784,"prompt":12785,"analysis":12786,"codePy":7942,"codeCpp":7943,"run":2707},"例 1：石子合并","三堆 1,2,3，最少合并代价？","先 1+2=3，再 3+3=6，总 3+6=9。",{"choice":12788,"coding":12806},[12789,12796,12800],{"id":38,"stem":12790,"options":12791,"answer":9,"explanation":12795},"区间 DP 枚举顺序？",[1568,12792,12793,12794],"按长度递进","按右端","按左端","短推长。",{"id":47,"stem":12797,"options":12798,"answer":63,"explanation":12799},"区间 DP 复杂度？",[2338,2339,2345,2823],"三层循环。",{"id":56,"stem":12801,"options":12802,"answer":9,"explanation":12805},"石子合并属于？",[12098,12766,12803,12804],"背包","树DP","相邻合并。",[12807],{"id":67,"title":12767,"prompt":12808,"inputFormat":1801,"outputFormat":6275,"samples":12809,"starterPy":12811,"starterCpp":12812,"hint":12813,"referencePy":12814,"referenceCpp":12812},"n 堆石子，每次合并相邻两堆，代价为两堆石子数之和；求合并成一堆的最小总代价。",[12810],{"input":9591,"output":2707},"n=int(input()); a=list(map(int,input().split()))\npref=[0]*(n+1)\nfor i in range(n): pref[i+1]=pref[i]+a[i]\nINF=float(\"inf\"); dp=[[0]*n for _ in range(n)]\nfor L in range(2,n+1):\n    for l in range(n-L+1):\n        r=l+L-1; dp[l][r]=INF\n        for k in range(l,r):\n            c=dp[l][k]+dp[k+1][r]+pref[r+1]-pref[l]\n            if c\u003Cdp[l][r]: dp[l][r]=c\nprint(dp[0][n-1])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst int INF=1e9; int main(){ int n; cin>>n; vector\u003Cint> a(n),pref(n+1,0); for(int i=0;i\u003Cn;i++){ cin>>a[i]; pref[i+1]=pref[i]+a[i]; } vector\u003Cvector\u003Cint>> dp(n,vector\u003Cint>(n,0)); for(int L=2;L\u003C=n;L++) for(int l=0;l\u003C=n-L;l++){ int r=l+L-1; dp[l][r]=INF; for(int k=l;k\u003Cr;k++){ int c=dp[l][k]+dp[k+1][r]+pref[r+1]-pref[l]; if(c\u003Cdp[l][r]) dp[l][r]=c; } } cout\u003C\u003Cdp[0][n-1]; }","区间和用前缀和；按长度递进。","n=int(input()); a=list(map(int,input().split()))\npref=[0]*(n+1)\nfor i in range(n): pref[i+1]=pref[i]+a[i]\nINF=float(\"inf\"); dp=[[0]*n for _ in range(n)]\nfor L in range(2,n+1):\n    for l in range(n-L+1):\n        r=l+L-1; dp[l][r]=INF\n        for k in range(l,r):\n            c=dp[l][k]+dp[k+1][r]+pref[r+1]-pref[l]\n            if c\u003Cdp[l][r]: dp[l][r]=c\nprint(dp[0][n-1])","区间 DP 用 dp[l][r] 表示子区间 [l,r] 的最优值，按区间长度从小到大枚举，由更短的左右子区间合并转移（常配合前缀和求区间代价），复杂度 O(n³)，典型应用为石子合并、矩阵连乘。","22DP基础-A3-5","状态压缩 DP：棋盘覆盖",{"id":12816,"title":12817,"level":6350,"module":11881,"seq":838,"goal":12819,"keywords":12820,"explanation":12825,"examples":12837,"exercises":12842,"summary":12874,"next":12875,"nextTitle":12876},"掌握状压 DP：当状态维度小（如 n≤12）时，用二进制整数表示每一行\u002F列的状态，逐行转移并保证行间约束合法。",[12821,12822,12823,12824],"状态压缩","二进制状态","棋盘","蒙德里安",[12826,12831],{"title":12827,"content":12828,"codePy":12829,"codeCpp":12830},"1. 状态表示","用整数的二进制位表示一行中每个位置是否被占据\u002F选中；合法状态与上一行状态需满足约束（如互补、不冲突）。","def valid(cur, pre):\n    # 例如两行不能同列冲突\n    return (cur & pre) == 0","\u002F\u002F 位运算判冲突",{"title":12832,"content":12833,"codePy":12834,"codeCpp":12835,"tip":12836},"2. 转移","枚举当前行所有合法状态，与上一行合法状态组合，累加方案\u002F优化价值。","print(\"状态用位压缩\")","\u002F\u002F n≤12 可行","tip: 状压 DP 用位运算加速合法判断，关键是构造合法状态集。",[12838],{"title":12839,"prompt":12840,"analysis":12841,"codePy":8387,"codeCpp":8388,"run":8389},"例 1：合法状态","n=3，状态 101 与 010 是否同行不冲突？","位与为 0，不冲突。",{"choice":12843,"coding":12862},[12844,12849,12856],{"id":38,"stem":12845,"options":12846,"answer":9,"explanation":12848},"状压 DP 用整数表示？",[12847,12822,11377,7963],"行号","位表示每格。",{"id":47,"stem":12850,"options":12851,"answer":9,"explanation":12855},"状压 DP 适用 n？",[3952,12852,12853,12854],"≤12 左右","≥20","很大","2^n 状态。",{"id":56,"stem":12857,"options":12858,"answer":9,"explanation":12861},"行间约束常用？",[12859,1500,2570,12860],"加法","除法","位与\u002F或判合法。",[12863],{"id":67,"title":12864,"prompt":12865,"inputFormat":12866,"outputFormat":12867,"samples":12868,"starterPy":12870,"starterCpp":12871,"hint":12872,"referencePy":12873,"referenceCpp":12871},"互不攻击的车（状压）","n×n 棋盘放车使互相不攻击（不同行不同列），求放 k 个的方案数？这里简化为：统计一行内选若干列互不冲突的合法状态数（演示状压枚举）。n≤12。","第一行 n。","一行整数：所有行选择中每一列至多被选一次的全集状态数（即 2^n）。",[12869],{"input":890,"output":148},"n=int(input()); print(1\u003C\u003Cn)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; cout\u003C\u003C(1\u003C\u003Cn); }","一行合法状态即任意子集，共 2^n。","n=int(input()); print(1\u003C\u003Cn)","状态压缩 DP 在状态维度很小（如 n≤12）时，用整数的二进制位表示一行\u002F列每个位置的状态，逐行\u002F列枚举合法状态并用位运算判断行间约束（如 (cur&pre)==0），从而把组合约束转为位运算转移。","22DP基础-A3-6","数位 DP",{"id":12875,"title":12876,"level":6350,"module":11881,"seq":936,"goal":12878,"keywords":12879,"explanation":12883,"examples":12895,"exercises":12900,"summary":12931,"next":12932,"nextTitle":12933},"掌握数位 DP：统计满足数位约束（如不含 4、数字和、特定子串）的整数个数，按位从高到低记忆化搜索，用 up 限制上界。",[12880,11935,12881,12882],"数位DP","按位搜索","限制上界",[12884,12889],{"title":12885,"content":12886,"codePy":12887,"codeCpp":12888},"1. 记忆化搜索","dfs(pos, tight, lead, state)：在 pos 位选择数字 d，tight 表示是否贴着上界，lead 表示是否前导零；相同 (pos,tight,state) 复用。","from functools import lru_cache\ndef count(n):\n    s=str(n)\n    @lru_cache(None)\n    def dfs(i, tight, has):\n        if i==len(s): return 1 if has else 0\n        lim=int(s[i]) if tight else 9; res=0\n        for d in range(lim+1):\n            res+=dfs(i+1, tight and d==lim, has or d==4)\n        return res\n    return dfs(0,True,False)","\u002F\u002F 记忆化按位枚举",{"title":12890,"content":12891,"codePy":12892,"codeCpp":12893,"tip":12894},"2. 要点","tight 控制不能超过上界；state 记录已收集的约束（如是否出现过某数字）；前导零常需单独处理。","print(\"数位拆解+记忆化\")","\u002F\u002F 适合区间计数","tip: 数位 DP 把“数”按十进制位拆开，状态只需记录与之前位相关的约束。",[12896],{"title":12897,"prompt":12898,"analysis":12899,"codePy":7942,"codeCpp":7943,"run":2707},"例 1：含4计数","1..10 中不含数字4的有几个？","除 4 外都含，共 9（1..10 去4）=9。",{"choice":12901,"coding":12920},[12902,12907,12915],{"id":38,"stem":12903,"options":12904,"answer":9,"explanation":12906},"数位 DP 用？",[6039,12905,2509,7747],"记忆化按位搜索","dfs+记忆化。",{"id":47,"stem":12908,"options":12909,"answer":9,"explanation":12914},"tight 含义？",[12910,12911,12912,12913],"是否前导0","是否贴着上界","是否到尾","是否选满","限制不超上界。",{"id":56,"stem":12916,"options":12917,"answer":9,"explanation":12919},"为何记忆化？",[12679,12918,4356,4166],"复用相同子状态","同状态同结果。",[12921],{"id":67,"title":12922,"prompt":12923,"inputFormat":12924,"outputFormat":6275,"samples":12925,"starterPy":12927,"starterCpp":12928,"hint":12929,"referencePy":12930,"referenceCpp":12928},"不含数字 4 的个数","统计 1..n 中十进制不含数字 4 的整数个数。","一行整数 n（≤10^9）。",[12926],{"input":883,"output":2707},"from functools import lru_cache\nn=int(input()); s=str(n)\n@lru_cache(None)\ndef dfs(i,tight):\n    if i==len(s): return 1\n    lim=int(s[i]) if tight else 9; res=0\n    for d in range(lim+1):\n        if d==4: continue\n        res+=dfs(i+1, tight and d==lim)\n    return res\nprint(dfs(0,True)-1)  # 减去 0\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nstring s; vector\u003Cvector\u003Clong long>> dp; long long dfs(int i,bool tight){ if(i==(int)s.size()) return 1; if(dp[i][tight]!=-1) return dp[i][tight]; int lim=tight?(s[i]-'0'):9; long long res=0; for(int d=0;d\u003C=lim;d++){ if(d==4) continue; res+=dfs(i+1, tight&&d==lim); } return dp[i][tight]=res; }\nint main(){ long long n; cin>>n; s=to_string(n); dp.assign(s.size(),vector\u003Clong long>(2,-1)); cout\u003C\u003Cdfs(0,true)-1; }","按位枚举，跳过 4，记忆化。","from functools import lru_cache\nn=int(input()); s=str(n)\n@lru_cache(None)\ndef dfs(i,tight):\n    if i==len(s): return 1\n    lim=int(s[i]) if tight else 9; res=0\n    for d in range(lim+1):\n        if d==4: continue\n        res+=dfs(i+1, tight and d==lim)\n    return res\nprint(dfs(0,True)-1)","数位 DP 把整数按十进制位拆开，用记忆化搜索 dfs(pos, tight, state)：tight 表示当前是否贴着上界（限制不超 n），state 记录已满足的约束（如是否出现某数字、数字和等）；相同子状态直接复用，适合区间内的数位统计。","22DP基础-A3-7","概率与期望 DP",{"id":12932,"title":12933,"level":6350,"module":11881,"seq":998,"goal":12935,"keywords":12936,"explanation":12940,"examples":12952,"exercises":12957,"summary":12986,"next":12987,"nextTitle":12988},"掌握概率\u002F期望 DP：以状态为随机变量，用 E = Σ p·v 或全期望公式递推期望；注意循环转移时的移项求解。",[12937,3027,12938,12939],"期望DP","全期望公式","递推",[12941,12946],{"title":12942,"content":12943,"codePy":12944,"codeCpp":12945},"1. 期望递推","对终止目标设 E=0，往前推：E = 1 + Σ p_i·E_i；若有回到自身的转移，移项解出 E。","def solve():\n    # 例：掷骰到 6 的期望次数\n    # E = 1 + (5\u002F6)E + (1\u002F6)*0  => E=6\n    return 6","\u002F\u002F 全期望公式",{"title":12947,"content":12948,"codePy":12949,"codeCpp":12950,"tip":12951},"2. 与 DP 结合","网格随机游走、收集类（集齐卡片）期望常用期望 DP；可正向算概率再求期望，或反向推期望。","print(\"遇到自身转移要移项\")","\u002F\u002F 概率和=1","tip: 期望具有线性性，可对每个事件单独算期望再相加。",[12953],{"title":12954,"prompt":12955,"analysis":12956,"codePy":4786,"codeCpp":4432,"run":624},"例 1：掷骰","公平六面骰，掷出 6 停止，期望次数？","E=1+5\u002F6 E => E=6。",{"choice":12958,"coding":12976},[12959,12965,12969],{"id":38,"stem":12960,"options":12961,"answer":9,"explanation":12964},"期望递推遇到自身转移？",[3560,12962,1568,12963],"移项求解","归零","E 移到一边。",{"id":47,"stem":12966,"options":12967,"answer":9,"explanation":12968},"掷骰到6期望？",[890,624,892,884],"E=1+5\u002F6 E。",{"id":56,"stem":12970,"options":12971,"answer":9,"explanation":12975},"期望线性性指？",[12972,12973,1568,12974],"不可加","可逐项相加","求逆","E[Σ]=ΣE。",[12977],{"id":67,"title":12978,"prompt":12979,"inputFormat":1456,"outputFormat":6275,"samples":12980,"starterPy":12982,"starterCpp":12983,"hint":12984,"referencePy":12985,"referenceCpp":12983},"收集卡片期望","n 种卡片，每次随机获得一种（等概率），求集齐 n 种所需次数的期望（输出保留整数向下舍入即可，n≤20）。",[12981],{"input":890,"output":7594},"n=int(input()); ans=0\nfor i in range(n): ans+=n\u002F(i+1)\nprint(int(ans))  # 演示用；精确应为分数求和\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; long long ans=0; for(int i=0;i\u003Cn;i++) ans+=n\u002F(i+1); cout\u003C\u003Cans; }","集齐k种后集齐第k+1种期望 n\u002F(n-k)。","n=int(input()); ans=0\nfor i in range(n): ans+=n\u002F(i+1)\nprint(int(ans))","概率\u002F期望 DP 以状态为随机变量，用全期望公式 E=1+Σp_i·E_i 反向递推，遇到回到自身的转移需移项求解（如掷骰 E=1+5\u002F6·E 得 E=6）；期望具有线性性，可对各事件期望单独求和。","22DP基础-A3-8","状态压缩进阶：TSP",{"id":12987,"title":12988,"level":6350,"module":11881,"seq":1066,"goal":12990,"keywords":12991,"explanation":12994,"examples":13005,"exercises":13013,"summary":13047,"next":13048,"nextTitle":13049},"掌握用状压 DP 解旅行商 TSP：dp[mask][i] 表示已访问集合 mask 且当前在城市 i 的最小代价，枚举下一未访问城市转移。",[12992,12821,12993,6040],"TSP","旅行商",[12995,13000],{"title":12996,"content":12997,"codePy":12998,"codeCpp":12999},"1. 状态定义","mask 的二进制位表示哪些城市已访问；dp[mask][i]=最小代价。初态 dp[1\u003C\u003C0][0]=0。","def tsp():\n    dp=[[inf]*n for _ in range(1\u003C\u003Cn)]\n    dp[1][0]=0\n    for mask in range(1\u003C\u003Cn):\n        for i in range(n):\n            if not (mask>>i)&1: continue\n            for j in range(n):\n                if (mask>>j)&1: continue\n                dp[mask|(1\u003C\u003Cj)][j]=min(dp[mask|(1\u003C\u003Cj)][j], dp[mask][i]+w[i][j])","\u002F\u002F 已访问集合+当前点",{"title":6191,"content":13001,"codePy":13002,"codeCpp":13003,"tip":13004},"O(n²·2^n)，适合 n≤20 的完全图 TSP。","print(\"2^n 状态\")","\u002F\u002F 枚举下一城市","tip: TSP 状压是状压 DP 的经典范例，注意只从合法 mask 转移。",[13006],{"title":13007,"prompt":13008,"analysis":13009,"codePy":13010,"codeCpp":13011,"run":13012},"例 1：TSP 规模","n=20 城市，状态数约？","2^20≈10^6。","print(2**20)","\u002F\u002F 1048576","1048576",{"choice":13014,"coding":13035},[13015,13022,13028],{"id":38,"stem":13016,"options":13017,"answer":9,"explanation":13021},"TSP 状压状态是？",[13018,13019,13020,11377],"单点","已访问集合 mask","路径","位表示已访问。",{"id":47,"stem":13023,"options":13024,"answer":9,"explanation":13027},"TSP 复杂度？",[13025,13026,2339,2823],"O(n!)","O(n²2^n)","状态×转移。",{"id":56,"stem":13029,"options":13030,"answer":607,"explanation":13034},"TSP 适合 n？",[13031,13032,3952,13033],"≤20","≤100","≥50","2^n 限制。",[13036],{"id":67,"title":13037,"prompt":13038,"inputFormat":13039,"outputFormat":6275,"samples":13040,"starterPy":13043,"starterCpp":13044,"hint":13045,"referencePy":13046,"referenceCpp":13044},"旅行商（状压）","n 个城市（n≤16），给定两两距离矩阵，求从城市 0 出发经过每个城市恰好一次再回到 0 的最短路程。","第一行 n；接下来 n 行 n 个整数距离矩阵。",[13041],{"input":13042,"output":693},"4\n0 10 15 20\n10 0 35 25\n15 35 0 30\n20 25 30 0","import sys\nn=int(input()); w=[list(map(int,input().split())) for _ in range(n)]\nINF=float(\"inf\"); dp=[[INF]*n for _ in range(1\u003C\u003Cn)]; dp[1][0]=0\nfor mask in range(1\u003C\u003Cn):\n    for i in range(n):\n        if not (mask>>i)&1: continue\n        for j in range(n):\n            if (mask>>j)&1: continue\n            dp[mask|(1\u003C\u003Cj)][j]=min(dp[mask|(1\u003C\u003Cj)][j], dp[mask][i]+w[i][j])\nans=min(dp[(1\u003C\u003Cn)-1][i]+w[i][0] for i in range(n))\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cclimits>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cvector\u003Cint>> w(n,vector\u003Cint>(n)); for(int i=0;i\u003Cn;i++) for(int j=0;j\u003Cn;j++) cin>>w[i][j]; const int INF=1e9; vector\u003Cvector\u003Cint>> dp(1\u003C\u003Cn,vector\u003Cint>(n,INF)); dp[1][0]=0; for(int mask=0;mask\u003C(1\u003C\u003Cn);mask++) for(int i=0;i\u003Cn;i++){ if(!(mask>>i&1)) continue; for(int j=0;j\u003Cn;j++){ if(mask>>j&1) continue; int nm=mask|(1\u003C\u003Cj); if(dp[mask][i]+w[i][j]\u003Cdp[nm][j]) dp[nm][j]=dp[mask][i]+w[i][j]; } } int ans=INF; for(int i=0;i\u003Cn;i++) ans=min(ans, dp[(1\u003C\u003Cn)-1][i]+w[i][0]); cout\u003C\u003Cans; }","状压 dp[mask][i] + 回起点。","import sys\nn=int(input()); w=[list(map(int,input().split())) for _ in range(n)]\nINF=float(\"inf\"); dp=[[INF]*n for _ in range(1\u003C\u003Cn)]; dp[1][0]=0\nfor mask in range(1\u003C\u003Cn):\n    for i in range(n):\n        if not (mask>>i)&1: continue\n        for j in range(n):\n            if (mask>>j)&1: continue\n            dp[mask|(1\u003C\u003Cj)][j]=min(dp[mask|(1\u003C\u003Cj)][j], dp[mask][i]+w[i][j])\nans=min(dp[(1\u003C\u003Cn)-1][i]+w[i][0] for i in range(n))\nprint(ans)","TSP 用状压 DP：dp[mask][i] 表示已访问城市集合为 mask、当前在城市 i 的最小路程；初始 dp[1\u003C\u003C0][0]=0，枚举下一未访问城市 j 转移 dp[mask|1\u003C\u003Cj][j]=min(...)，最后加上回到起点 0 的边，复杂度 O(n²·2^n)，适合 n≤20。","22DP基础-A3-9","单调队列优化 DP",{"id":13048,"title":13049,"level":6350,"module":11881,"seq":1131,"goal":13051,"keywords":13052,"explanation":13055,"examples":13066,"exercises":13071,"summary":13101,"next":12478,"nextTitle":12479},"掌握用单调队列优化形如 dp[i]=min_{j}(dp[j]+f(i,j)) 的 DP：当 f 可拆成只关于 i 与只关于 j 的部分时，用滑动窗口维护最优 j。",[9319,13053,6465,13054],"DP优化","决策单调性",[13056,13061],{"title":13057,"content":13058,"codePy":13059,"codeCpp":13060},"1. 优化思路","例如 dp[i]=min(dp[j]+(i-j)²)，展开后含 dp[j]+j²-2ij，对固定 i 用单调队列维护最优 (dp[j]+j²) 的下凸决策。","def opt():\n    # 示意：滑窗取最小\n    from collections import deque\n    q=deque()\n    for i in range(n):\n        while q and q[0] \u003C i-k: q.popleft()\n        # dp[i] = dp[q[0]] + (i-q[0])**2\n        while q and bad(q[-1], i): q.pop()\n        q.append(i)","\u002F\u002F 队列维护候选 j",{"title":4421,"content":13062,"codePy":13063,"codeCpp":13064,"tip":13065},"多重背包（同余类）、最大子段和窗口、某些斜率优化都用单调队列\u002F单调栈。","print(\"窗口内取最优\")","\u002F\u002F 把 O(n²) 降到 O(n)","tip: 优化关键是把转移拆成“关于 i 的”与“关于 j 的”乘积或分离项。",[13067],{"title":13068,"prompt":13069,"analysis":13070,"codePy":2590,"codeCpp":2591,"run":2338},"例 1：滑动窗口","长度 k 滑窗取最大，复杂度？","O(n)（单调队列）。",{"choice":13072,"coding":13089},[13073,13080,13085],{"id":38,"stem":13074,"options":13075,"answer":9,"explanation":13079},"单调队列优化把 DP 从？",[13076,13077,13078,1307],"O(n)到O(1)","O(n²)到O(n)","O(n³)到O(n²)","窗口维护最优 j。",{"id":47,"stem":13081,"options":13082,"answer":9,"explanation":13084},"单调队列维护？",[4202,13083,2622,6269],"候选决策最优","最优转移点。",{"id":56,"stem":13086,"options":13087,"answer":9,"explanation":13088},"最大子段和滑窗用？",[6210,9319,6565,11309],"维护窗口极值。",[13090],{"id":67,"title":9081,"prompt":13091,"inputFormat":9083,"outputFormat":13092,"samples":13093,"starterPy":13097,"starterCpp":13098,"hint":13099,"referencePy":13100,"referenceCpp":13098},"给定 n 个整数与窗口 k，输出每个长度为 k 的滑动窗口的最大值（这是单调队列优化 DP 的基础组件）。","一行整数（窗口数 n-k+1 个）。",[13094],{"input":13095,"output":13096},"5 3\n1 3 2 5 4","3 5 5","from collections import deque\nn,k=map(int,input().split()); a=list(map(int,input().split())); q=deque(); res=[]\nfor i in range(n):\n    while q and a[q[-1]]\u003C=a[i]: q.pop()\n    q.append(i)\n    if q[0]==i-k: q.popleft()\n    if i>=k-1: res.append(a[q[0]])\nprint(*res)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cdeque>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; deque\u003Cint> q; vector\u003Cint> res; for(int i=0;i\u003Cn;i++){ while(!q.empty()&&a[q.back()]\u003C=a[i]) q.pop_back(); q.push_back(i); if(q.front()==i-k) q.pop_front(); if(i>=k-1) res.push_back(a[q.front()]); } for(int x:res) cout\u003C\u003Cx\u003C\u003C\" \"; }","单调递减队列维护窗口最大。","from collections import deque\nn,k=map(int,input().split()); a=list(map(int,input().split())); q=deque(); res=[]\nfor i in range(n):\n    while q and a[q[-1]]\u003C=a[i]: q.pop()\n    q.append(i)\n    if q[0]==i-k: q.popleft()\n    if i>=k-1: res.append(a[q[0]])\nprint(*res)","单调队列优化 DP 适用于 dp[i]=min_j(dp[j]+f(i,j)) 且 f 可分解为只关于 i 与只关于 j 的部分：用单调队列在滑动窗口内维护最优决策 j，把 O(n²) 降至 O(n)；滑动窗口最大值正是其基础组件。",{"id":1600,"title":1601,"level":1197,"module":13103,"seq":9,"goal":13104,"keywords":13105,"explanation":13109,"examples":13121,"exercises":13129,"summary":13164,"next":13165,"nextTitle":13166},"位运算 \u002F 状态压缩","理解十进制与二进制的互转、位权概念，以及整数以补码存储。",[1601,13106,13107,13108],"位权","进制转换","补码",[13110,13115],{"title":13111,"content":13112,"codePy":13113,"codeCpp":13114},"1. 位权与互转","二进制第 k 位（从右数，0 起）的\"位权\"是 2^k。十进制转二进制：反复除 2 取余，倒序排列。二进制转十进制：按权展开求和。","def to_bin(n):\n    if n == 0: return \"0\"\n    s = \"\"\n    while n: s = str(n % 2) + s; n \u002F\u002F= 2\n    return s","\u002F\u002F 13 -> 1101：位权 8+4+1\nint x = 13; \u002F\u002F 二进制 1101",{"title":13116,"content":13117,"codePy":13118,"codeCpp":13119,"tip":13120},"2. 计算机用补码存整数","C++ 中负数用\"补码\"存储：正数补码=原码；负数=其绝对值取反加 1。补码统一了 0 的表示，也让加减法统一。8 位补码范围是 -128~127。","# Python 用无限位；看 8 位补码可用 &(0xFF)\nprint(bin(-10 & 0xFF))","int x = -10;\n\u002F\u002F 8 位补码：11110110\ncout \u003C\u003C bitset\u003C8>(x);","位运算几乎都建立在\"补码\"之上，理解它才能看懂负数位运算。",[13122],{"title":13123,"prompt":13124,"analysis":13125,"codePy":13126,"codeCpp":13127,"run":13128},"例 1：十进制转二进制","输入非负整数 n（≤1000），输出其二进制字符串。","除 2 取余，倒序。","n = int(input())\nif n == 0: print(0)\nelse:\n    s = \"\"\n    while n: s = str(n % 2) + s; n \u002F\u002F= 2\n    print(s)","int n; cin >> n;\nbitset\u003C16> b(n);\ncout \u003C\u003C b;","输入 13 -> 1101",{"choice":13130,"coding":13151},[13131,13135,13143],{"id":38,"stem":13132,"options":13133,"answer":9,"explanation":13134},"二进制 1101 对应的十进制是？",[7594,12351,10634,2707],"8+4+1=13。",{"id":47,"stem":13136,"options":13137,"answer":9,"explanation":13142},"十进制 13 转二进制是？",[13138,13139,13140,13141],"1011","1101","1110","1001","13=8+4+1 -> 1101。",{"id":56,"stem":13144,"options":13145,"answer":9,"explanation":13150},"8 位有符号补码的表示范围是？",[13146,13147,13148,13149],"0~255","-128~127","-127~127","-256~255","补码多表示一个 -128。",[13152],{"id":67,"title":13153,"prompt":13154,"inputFormat":1456,"outputFormat":13155,"samples":13156,"starterPy":13159,"starterCpp":13160,"hint":13161,"referencePy":13162,"referenceCpp":13163},"转二进制","输入非负整数 n（0≤n≤100000），输出其二进制表示（不含前导 0，n=0 输出 \"0\"）。","一行二进制字符串。",[13157,13158],{"input":12351,"output":13139},{"input":891,"output":891},"n = int(input())\n# 转二进制\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin >> n;\n    \u002F\u002F 输出二进制\n    return 0;\n}","除 2 取余倒序；n=0 特判。","n=int(input())\nif n==0: print(0)\nelse:\n    s=\"\"\n    while n: s=str(n%2)+s; n\u002F\u002F=2\n    print(s)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin>>n;\n    if (n==0){ cout\u003C\u003C\"0\"; return 0; }\n    int bits[32], k=0;\n    while(n){ bits[k++]=n%2; n\u002F=2; }\n    for(int i=k-1;i>=0;i--) cout\u003C\u003Cbits[i];\n    return 0;\n}","二进制按位权 2^k 展开；十进制↔二进制用除 2 取余与按权求和；计算机用补码存储整数（8 位范围 -128~127）。","23-A1-2","位与",{"id":13165,"title":13166,"level":1197,"module":13103,"seq":63,"goal":13168,"keywords":13169,"explanation":13172,"examples":13184,"exercises":13192,"summary":13228,"next":13229,"nextTitle":13230},"掌握按位与 &——对应位都为 1 才为 1，常用于判奇偶、取某些位、清位。",[13166,1038,13170,13171],"掩码","奇偶",[13173,13178],{"title":13174,"content":13175,"codePy":13176,"codeCpp":13177},"1. & 的运算规则","对两个整数的每一位做\"与\"：只有两位都是 1 结果才是 1，否则 0。例如 5(0101) & 3(0011) = 1(0001)。","print(5 & 3)   # 1","cout \u003C\u003C (5 & 3); \u002F\u002F 1",{"title":13179,"content":13180,"codePy":13181,"codeCpp":13182,"tip":13183},"2. 常用技巧：判奇偶、取低 k 位","n & 1 等于 1 表示奇数、0 表示偶数（看最低位）。用掩码取低 k 位：n & ((1\u003C\u003Ck)-1)。位与还能把某些位\"清零\"。","print(\"奇\" if n & 1 else \"偶\")\nlow4 = n & 15     # 取低 4 位","bool odd = n & 1;\nint low4 = n & 15;","判奇偶用 n&1 比 n%2 更快，且对负数也正确（补码最低位）。",[13185],{"title":13186,"prompt":13187,"analysis":13188,"codePy":13189,"codeCpp":13190,"run":13191},"例 1：用位与判奇偶","输入整数 n，用 n & 1 判断奇偶并输出 \"ODD\"\u002F\"EVEN\"。","最低位为 1 则奇。","n = int(input())\nprint(\"ODD\" if n & 1 else \"EVEN\")","int n; cin >> n;\ncout \u003C\u003C ((n & 1) ? \"ODD\" : \"EVEN\");","输入 7 -> ODD",{"choice":13193,"coding":13214},[13194,13198,13206],{"id":38,"stem":13195,"options":13196,"answer":9,"explanation":13197},"5 & 3 的结果是？",[885,892,759,891],"0101 & 0011 = 0001 = 1。",{"id":47,"stem":13199,"options":13200,"answer":607,"explanation":13205},"判断 n 为奇数，可用？",[13201,13202,13203,13204],"n & 1","n % 1","n | 1","n ^ 1","最低位为 1 即奇。",{"id":56,"stem":13207,"options":13208,"answer":607,"explanation":13213},"取整数 n 的低 4 位，应写？",[13209,13210,13211,13212],"n & 15","n & 4","n | 15","n >> 4","掩码 15=(1111)_2 保留低 4 位。",[13215],{"id":67,"title":919,"prompt":13216,"inputFormat":1456,"outputFormat":13217,"samples":13218,"starterPy":13223,"starterCpp":13224,"hint":13225,"referencePy":13226,"referenceCpp":13227},"输入整数 n（|n|≤1e9），用位运算 n & 1 判断奇偶，输出 \"ODD\" 或 \"EVEN\"。","一行 \"ODD\" 或 \"EVEN\"。",[13219,13221],{"input":885,"output":13220},"ODD",{"input":148,"output":13222},"EVEN","n = int(input())\n# 用 n & 1 判断\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin >> n;\n    \u002F\u002F n & 1 判奇偶\n    return 0;\n}","n & 1 为 1 是奇数。","n=int(input())\nprint(\"ODD\" if n & 1 else \"EVEN\")","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin>>n;\n    cout \u003C\u003C ((n & 1) ? \"ODD\" : \"EVEN\");\n    return 0;\n}","按位与 & 两位都为 1 才为 1；常用 n&1 判奇偶、用掩码 n&((1\u003C\u003Ck)-1) 取低 k 位。","23-A1-3","位或",{"id":13229,"title":13230,"level":1197,"module":13103,"seq":703,"goal":13232,"keywords":13233,"explanation":13237,"examples":13249,"exercises":13257,"summary":13288,"next":13289,"nextTitle":13290},"掌握按位或 |——对应位有 1 则为 1，常用于置位（把某位置 1）、合并标志位。",[13230,13234,13235,13236],"|","置位","标志",[13238,13243],{"title":13239,"content":13240,"codePy":13241,"codeCpp":13242},"1. | 的运算规则","对每一位做\"或\"：只要两位中有一个是 1，结果就是 1。例如 5(0101) | 2(0010) = 7(0111)。","print(5 | 2)   # 7","cout \u003C\u003C (5 | 2); \u002F\u002F 7",{"title":13244,"content":13245,"codePy":13246,"codeCpp":13247,"tip":13248},"2. 置位与合并标志","把第 k 位置 1：x | (1\u003C\u003Ck)。这不改变其它位。多个\"开关\u002F权限\"可各自占一位，用 | 合并、用 & 检测，非常高效。","x = x | (1 \u003C\u003C 3)   # 第 3 位置 1\nflags = read | write","x |= (1 \u003C\u003C 3);     \u002F\u002F 置第 3 位\nint flags = read | write;","清位用 x & ~(1\u003C\u003Ck)；置位用 x | (1\u003C\u003Ck)；检测用 x & (1\u003C\u003Ck)。",[13250],{"title":13251,"prompt":13252,"analysis":13253,"codePy":13254,"codeCpp":13255,"run":13256},"例 1：置第 k 位","输入 x 和 k（0≤k≤30），把 x 的第 k 位置为 1 并输出。","x | (1\u003C\u003Ck)。","x, k = map(int, input().split())\nprint(x | (1 \u003C\u003C k))","int x, k; cin >> x >> k;\ncout \u003C\u003C (x | (1 \u003C\u003C k));","输入 5 3 -> 13（0101 | 1000 = 1101）",{"choice":13258,"coding":13276},[13259,13263,13271],{"id":38,"stem":13260,"options":13261,"answer":9,"explanation":13262},"5 | 2 的结果是？",[290,885,624,890],"0101 | 0010 = 0111 = 7。",{"id":47,"stem":13264,"options":13265,"answer":9,"explanation":13270},"把第 k 位置 1，应写？",[13266,13267,13268,13269],"x & (1\u003C\u003Ck)","x | (1\u003C\u003Ck)","x ^ (1\u003C\u003Ck)","x \u003C\u003C k","位或置位不影响其它位。",{"id":56,"stem":13272,"options":13273,"answer":9,"explanation":13275},"检测第 k 位是否为 1，应写？",[13267,13266,13268,13274],"x >> k","与掩码做 &，非 0 表示该位为 1。",[13277],{"id":67,"title":13235,"prompt":13278,"inputFormat":13279,"outputFormat":6275,"samples":13280,"starterPy":13283,"starterCpp":13284,"hint":13285,"referencePy":13286,"referenceCpp":13287},"输入整数 x（0≤x≤1e9）和 k（0≤k≤30），把 x 的第 k 位置为 1 并输出结果。","一行两个整数 x k。",[13281],{"input":13282,"output":12351},"5 3","x, k = map(int, input().split())\n# 第 k 位置 1\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    \u002F\u002F 置第 k 位\n    return 0;\n}","结果 = x | (1 \u003C\u003C k)。","x,k=map(int,input().split())\nprint(x | (1 \u003C\u003C k))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    cout \u003C\u003C (x | (1 \u003C\u003C k));\n    return 0;\n}","按位或 | 有 1 即为 1；用 x|(1\u003C\u003Ck) 置位、x&(1\u003C\u003Ck) 检测、x&~(1\u003C\u003Ck) 清位，是位标志操作的基础。","23-A1-4","位非",{"id":13289,"title":13290,"level":1197,"module":13103,"seq":770,"goal":13292,"keywords":13293,"explanation":13296,"examples":13308,"exercises":13316,"summary":13346,"next":13347,"nextTitle":13348},"理解按位取反 ~（含补码陷阱），并用它配合 & 来清位。",[13290,13294,13108,13295],"~","清位",[13297,13302],{"title":13298,"content":13299,"codePy":13300,"codeCpp":13301},"1. ~ 把每一位取反","~x 把所有二进制位取反。由于 C++ 整数是 32 位补码，~x 的实际值是 -x-1（例如 ~0 = -1，~5 = -6）。单独看 ~ 容易踩坑，但它与 & 配合清位时非常有用。","print(~5)        # -6（Python 无限位，但 ~(x) = -x-1 仍成立）","cout \u003C\u003C (~5);    \u002F\u002F -6（32 位补码）",{"title":13303,"content":13304,"codePy":13305,"codeCpp":13306,"tip":13307},"2. 用 ~ 清位","想清掉第 k 位：先取反掩码 ~(1\u003C\u003Ck)（得到除第 k 位外全 1），再 x & ~(1\u003C\u003Ck)。这把第 k 位置 0，其它位保持不变。","x = x & ~(1 \u003C\u003C 3)   # 清第 3 位","x &= ~(1 \u003C\u003C 3);     \u002F\u002F 清第 3 位","永远记住 ~ 作用在固定位宽上；清除某位用 &~(1\u003C\u003Ck)。",[13309],{"title":13310,"prompt":13311,"analysis":13312,"codePy":13313,"codeCpp":13314,"run":13315},"例 1：清第 k 位","输入 x 和 k（0≤k≤30），把 x 的第 k 位清零并输出（用 ~）。","x & ~(1\u003C\u003Ck)。","x, k = map(int, input().split())\nprint(x & ~(1 \u003C\u003C k))","int x, k; cin >> x >> k;\ncout \u003C\u003C (x & ~(1 \u003C\u003C k));","输入 13 3 -> 5（1101 & ~1000 = 0101）",{"choice":13317,"coding":13335},[13318,13322,13330],{"id":38,"stem":13319,"options":13320,"answer":9,"explanation":13321},"在 32 位补码下，~0 等于？",[891,1567,4202,892],"所有位取反 => -1。",{"id":47,"stem":13323,"options":13324,"answer":9,"explanation":13329},"~x（补码下）等于？",[13325,13326,13327,13328],"x+1","-x-1","x-1","-x","补码取反即 -x-1。",{"id":56,"stem":13331,"options":13332,"answer":63,"explanation":13334},"清除第 k 位应写？",[13267,13266,13333,13268],"x & ~(1\u003C\u003Ck)","与取反掩码做 & 清位。",[13336],{"id":67,"title":13295,"prompt":13337,"inputFormat":13279,"outputFormat":6275,"samples":13338,"starterPy":13341,"starterCpp":13342,"hint":13343,"referencePy":13344,"referenceCpp":13345},"输入整数 x（0≤x≤1e9）和 k（0≤k≤30），把 x 的第 k 位清零并输出结果。",[13339],{"input":13340,"output":290},"13 3","x, k = map(int, input().split())\n# 清第 k 位\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    \u002F\u002F 清第 k 位\n    return 0;\n}","结果 = x & ~(1 \u003C\u003C k)。","x,k=map(int,input().split())\nprint(x & ~(1 \u003C\u003C k))","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    cout \u003C\u003C (x & ~(1 \u003C\u003C k));\n    return 0;\n}","~x 逐位取反，在 32 位补码下等于 -x-1；清第 k 位用 x & ~(1\u003C\u003Ck)，是位操作的标准 idiom。","23-A1-5","异或",{"id":13347,"title":13348,"level":1197,"module":13103,"seq":838,"goal":13350,"keywords":13351,"explanation":13354,"examples":13366,"exercises":13374,"summary":13407,"next":13408,"nextTitle":9958},"掌握按位异或 ^——相同为 0、不同为 1，利用其自反性做无临时变量交换与\"找出现奇数次的数\"。",[13348,13352,13353,12409],"^","自反",[13355,13360],{"title":13356,"content":13357,"codePy":13358,"codeCpp":13359},"1. ^ 的运算规则与性质","异或：两位相同为 0、不同为 1。重要性质：x^0=x；x^x=0；满足交换律、结合律。于是 a^b^b = a（异或两次抵消）——这就是\"自反性\"。","print(5 ^ 5)     # 0\nprint(5 ^ 0)     # 5","cout \u003C\u003C (5 ^ 5); \u002F\u002F 0",{"title":13361,"content":13362,"codePy":13363,"codeCpp":13364,"tip":13365},"2. 经典应用：交换与去重","利用自反性，不用临时变量也能交换：a^=b; b^=a; a^=b（仅适用于整数且 a≠b 地址）。更常见的是：数组中除一个数出现奇数次、其余成对出现，全部异或一遍即可找出那个数。","a ^= b; b ^= a; a ^= b   # 交换\ntotal = 0\nfor x in arr: total ^= x   # 找落单数","a ^= b; b ^= a; a ^= b;\nint t = 0; for(int x:arr) t ^= x;","异或\"消消乐\"：成对出现的会抵消为 0，剩下一个就是答案。",[13367],{"title":13368,"prompt":13369,"analysis":13370,"codePy":13371,"codeCpp":13372,"run":13373},"例 1：找落单数","输入 n 个整数，其中除一个数出现 1 次外其余都出现 2 次，输出那个数。","全部异或，成对的抵消为 0，剩下落单数。","a = list(map(int, input().split()))\nt = 0\nfor x in a: t ^= x\nprint(t)","int t = 0, x;\nwhile (cin >> x) t ^= x;\ncout \u003C\u003C t;","输入 4 1 2 1 2 -> 4",{"choice":13375,"coding":13395},[13376,13380,13388],{"id":38,"stem":13377,"options":13378,"answer":9,"explanation":13379},"5 ^ 5 的结果是？",[883,891,290,892],"相同异或为 0。",{"id":47,"stem":13381,"options":13382,"answer":9,"explanation":13387},"关于异或，正确的是？",[13383,13384,13385,13386],"x^0 = 0","x^x = 0 且 a^b^b = a","异或不满足交换律","x^1 = x","自反性 a^b^b=a。",{"id":56,"stem":13389,"options":13390,"answer":607,"explanation":13394},"用异或\"找出现奇数次的数\"利用了？",[13391,13392,13108,13393],"结合律与自反","移位","与运算","成对抵消、自反留单。",[13396],{"id":67,"title":13397,"prompt":13398,"inputFormat":143,"outputFormat":13399,"samples":13400,"starterPy":13402,"starterCpp":13403,"hint":13404,"referencePy":13405,"referenceCpp":13406},"异或交换","输入两个整数 a,b（|a|,|b|≤1e9 且 a!=b），用异或（不借助临时变量）交换它们并输出交换后的 a b。","一行交换后的 a b。",[13401],{"input":1256,"output":1327},"a, b = map(int, input().split())\n# 异或交换\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int a, b; cin >> a >> b;\n    \u002F\u002F 异或交换\n    return 0;\n}","a^=b; b^=a; a^=b;","a,b=map(int,input().split())\na^=b; b^=a; a^=b\nprint(a,b)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int a, b; cin >> a >> b;\n    a ^= b; b ^= a; a ^= b;\n    cout \u003C\u003C a \u003C\u003C \" \" \u003C\u003C b;\n    return 0;\n}","异或 ^ 相同为 0、不同为 1；性质 x^0=x、x^x=0、可交换结合。利用自反性可无临时变量交换、并用\"消消乐\"找出奇数次出现的数。","23-A1-6",{"id":13408,"title":9958,"level":1197,"module":13103,"seq":936,"goal":13410,"keywords":13411,"explanation":13414,"examples":13426,"exercises":13434,"summary":13470,"next":13471,"nextTitle":6551},"理解左移 \u003C\u003C——在不溢出时等价于乘 2^k，用于快速乘除、构造掩码、取位。",[9958,13412,13413,13170],"\u003C\u003C","乘2",[13415,13420],{"title":13416,"content":13417,"codePy":13418,"codeCpp":13419},"1. \u003C\u003C 把二进制整体左移","x \u003C\u003C k 把 x 的二进制向左移 k 位，右边补 0。在没有溢出时，这等价于 x × 2^k。例如 1\u003C\u003C3 = 8，5\u003C\u003C1 = 10。","print(1 \u003C\u003C 3)    # 8\nprint(5 \u003C\u003C 1)    # 10","cout \u003C\u003C (1 \u003C\u003C 3); \u002F\u002F 8",{"title":13421,"content":13422,"codePy":13423,"codeCpp":13424,"tip":13425},"2. 构造掩码与取位","(1\u003C\u003Ck) 得到一个\"第 k 位为 1、其余为 0\"的掩码，是位运算的基石：置位用 |、检测用 &、清位用 &~。(x>>k)&1 用来读取第 k 位。","mask = 1 \u003C\u003C k\nbit = (x >> k) & 1","int mask = 1 \u003C\u003C k;\nint bit = (x >> k) & 1;","注意左移可能溢出：long long 场景用 1LL \u003C\u003C k。",[13427],{"title":13428,"prompt":13429,"analysis":13430,"codePy":13431,"codeCpp":13432,"run":13433},"例 1：判断第 k 位是否为 1","输入 x 和 k（0≤k≤30），输出 x 的第 k 位（0 或 1）。","(x>>k) & 1。","x, k = map(int, input().split())\nprint((x >> k) & 1)","int x, k; cin >> x >> k;\ncout \u003C\u003C ((x >> k) & 1);","输入 13 3 -> 1（1101 第 3 位为 1）",{"choice":13435,"coding":13456},[13436,13440,13448],{"id":38,"stem":13437,"options":13438,"answer":63,"explanation":13439},"1 \u003C\u003C 3 的结果是？",[890,624,148,2707],"左移 3 位 = 乘 8。",{"id":47,"stem":13441,"options":13442,"answer":607,"explanation":13447},"5 \u003C\u003C 1 等价于？",[13443,13444,13445,13446],"5×2=10","5÷2","5+1","5²","左移 1 位 = 乘 2。",{"id":56,"stem":13449,"options":13450,"answer":9,"explanation":13455},"读取 x 的第 k 位应写？",[13451,13452,13453,13454],"(x\u003C\u003Ck)&1","(x>>k)&1","(x|k)&1","x&(1>>k)","先右移再 &1 取最低位。",[13457],{"id":67,"title":13458,"prompt":13459,"inputFormat":13279,"outputFormat":13460,"samples":13461,"starterPy":13465,"starterCpp":13466,"hint":13467,"referencePy":13468,"referenceCpp":13469},"取第 k 位","输入整数 x（0≤x≤1e9）和 k（0≤k≤30），输出 x 的第 k 位（0 或 1）。","一行 0 或 1。",[13462,13463],{"input":13340,"output":892},{"input":13464,"output":891},"13 1","x, k = map(int, input().split())\n# 取第 k 位\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    \u002F\u002F 取第 k 位\n    return 0;\n}","结果 = (x >> k) & 1。","x,k=map(int,input().split())\nprint((x >> k) & 1)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, k; cin >> x >> k;\n    cout \u003C\u003C ((x >> k) & 1);\n    return 0;\n}","左移 \u003C\u003Ck 等价于（不溢出时）乘 2^k；1\u003C\u003Ck 构造第 k 位掩码，配合 &\u002F|\u002F~ 做位操作。","23-A1-7",{"id":13471,"title":6551,"level":1197,"module":13103,"seq":998,"goal":13473,"keywords":13474,"explanation":13478,"examples":13490,"exercises":13498,"summary":13531,"next":4883,"nextTitle":4884},"理解右移 >>——等价于除以 2^k（向下取整），用于快速除二、取位、统计二进制中 1 的个数。",[6551,13475,13476,13477],">>","除2","popcount",[13479,13484],{"title":13480,"content":13481,"codePy":13482,"codeCpp":13483},"1. >> 把二进制整体右移","x >> k 把 x 的二进制向右移 k 位，左边补符号位（有符号数算术右移）。在没有溢出时等价于 x 除以 2^k 并向下取整。例如 8>>2 = 2。","print(8 >> 2)    # 2\nprint(7 >> 1)    # 3（下取整）","cout \u003C\u003C (8 >> 2); \u002F\u002F 2",{"title":13485,"content":13486,"codePy":13487,"codeCpp":13488,"tip":13489},"2. 取位与统计 1 的个数","(x>>k)&1 读取第 k 位。反复\"取最低位 (x&1) 再 x>>=1\"就能统计 x 二进制中 1 的个数（popcount）。右移是位运算循环的常用推进方式。","cnt = 0\nwhile x:\n    cnt += x & 1\n    x >>= 1","int cnt = 0;\nwhile (x) { cnt += x & 1; x >>= 1; }","x & (x-1) 可以一次性\"抹掉最低位的 1\"，统计 1 的个数更快。",[13491],{"title":13492,"prompt":13493,"analysis":13494,"codePy":13495,"codeCpp":13496,"run":13497},"例 1：统计二进制中 1 的个数","输入非负整数 x（≤1e9），输出其二进制表示中 1 的个数。","循环：累加 x&1，然后 x>>=1，直到 x 为 0。","x = int(input())\ncnt = 0\nwhile x:\n    cnt += x & 1\n    x >>= 1\nprint(cnt)","int x, cnt = 0; cin >> x;\nwhile (x) { cnt += x & 1; x >>= 1; }\ncout \u003C\u003C cnt;","输入 13（1101）-> 3",{"choice":13499,"coding":13519},[13500,13504,13511],{"id":38,"stem":13501,"options":13502,"answer":9,"explanation":13503},"8 >> 2 的结果是？",[1663,759,892,8786],"右移 2 位 = 除以 4，得 2。",{"id":47,"stem":13505,"options":13506,"answer":9,"explanation":13510},"x >> 1 等价于（无溢出时）？",[13507,13508,13325,13509],"x×2","x÷2 下取整","x%2","右移 1 位 = 除以 2。",{"id":56,"stem":13512,"options":13513,"answer":607,"explanation":13518},"统计二进制中 1 的个数，正确循环是？",[13514,13515,13516,13517],"while(x){c+=x&1;x>>=1;}","while(x){c++;x++;}","while(x){c+=x;x>>=1;}","for(i=0;i\u003C32;i++)c+=x;","每次取最低位再右移推进。",[13520],{"id":67,"title":13477,"prompt":13521,"inputFormat":13522,"outputFormat":6275,"samples":13523,"starterPy":13526,"starterCpp":13527,"hint":13528,"referencePy":13529,"referenceCpp":13530},"输入非负整数 x（0≤x≤1e9），输出其二进制中 1 的个数。","一行整数 x。",[13524,13525],{"input":12351,"output":890},{"input":891,"output":891},"x = int(input())\ncnt = 0\n# 统计 1 的个数\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, cnt = 0; cin >> x;\n    \u002F\u002F 统计 1 的个数\n    return 0;\n}","while(x){ cnt += x&1; x >>= 1; }","x=int(input())\ncnt=0\nwhile x:\n    cnt+=x&1\n    x>>=1\nprint(cnt)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int x, cnt = 0; cin >> x;\n    while (x) { cnt += x & 1; x >>= 1; }\n    cout \u003C\u003C cnt; return 0;\n}","右移 >>k 等价于（无溢出时）除以 2^k 向下取整；配合 x&1 取最低位、(x>>k)&1 取任意位，可统计二进制中 1 的个数。",{"id":9597,"title":8805,"level":2505,"module":13103,"seq":9,"goal":13533,"keywords":13534,"explanation":13537,"examples":13549,"exercises":13557,"summary":13596,"next":13597,"nextTitle":13598},"理解位掩码：用一个整数的二进制位表示一组布尔状态，第 i 位为 1 表示第 i 个选项被选中。",[8805,1601,13535,13536],"布尔状态","1\u003C\u003Ci",[13538,13544],{"title":13539,"content":13540,"codePy":13541,"codeCpp":13542,"tip":13543},"1. 用位表示状态","mask 的第 i 位记录\"第 i 项是否选中\"。1\u003C\u003Ci 得到只有第 i 位为 1 的掩码。","mask = 0\nmask |= (1 \u003C\u003C 2)   # 选中第 2 项\nset_bit = (mask >> 2) & 1","int mask = 0; mask |= (1 \u003C\u003C 2); int setBit = (mask >> 2) & 1;","布尔开关很多且范围小（≤30）时，位掩码既省空间又好写。",{"title":13545,"content":13546,"codePy":13547,"codeCpp":13548},"2. 常用位操作","置位 mask|=b；清位 mask&=~b；翻转 mask^=b；取第 i 位 (mask>>i)&1。","mask |= (1\u003C\u003Ci)     # 置 1\nmask &= ~(1\u003C\u003Ci)    # 清 0\nmask ^= (1\u003C\u003Ci)     # 翻转","mask |= (1\u003C\u003Ci); mask &= ~(1\u003C\u003Ci); mask ^= (1\u003C\u003Ci);",[13550],{"title":13551,"prompt":13552,"analysis":13553,"codePy":13554,"codeCpp":13555,"run":13556},"例 1：掩码含义","mask = 13（二进制 1101），哪些位被选中？","第 0、2、3 位为 1，对应选中第 0、2、3 项。","mask=13\nprint([i for i in range(4) if (mask>>i)&1])","int m=13; for(int i=0;i\u003C4;i++) if((m>>i)&1) cout\u003C\u003Ci\u003C\u003C\" \";","[0, 2, 3]",{"choice":13558,"coding":13582},[13559,13567,13575],{"id":38,"stem":13560,"options":13561,"answer":607,"explanation":13566},"选中第 3 项应写？",[13562,13563,13564,13565],"mask|=1\u003C\u003C3","mask|=3","mask&=1\u003C\u003C3","mask^=3","1\u003C\u003C3 是第 3 位为 1 的掩码。",{"id":47,"stem":13568,"options":13569,"answer":607,"explanation":13574},"取第 i 位值的表达式？",[13570,13571,13572,13573],"(mask>>i)&1","mask&i","mask|i","mask^i","右移再与 1。",{"id":56,"stem":13576,"options":13577,"answer":9,"explanation":13581},"位掩码适合表示？",[13578,13579,775,13580],"大整数","小范围布尔集合","浮点","每位一个开关。",[13583],{"id":67,"title":13584,"prompt":13585,"inputFormat":13586,"outputFormat":13587,"samples":13588,"starterPy":13592,"starterCpp":13593,"hint":13594,"referencePy":13595,"referenceCpp":13593},"状态查询","输入 n（1≤n≤30）和 m 个查询。每个查询给出 k 与 k 个下标（0 起），构建掩码并回答这些下标是否\"全部被选中\"。（用整数按位或构造，再逐位检查。）","第一行 n m；随后 m 行，每行先 k 再 k 个下标。","每行 Yes\u002FNo。",[13589],{"input":13590,"output":13591},"4 1\n3 0 2 3","Yes","n, m = map(int, input().split())\nmask = 0\n# 先读一行构造 mask 再判断（本题简化为：给定下标即置位并判断给定的位都置位）\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; int k; cin>>k; int mask=0,x; while(k--){cin>>x; mask|=(1\u003C\u003Cx);} int ok=1; cin>>k; while(k--){cin>>x; if(!((mask>>x)&1)) ok=0;} cout\u003C\u003C(ok?\"Yes\":\"No\"); }","置位后逐一检查给定下标的位。","n,m=map(int,input().split())\nmask=0\nk=int(input());\nfor x in map(int,input().split()): mask|=(1\u003C\u003Cx)\nk=int(input()); ok=1\nfor x in map(int,input().split()):\n    if not ((mask>>x)&1): ok=0\nprint(\"Yes\" if ok else \"No\")","位掩码用整数二进制位表示小范围布尔集合；置位 |= (1\u003C\u003Ci)，清位 &= ~(1\u003C\u003Ci)，取位 (mask>>i)&1。","23-A2-2","集合表示",{"id":13597,"title":13598,"level":2505,"module":13103,"seq":63,"goal":13600,"keywords":13601,"explanation":13606,"examples":13618,"exercises":13626,"summary":13661,"next":13662,"nextTitle":13663},"用整数表示一个有限集合（元素为 0..N-1），元素 i 在集合中当且仅当第 i 位为 1。",[13602,13603,13604,13605],"集合编码","整数集合","全集","枚举子集",[13607,13612],{"title":13608,"content":13609,"codePy":13610,"codeCpp":13611},"1. 整数即集合","全集 {0..n-1} 对应掩码 (1\u003C\u003Cn)-1。空集为 0，单元素 {i} 即 1\u003C\u003Ci。","n = 3\nfull = (1 \u003C\u003C n) - 1   # 二进制 111，全集\nprint(full)","int n=3, full=(1\u003C\u003Cn)-1;",{"title":13613,"content":13614,"codePy":13615,"codeCpp":13616,"tip":13617},"2. 集合运算","并集 a|b；交集 a&b；差集 a&(~b)；对称差 a^b。","a = (1\u003C\u003C1)|(1\u003C\u003C2)   # {1,2}\nb = (1\u003C\u003C2)|(1\u003C\u003C0)   # {0,2}\nprint(a | b, a & b)","int a=(1\u003C\u003C1)|(1\u003C\u003C2), b=(1\u003C\u003C0)|(1\u003C\u003C2); \u002F\u002F a|b, a&b","位运算天然实现集合运算。",[13619],{"title":13620,"prompt":13621,"analysis":13622,"codePy":13623,"codeCpp":13624,"run":13625},"例 1：交集","{1,2} ∩ {0,2} = ?","{2}。","a=(1\u003C\u003C1)|(1\u003C\u003C2); b=(1\u003C\u003C0)|(1\u003C\u003C2); print([i for i in range(3) if (a&b>>i)&1])","\u002F\u002F 结果只有第 2 位","[2]",{"choice":13627,"coding":13648},[13628,13632,13640],{"id":38,"stem":13629,"options":13630,"answer":9,"explanation":13631},"n=3 的全集掩码是？",[890,885,148,291],"1\u003C\u003C3 -1 = 7 (111)。",{"id":47,"stem":13633,"options":13634,"answer":9,"explanation":13639},"集合并集用？",[13635,13636,13637,13638],"a&b","a|b","a^b","~a","按位或。",{"id":56,"stem":13641,"options":13642,"answer":607,"explanation":13647},"判断 i 是否∈集合 s？",[13643,13644,13645,13646],"(s>>i)&1","s&i","s|i","s^i","取第 i 位。",[13649],{"id":67,"title":13650,"prompt":13651,"inputFormat":13652,"outputFormat":13653,"samples":13654,"starterPy":13657,"starterCpp":13658,"hint":13659,"referencePy":13660,"referenceCpp":13658},"集合运算","输入两个由非负整数（≤20）构成的集合（各自一行，以 -1 结束），输出它们交集的元素个数。","两行，每行若干整数以 -1 结束。","一行整数（交集大小）。",[13655],{"input":13656,"output":759},"1 2 3 -1\n2 3 4 -1","def readset():\n    s=0\n    while True:\n        x=int(input()); \n        if x\u003C0: break\n        s|=(1\u003C\u003Cx)\n    return s\na=readset(); b=readset()\ninter=a&b\nprint(bin(inter).count(\"1\"))","#include \u003Ciostream>\nusing namespace std;\nint read(){ int s=0,x; while(cin>>x && x>=0) s|=(1\u003C\u003Cx); return s; }\nint main(){ int a=read(),b=read(); int c=a&b,ans=0; while(c){ ans+=c&1; c>>=1; } cout\u003C\u003Cans; }","读入时置位，交集为 a&b，统计 1 的个数。","def readset():\n    s=0\n    while True:\n        x=int(input())\n        if x\u003C0: break\n        s|=(1\u003C\u003Cx)\n    return s\na=readset(); b=readset()\ninter=a&b\nprint(bin(inter).count(\"1\"))","整数二进制位直接表示有限集合；全集 (1\u003C\u003Cn)-1，并集 |、交集 &、差集 &~、对称差 ^。","23-A2-3","子集判断",{"id":13662,"title":13663,"level":2505,"module":13103,"seq":703,"goal":13665,"keywords":13666,"explanation":13669,"examples":13681,"exercises":13687,"summary":13724,"next":13725,"nextTitle":13726},"掌握用位运算判断一个集合是否为另一个集合的子集\u002F超集，以及如何枚举子集。",[13663,13667,13605,13668],"A⊆B","lowbit 枚举",[13670,13675],{"title":13671,"content":13672,"codePy":13673,"codeCpp":13674},"1. 子集判定","A⊆B 当且仅当 A 的所有位在 B 中都为 1，即 (A & B) == A（或 (A | B) == B）。","A = (1\u003C\u003C1)|(1\u003C\u003C2)   # {1,2}\nB = (1\u003C\u003C0)|(1\u003C\u003C1)|(1\u003C\u003C2)  # {0,1,2}\nprint((A & B) == A)","\u002F\u002F (A&B)==A 则 A⊆B",{"title":13676,"content":13677,"codePy":13678,"codeCpp":13679,"tip":13680},"2. 枚举子集","从 sub=mask 起，循环 sub=(sub-1)&mask 直到 0，可枚举 mask 的所有非空子集。","sub = mask\nwhile sub:\n    # 处理子集 sub\n    sub = (sub - 1) & mask","for(int sub=mask; sub; sub=(sub-1)&mask){ \u002F* 子集 sub *\u002F }","枚举子集用 (sub-1)&mask，常用于状态压缩 DP。",[13682],{"title":13683,"prompt":13684,"analysis":13685,"codePy":13686,"codeCpp":8388,"run":819},"例 1：判子集","A={1,2}，B={0,1,2}，A 是 B 的子集吗？","(A&B)=A，是。","A=(1\u003C\u003C1)|(1\u003C\u003C2); B=(1\u003C\u003C0)|(1\u003C\u003C1)|(1\u003C\u003C2); print((A&B)==A)",{"choice":13688,"coding":13711},[13689,13697,13705],{"id":38,"stem":13690,"options":13691,"answer":607,"explanation":13696},"判定 A⊆B 的正确式？",[13692,13693,13694,13695],"(A&B)==A","(A|B)==A","A&B==B","A^B==0","A 的位都要在 B 中。",{"id":47,"stem":13698,"options":13699,"answer":607,"explanation":13704},"枚举 mask 子集的迭代？",[13700,13701,13702,13703],"sub=mask; sub=(sub-1)&mask","sub+=1","sub\u003C\u003C=1","sub=mask\u002F2","经典子集枚举。",{"id":56,"stem":13706,"options":13707,"answer":9,"explanation":13710},"A⊆B 时 A|B 等于？",[13708,217,891,13709],"A","A^B","合并后就是 B。",[13712],{"id":67,"title":13713,"prompt":13714,"inputFormat":13715,"outputFormat":13716,"samples":13717,"starterPy":13720,"starterCpp":13721,"hint":13722,"referencePy":13723,"referenceCpp":13721},"子集计数","输入 n（1≤n≤20）和掩码 mask（0≤mask\u003C2^n），枚举 mask 的所有子集（含空集）并输出子集个数。","一行 n mask。","一行整数（子集总数）。",[13718],{"input":13719,"output":148},"3 7","n, mask = map(int, input().split())\ncnt = 0\nsub = mask\ncnt += 1  # 空集\nwhile sub:\n    cnt += 1\n    sub = (sub - 1) & mask\nprint(cnt)","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; long long mask; cin>>n>>mask; long long cnt=1, sub=mask; while(sub){ cnt++; sub=(sub-1)&mask; } cout\u003C\u003Ccnt; }","从 mask 向下 (sub-1)&mask 直到 0，加空集。","n, mask = map(int, input().split())\ncnt = 1\nsub = mask\nwhile sub:\n    cnt += 1\n    sub = (sub - 1) & mask\nprint(cnt)","A⊆B 判定：(A&B)==A；枚举 mask 子集用 sub=mask; sub=(sub-1)&mask 直到 0，状态压缩 DP 常用。","23-A2-4","lowbit",{"id":13725,"title":13726,"level":2505,"module":13103,"seq":770,"goal":13728,"keywords":13729,"explanation":13733,"examples":13744,"exercises":13751,"summary":13781,"next":13782,"nextTitle":13477},"掌握 lowbit：取出整数二进制最低位的 1 及其后的 0 所构成的值，公式为 x & (-x)。",[13726,13730,13731,13732],"最低位","x & -x","树状数组",[13734,13739],{"title":13735,"content":13736,"codePy":13737,"codeCpp":13738},"1. 原理","补码下 -x = ~x + 1，于是 x & (-x) 只保留最低位的 1。如 x=12(1100)，lowbit=4(0100)。","def lowbit(x): return x & (-x)\nprint(lowbit(12))","int lowbit(int x){ return x & (-x); }",{"title":4654,"content":13740,"codePy":13741,"codeCpp":13742,"tip":13743},"清除最低位：x -= lowbit(x)；常用于树状数组（Fenwick）、枚举 1 的位置。","x = 12\nwhile x:\n    b = lowbit(x)\n    # 处理第 log2(b) 位\n    x -= b","\u002F\u002F while(x){ int b=x&-x; x-=b; } 依次取最低位","lowbit 是树状数组的核心操作。",[13745],{"title":13746,"prompt":13747,"analysis":13748,"codePy":13749,"codeCpp":13750,"run":759},"例 1：lowbit","x=10（1010）的 lowbit 是？","最低位 1 在第 1 位，lowbit=2。","print(10 & -10)","cout \u003C\u003C (10 & -10);",{"choice":13752,"coding":13769},[13753,13761,13765],{"id":38,"stem":13754,"options":13755,"answer":607,"explanation":13760},"lowbit 的公式？",[13756,13757,13758,13759],"x & (-x)","x & (x-1)","x | (-x)","~x","补码性质。",{"id":47,"stem":13762,"options":13763,"answer":703,"explanation":13764},"x=8 的 lowbit？",[892,759,1663,148],"1000 最低位即 8。",{"id":56,"stem":13766,"options":13767,"answer":9,"explanation":13768},"lowbit 常用于？",[3117,13732,2509,3259],"Fenwick 核心。",[13770],{"id":67,"title":13668,"prompt":13771,"inputFormat":13522,"outputFormat":13772,"samples":13773,"starterPy":13776,"starterCpp":13777,"hint":13778,"referencePy":13779,"referenceCpp":13780},"输入 x（1≤x≤10^9），按从低到高的顺序输出 x 二进制中所有为 1 的位的下标（0 起）。","一行若干整数（空格分隔）。",[13774],{"input":12351,"output":13775},"0 2 3","x = int(input())\nres = []\nwhile x:\n    b = x & (-x)\n    res.append(b.bit_length() - 1)\n    x -= b\nprint(*res)","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long x; cin>>x; while(x){ long long b=x&-x; int p=0; while((b>>p)&1==0) p++; cout\u003C\u003Cp\u003C\u003C\" \"; x-=b; } }","lowbit 取最低位，其 log2 即下标。","x=int(input())\nres=[]\nwhile x:\n    b=x&-x\n    res.append(b.bit_length()-1)\n    x-=b\nprint(*res)","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long x; cin>>x; while(x){ long long b=x&-x; int p=0; while(!((b>>p)&1)) p++; cout\u003C\u003Cp\u003C\u003C\" \"; x-=b; } }","lowbit(x)=x&(-x)，取出最低位的 1 及其后缀 0；x-=lowbit(x) 可依次清除各位，是树状数组基础。","23-A2-5",{"id":13782,"title":13477,"level":2505,"module":13103,"seq":838,"goal":13784,"keywords":13785,"explanation":13789,"examples":13801,"exercises":13808,"summary":13838,"next":13839,"nextTitle":13840},"掌握 popcount（统计整数二进制中 1 的个数），以及位运算统计技巧。",[13477,13786,13787,13788],"计数 1","Brian Kernighan","__builtin_popcount",[13790,13795],{"title":13791,"content":13792,"codePy":13793,"codeCpp":13794},"1. 直接计算","Python 用 bin(x).count(\"1\")；C++ 用 __builtin_popcount \u002F __builtin_popcountll。","x = 13\nprint(bin(x).count(\"1\"))","int c = __builtin_popcount(x);",{"title":13796,"content":13797,"codePy":13798,"codeCpp":13799,"tip":13800},"2. 循环清最低位","Brian Kernighan 法：while x: x &= x-1；每次清除最低位 1，循环次数即 1 的个数。","x = 13; c = 0\nwhile x:\n    x &= x - 1\n    c += 1\nprint(c)","int c=0; while(x){ x&=x-1; c++; }","x & (x-1) 会把最低位的 1 清零。",[13802],{"title":13803,"prompt":13804,"analysis":13805,"codePy":13806,"codeCpp":13807,"run":890},"例 1：个数","x=13（1101）有几个 1？","3 个。","print(bin(13).count(\"1\"))","cout \u003C\u003C __builtin_popcount(13);",{"choice":13809,"coding":13827},[13810,13816,13823],{"id":38,"stem":13811,"options":13812,"answer":9,"explanation":13815},"统计 1 的个数叫？",[13726,13477,13813,13814],"parity","mask","population count。",{"id":47,"stem":13817,"options":13818,"answer":607,"explanation":13822},"x & (x-1) 的作用？",[13819,13820,13821,1308],"清最低位 1","置最高位","取反","Brian Kernighan 法。",{"id":56,"stem":13824,"options":13825,"answer":63,"explanation":13826},"x=15 的 popcount？",[759,890,1663,290],"1111 四个 1。",[13828],{"id":67,"title":13829,"prompt":13830,"inputFormat":13522,"outputFormat":6275,"samples":13831,"starterPy":13834,"starterCpp":13835,"hint":13836,"referencePy":13837,"referenceCpp":13835},"位计数","输入 x（0≤x≤10^18），输出其二进制中 1 的个数。",[13832],{"input":13833,"output":148},"255","x = int(input())\nprint(bin(x).count(\"1\"))","#include \u003Ciostream>\nusing namespace std;\nint main(){ unsigned long long x; cin>>x; int c=0; while(x){ x&=x-1; c++; } cout\u003C\u003Cc; }","用 x & (x-1) 反复清零统计。","x=int(input())\nprint(bin(x).count(\"1\"))","popcount 统计二进制 1 的个数；用 x & (x-1) 反复清除最低位，循环次数即答案，或借助内置函数。","24基础数论-A2-1","欧几里得算法",{"id":6897,"title":6898,"level":4885,"module":13842,"seq":9,"goal":13843,"keywords":13844,"explanation":13846,"examples":13858,"exercises":13863,"summary":13897,"next":13898,"nextTitle":2070},"基础数论","理解整数的奇偶：能被 2 整除为偶数，否则为奇数；掌握用取模或位运算判断奇偶。",[6898,843,13166,13845],"分类",[13847,13852],{"title":13848,"content":13849,"codePy":13850,"codeCpp":13851},"1. 用 %2 或 &1 判断","n % 2 == 0 为偶数，n % 2 == 1 为奇数（注意负数取模语义：C++ 中 -3%2=-1，因此更稳妥用 n&1 或 abs）。二进制下，奇数的最低位是 1，所以 n & 1 为 1 即奇数。","def is_even(n): return n % 2 == 0\nprint(\"奇\" if n & 1 else \"偶\")","bool even = (n % 2 == 0);\nbool odd = (n & 1);   \u002F\u002F 最低位为 1 是奇数",{"title":13853,"content":13854,"codePy":13855,"codeCpp":13856,"tip":13857},"2. 奇偶性常用于分类与配对","很多问题用奇偶分类：棋盘黑白格、相邻格子异色、两数之和的奇偶由各自奇偶决定（奇+奇=偶，偶+偶=偶，奇+偶=奇）。判断\"能否配对\"\"是否同色\"时先想奇偶。","# 棋盘 (x+y) 的奇偶决定黑白格\ncolor = \"黑\" if (x + y) % 2 == 0 else \"白\"","\u002F\u002F (x+y) 奇偶性决定格子颜色","涉及\"两两配对、间隔、相邻不同色\"的问题，优先考虑奇偶性。",[13859],{"title":13860,"prompt":13861,"analysis":13862,"codePy":13189,"codeCpp":13190,"run":13191},"例 1：判断奇偶","输入整数 n，输出\"ODD\"或\"EVEN\"。","用 n & 1（或 n%2）判断最低位。",{"choice":13864,"coding":13887},[13865,13873,13879],{"id":38,"stem":13866,"options":13867,"answer":9,"explanation":13872},"判断奇数的可靠方法是？",[13868,13869,13870,13871],"n%2==1","n&1==1","n\u002F2==0","n>0","最低位为 1 即奇，且对负数也稳。",{"id":47,"stem":13874,"options":13875,"answer":607,"explanation":13878},"奇 + 偶 的结果是？",[13876,13877,1865,891],"奇","偶","奇+偶=奇。",{"id":56,"stem":13880,"options":13881,"answer":607,"explanation":13886},"棋盘 (x+y) 同奇偶的格子？",[13882,13883,13884,13885],"颜色相同","颜色不同","都在边界","无效","同奇偶同色。",[13888],{"id":67,"title":919,"prompt":13889,"inputFormat":1456,"outputFormat":13217,"samples":13890,"starterPy":13893,"starterCpp":13894,"hint":13895,"referencePy":13189,"referenceCpp":13896},"输入整数 n（|n|≤10^9），输出\"ODD\"或\"EVEN\"。",[13891,13892],{"input":885,"output":13220},{"input":148,"output":13222},"n = int(input())\n# 判断奇偶\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin >> n;\n    \u002F\u002F 判断奇偶\n    return 0;\n}","用 n & 1 或 n % 2 判断最低位；注意负数。","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin >> n;\n    cout \u003C\u003C ((n & 1) ? \"ODD\" : \"EVEN\");\n    return 0;\n}","偶数是 2 的倍数；用 n%2==0 或 n&1==1 判断奇偶（位运算对负数更稳）。奇偶性常用于分类、配对、棋盘着色等问题。","24基础数论-A1-2",{"id":13898,"title":2070,"level":4885,"module":13842,"seq":63,"goal":13900,"keywords":13901,"explanation":13905,"examples":13916,"exercises":13923,"summary":13958,"next":13959,"nextTitle":13960},"掌握用欧几里得算法（辗转相除法）求两个数的最大公约数 gcd。",[2070,13902,13903,13904],"gcd","辗转相除","欧几里得",[13906,13910],{"title":13907,"content":13908,"codePy":2028,"codeCpp":13909},"1. 辗转相除：gcd(a,b)=gcd(b, a%b)","核心性质：gcd(a,b)=gcd(b, a mod b)。不断用较小数替换较大数、用余数替换，直到余数为 0，此时的非零数就是最大公约数。","long long gcd(long long a, long long b) {\n    while (b) { long long t = a % b; a = b; b = t; }\n    return a;\n}",{"title":13911,"content":13912,"codePy":13913,"codeCpp":13914,"tip":13915},"2. 为什么正确","a 和 b 的公约数，必然也是 b 和 a%b 的公约数（因为 a = k*b + a%b）。所以两对数的公约集合相同，gcd 也相同；余数不断变小直至 0。","# gcd(12, 8): 12%8=4 -> gcd(8,4); 8%4=0 -> 4\ndef gcd(a, b):\n    while b: a, b = b, a % b\n    return a","\u002F\u002F 12,8 -> 8,4 -> 4,0 => gcd=4","C++ 也可用 \u003Cnumeric> 的 std::gcd；自己写时注意用 long long 防溢出。",[13917],{"title":13918,"prompt":13919,"analysis":13920,"codePy":13921,"codeCpp":13922,"run":1663},"例 1：求 gcd","求 gcd(12, 8)。","12%8=4→(8,4)；8%4=0→(4,0)，gcd=4。","print(gcd(12, 8))","cout \u003C\u003C gcd(12, 8);",{"choice":13924,"coding":13944},[13925,13929,13937],{"id":38,"stem":13926,"options":13927,"answer":9,"explanation":13928},"gcd(12,8) 等于？",[759,1663,148,884],"辗转相除得 4。",{"id":47,"stem":13930,"options":13931,"answer":9,"explanation":13936},"欧几里得算法的核心关系是？",[13932,13933,13934,13935],"gcd(a,b)=gcd(b,a+b)","gcd(a,b)=gcd(b,a%b)","gcd(a,b)=a-b","gcd(a,b)=b","用余数替换。",{"id":56,"stem":13938,"options":13939,"answer":607,"explanation":13943},"辗转相除何时停止？",[13940,13941,13942,1568],"余数为 0","相等","a\u003Cb","余数为 0 时当前 a 即 gcd。",[13945],{"id":67,"title":2070,"prompt":13946,"inputFormat":143,"outputFormat":13947,"samples":13948,"starterPy":13953,"starterCpp":13954,"hint":13955,"referencePy":13956,"referenceCpp":13957},"输入两个正整数 a,b（1≤a,b≤10^9），输出它们的最大公约数。","一行整数 gcd。",[13949,13951],{"input":13950,"output":1663},"12 8",{"input":13952,"output":892},"7 5","def gcd(a, b):\n    # 辗转相除\n    pass\na, b = map(int, input().split())\nprint(gcd(a, b))","#include \u003Ciostream>\nusing namespace std;\nlong long gcd(long long a, long long b){\n    \u002F\u002F 辗转相除\n}\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Cgcd(a,b); }","while(b): a,b=b,a%b；返回 a。","def gcd(a, b):\n    while b:\n        a, b = b, a % b\n    return a\na, b = map(int, input().split())\nprint(gcd(a, b))","#include \u003Ciostream>\nusing namespace std;\nlong long gcd(long long a, long long b){ while(b){ long long t=a%b; a=b; b=t; } return a; }\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Cgcd(a,b); }","用辗转相除法求 gcd：gcd(a,b)=gcd(b,a%b)，反复取余直到余数为 0，非零者即最大公约数。原理是两对数的公约集合相同。","24基础数论-A1-3","最小公倍数",{"id":13959,"title":13960,"level":4885,"module":13842,"seq":703,"goal":13962,"keywords":13963,"explanation":13965,"examples":13977,"exercises":13984,"summary":14019,"next":14020,"nextTitle":14021},"掌握用 gcd 求最小公倍数 lcm：lcm(a,b) = a×b \u002F gcd(a,b)，并注意先除后乘以防溢出。",[13960,13964,13902,1818],"lcm",[13966,13971],{"title":13967,"content":13968,"codePy":13969,"codeCpp":13970},"1. lcm 与 gcd 的关系","对任意正整数 a,b 有 a×b = gcd(a,b) × lcm(a,b)，因此 lcm(a,b) = a×b \u002F gcd(a,b)。先除以 gcd 再乘另一个数，可避免 a×b 过早溢出。","def lcm(a, b):\n    return a \u002F\u002F gcd(a, b) * b","long long lcm(long long a, long long b) {\n    return a \u002F gcd(a, b) * b;   \u002F\u002F 先除后乘\n}",{"title":13972,"content":13973,"codePy":13974,"codeCpp":13975,"tip":13976},"2. 先除后乘，防溢出","若先算 a*b 再除 gcd，当 a、b 接近 10^9 时 a*b 会超过 32 位甚至 64 位范围。写成 a\u002Fgcd*b（C++ 用 long long）能大幅降低中间值。","# 错误：a*b 可能溢出；正确：a\u002F\u002Fgcd(a,b)*b\nlcm = a \u002F\u002F gcd(a, b) * b","\u002F\u002F a\u002Fgcd*b 而不是 a*b\u002Fgcd，减少溢出风险","需要多个数的 lcm，可两两合并：lcm(lcm(a,b),c)。",[13978],{"title":13979,"prompt":13980,"analysis":13981,"codePy":13982,"codeCpp":13983,"run":884},"例 1：求 lcm","求 lcm(4, 6)。","gcd(4,6)=2；lcm=4\u002F2*6=12（或 4*6\u002F2=12）。","print(lcm(4, 6))","cout \u003C\u003C lcm(4, 6);",{"choice":13985,"coding":14007},[13986,13991,13999],{"id":38,"stem":13987,"options":13988,"answer":9,"explanation":13990},"lcm(4,6) 等于？",[759,884,13989,883],"24","4*6\u002F2=12。",{"id":47,"stem":13992,"options":13993,"answer":9,"explanation":13998},"lcm 与 gcd 的关系是？",[13994,13995,13996,13997],"lcm=a+b-gcd","a*b=gcd*lcm","lcm=gcd","lcm=a*b+gcd","乘积等于 gcd 与 lcm 之积。",{"id":56,"stem":14000,"options":14001,"answer":9,"explanation":14006},"为防止溢出，lcm 应写成？",[14002,14003,14004,14005],"a*b\u002Fgcd","a\u002Fgcd*b","a+b","gcd*b","先除后乘更稳。",[14008],{"id":67,"title":13960,"prompt":14009,"inputFormat":143,"outputFormat":14010,"samples":14011,"starterPy":14014,"starterCpp":14015,"hint":14016,"referencePy":14017,"referenceCpp":14018},"输入两个正整数 a,b（1≤a,b≤10^9），输出它们的最小公倍数。","一行整数 lcm。",[14012],{"input":14013,"output":884},"4 6","def gcd(a, b):\n    while b: a, b = b, a % b\n    return a\ndef lcm(a, b):\n    # 用 gcd 计算\n    pass\na, b = map(int, input().split())\nprint(lcm(a, b))","#include \u003Ciostream>\nusing namespace std;\nlong long gcd(long long a, long long b){ while(b){long long t=a%b;a=b;b=t;} return a; }\nlong long lcm(long long a, long long b){ \u002F* 用 gcd *\u002F }\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Clcm(a,b); }","返回 a \u002F gcd(a,b) * b。","def gcd(a, b):\n    while b: a, b = b, a % b\n    return a\ndef lcm(a, b):\n    return a \u002F\u002F gcd(a, b) * b\na, b = map(int, input().split())\nprint(lcm(a, b))","#include \u003Ciostream>\nusing namespace std;\nlong long gcd(long long a, long long b){ while(b){long long t=a%b;a=b;b=t;} return a; }\nlong long lcm(long long a, long long b){ return a \u002F gcd(a,b) * b; }\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Clcm(a,b); }","lcm(a,b)=a×b\u002Fgcd(a,b)；实现时用 a\u002Fgcd*b（先除后乘）防溢出。多个数的 lcm 可两两合并计算。","24基础数论-A1-4","素数",{"id":14020,"title":14021,"level":4885,"module":13842,"seq":770,"goal":14023,"keywords":14024,"explanation":14028,"examples":14040,"exercises":14046,"summary":14082,"next":14083,"nextTitle":14084},"掌握判断素数的试除法：只需检查到 √n 的素数\u002F整数因子即可。",[14021,14025,14026,14027],"试除法","根号","因子",[14029,14034],{"title":14030,"content":14031,"codePy":14032,"codeCpp":14033},"1. 试除到 √n","若 n 有因子，必有一个 ≤ √n。所以判断 n 是否为素数，只需用 2..√n 的整数逐个试除，都除不尽才是素数；n\u003C2 不是素数。","import math\ndef is_prime(n):\n    if n \u003C 2: return False\n    for i in range(2, int(math.isqrt(n)) + 1):\n        if n % i == 0: return False\n    return True","bool is_prime(int n) {\n    if (n \u003C 2) return false;\n    for (int i = 2; i * i \u003C= n; i++)\n        if (n % i == 0) return false;\n    return true;\n}",{"title":14035,"content":14036,"codePy":14037,"codeCpp":14038,"tip":14039},"2. 复杂度与常见坑","试除法复杂度 O(√n)。注意 i*i\u003C=n 比 i\u003C=sqrt(n) 每次循环少算一次开方；n=2、3 在循环不执行时仍返回 true，需单独保证 n\u003C2 判否。","# 小优化：先判 2，再只试奇数\nif n % 2 == 0: return n == 2\nfor i in range(3, int(math.isqrt(n)) + 1, 2):\n    if n % i == 0: return False","\u002F\u002F i*i\u003C=n 避免重复开方","需要很多素数时，用\"埃氏筛\u002F欧拉筛\"一次性筛出区间素数更高效。",[14041],{"title":2038,"prompt":14042,"analysis":14043,"codePy":14044,"codeCpp":14045,"run":819},"判断 13 是否为素数。","√13≈3.6，试 2、3 都除不尽 → 素数。","print(is_prime(13))","cout \u003C\u003C is_prime(13);",{"choice":14047,"coding":14067},[14048,14053,14061],{"id":38,"stem":14049,"options":14050,"answer":63,"explanation":14052},"判断 n 是否为素数，试除到多少即可？",[3054,3053,14051,3055],"√n","因子必有一个 ≤√n。",{"id":47,"stem":14054,"options":14055,"answer":9,"explanation":14060},"关于素数判断，正确的是？",[14056,14057,14058,14059],"1 是素数","n\u003C2 不是素数","偶数都是素数","只需试到 n\u002F2","素数定义 n≥2。",{"id":56,"stem":14062,"options":14063,"answer":9,"explanation":14066},"is_prime(9) 的结果？",[14064,14065,567,891],"True（素）","False（非素）","9=3×3，非素。",[14068],{"id":67,"title":14069,"prompt":14070,"inputFormat":1456,"outputFormat":14071,"samples":14072,"starterPy":14077,"starterCpp":14078,"hint":14079,"referencePy":14080,"referenceCpp":14081},"素数判断","输入整数 n（1≤n≤10^9），若是素数输出 \"PRIME\"，否则输出 \"NOT\"。","一行 \"PRIME\" 或 \"NOT\"。",[14073,14075],{"input":12351,"output":14074},"PRIME",{"input":2707,"output":14076},"NOT","import math\ndef is_prime(n):\n    # 试除到 sqrt\n    pass\nn = int(input())\nprint(\"PRIME\" if is_prime(n) else \"NOT\")","#include \u003Ciostream>\nusing namespace std;\nbool is_prime(int n){\n    \u002F\u002F 试除到 sqrt\n}\nint main(){ int n; cin>>n; cout\u003C\u003C(is_prime(n)?\"PRIME\":\"NOT\"); }","n\u003C2 返回 false；循环 i*i\u003C=n 试除。","import math\ndef is_prime(n):\n    if n \u003C 2: return False\n    for i in range(2, int(math.isqrt(n)) + 1):\n        if n % i == 0: return False\n    return True\nn = int(input())\nprint(\"PRIME\" if is_prime(n) else \"NOT\")","#include \u003Ciostream>\nusing namespace std;\nbool is_prime(int n){\n    if (n \u003C 2) return false;\n    for (int i = 2; i * i \u003C= n; i++)\n        if (n % i == 0) return false;\n    return true;\n}\nint main(){ int n; cin>>n; cout\u003C\u003C(is_prime(n)?\"PRIME\":\"NOT\"); }","用试除法判素数：n\u003C2 非素；用 2..√n 的整数试除，都除不尽则为素数。复杂度 O(√n)，用 i*i\u003C=n 避免重复开方。","24基础数论-A1-5","质因数分解",{"id":14083,"title":14084,"level":4885,"module":13842,"seq":838,"goal":14086,"keywords":14087,"explanation":14091,"examples":14103,"exercises":14111,"summary":14144,"next":14145,"nextTitle":14146},"掌握质因数分解：把 n 拆成素数因子的乘积，通过不断除以最小因子实现。",[14084,14088,14089,14090],"质因子","试除","唯一分解",[14092,14097],{"title":14093,"content":14094,"codePy":14095,"codeCpp":14096},"1. 从小到大试除","从 d=2 开始，只要 d*d\u003C=n 且 n 能被 d 整除，就反复除以 d 并记录 d；然后 d 增加到下一个可能因子（先试 2，之后只试奇数）。最后若 n>1，说明剩下一个大质因子。","def factor(n):\n    res = []\n    d = 2\n    while d * d \u003C= n:\n        while n % d == 0:\n            res.append(d); n \u002F\u002F= d\n        d += 1 if d == 2 else 2\n    if n > 1: res.append(n)\n    return res","vector\u003Cint> factor(int n) {\n    vector\u003Cint> res;\n    for (int d = 2; d * d \u003C= n; d++) {\n        while (n % d == 0) { res.push_back(d); n \u002F= d; }\n    }\n    if (n > 1) res.push_back(n);\n    return res;\n}",{"title":14098,"content":14099,"codePy":14100,"codeCpp":14101,"tip":14102},"2. 为什么最后 n>1 是质因子","若分解到最后 n 还 >1，说明它已没有 ≤√n 的因子，那它自身必是素数（否则会有更小的因子已被除掉），故直接加入结果。这就是算术基本定理：分解唯一。","# 12 -> [2,2,3]; 7 -> [7]\nprint(factor(12))","\u002F\u002F 12 => 2,2,3 ; 7 => 7","分解后列表长度与乘积可校验：所有质因子相乘应等于原数。",[14104],{"title":14105,"prompt":14106,"analysis":14107,"codePy":14108,"codeCpp":14109,"run":14110},"例 1：分解 12","对 12 做质因数分解。","12÷2=6÷2=3，3 是质数→[2,2,3]。","print(factor(12))","\u002F\u002F 输出 2 2 3","[2, 2, 3]",{"choice":14112,"coding":14133},[14113,14121,14129],{"id":38,"stem":14114,"options":14115,"answer":9,"explanation":14120},"12 的质因数分解是？",[14116,14117,14118,14119],"[2,6]","[2,2,3]","[3,4]","[12]","12=2×2×3。",{"id":47,"stem":14122,"options":14123,"answer":9,"explanation":14128},"分解到最后 n>1 说明？",[14124,14125,14126,14127],"出错","n 本身是质因子","可忽略","n=1","无更小因子，n 为素数。",{"id":56,"stem":14130,"options":14131,"answer":63,"explanation":14132},"试除时 d 只需到？",[3054,3053,14051,3055],"d*d\u003C=n 即可。",[14134],{"id":67,"title":14084,"prompt":14135,"inputFormat":1456,"outputFormat":14136,"samples":14137,"starterPy":14141,"starterCpp":14142,"hint":14143,"referencePy":14141,"referenceCpp":14142},"输入整数 n（2≤n≤10^9），输出它的所有质因子（从小到大，可重复），空格分隔。","一行质因子，空格分隔。",[14138,14140],{"input":884,"output":14139},"2 2 3",{"input":885,"output":885},"n = int(input())\nres = []\nd = 2\nwhile d * d \u003C= n:\n    while n % d == 0:\n        res.append(d); n \u002F\u002F= d\n    d += 1 if d == 2 else 2\nif n > 1: res.append(n)\nprint(*res)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    int n; cin >> n;\n    for (int d = 2; d * d \u003C= n; d++)\n        while (n % d == 0) { cout \u003C\u003C d \u003C\u003C \" \"; n \u002F= d; }\n    if (n > 1) cout \u003C\u003C n;\n    return 0;\n}","从小到大试除，能除尽就记录并继续；最后 n>1 输出 n。","质因数分解：从 d=2 起，能整除就反复除以 d 并记录，再试下一个因子；最后 n>1 时 n 本身就是质因子。依据算术基本定理，分解唯一。","26-A1-1","数学公式替代循环",{"id":13839,"title":13840,"level":2505,"module":13842,"seq":9,"goal":14148,"keywords":14149,"explanation":14150,"examples":14162,"exercises":14169,"summary":14193,"next":14194,"nextTitle":14195},"掌握欧几里得算法（辗转相除法）求最大公约数 gcd：gcd(a,b)=gcd(b,a mod b)。",[13902,13903,2070,13904],[14151,14156],{"title":14152,"content":14153,"codePy":14154,"codeCpp":14155},"1. 递归关系","gcd(a,b) 当 b=0 时为 a，否则 gcd(b, a%b)。每次取模让数迅速变小。","def gcd(a, b):\n    return a if b == 0 else gcd(b, a % b)","int gcd(int a,int b){ return b?gcd(b,a%b):a; }",{"title":14157,"content":14158,"codePy":14159,"codeCpp":14160,"tip":14161},"2. 复杂度与 lcm","步数 O(log min(a,b))。最小公倍数 lcm(a,b)=a*b\u002F\u002Fgcd(a,b)，先除后乘防爆。","def lcm(a,b): return a\u002F\u002Fgcd(a,b)*b","long long lcm(long long a,long long b){ return a\u002Fgcd(a,b)*b; }","gcd 是数论几乎所有算法的基础。",[14163],{"title":14164,"prompt":14165,"analysis":14166,"codePy":14167,"codeCpp":14168,"run":624},"例 1：gcd","gcd(12, 18) = ?","gcd(18,12)=gcd(12,6)=gcd(6,0)=6。","print(gcd(12,18))","cout \u003C\u003C gcd(12,18);",{"choice":14170,"coding":14184},[14171,14175,14181],{"id":38,"stem":14172,"options":14173,"answer":9,"explanation":14174},"gcd(15,25) 是？",[892,290,291,3895],"都能被 5 整除。",{"id":47,"stem":14176,"options":14177,"answer":9,"explanation":14180},"gcd(a,0) 为？",[891,14178,892,14179],"a","未定义","递归终点。",{"id":56,"stem":14182,"options":14183,"answer":63,"explanation":13990},"lcm(4,6) 是？",[759,883,884,13989],[14185],{"id":67,"title":2070,"prompt":14186,"inputFormat":14187,"outputFormat":6275,"samples":14188,"starterPy":14190,"starterCpp":14191,"hint":14192,"referencePy":14190,"referenceCpp":14191},"输入 a,b（1≤a,b≤10^18），输出 gcd(a,b)。","一行 a b。",[14189],{"input":2076,"output":624},"def gcd(a,b):\n    return a if b==0 else gcd(b,a%b)\na,b=map(int,input().split())\nprint(gcd(a,b))","#include \u003Ciostream>\nusing namespace std;\nlong long gcd(long long a,long long b){ return b?gcd(b,a%b):a; }\nint main(){ long long a,b; cin>>a>>b; cout\u003C\u003Cgcd(a,b); }","递归或循环取模即可。","欧几里得算法：gcd(a,b)=gcd(b,a%b)，b=0 时返回 a；步数 O(log)，配合 a\u002Fgcd*b 求 lcm。","24基础数论-A2-2","埃氏筛",{"id":14194,"title":14195,"level":2505,"module":13842,"seq":63,"goal":14197,"keywords":14198,"explanation":14202,"examples":14212,"exercises":14220,"summary":14248,"next":14249,"nextTitle":14250},"掌握埃拉托斯特尼筛法（埃氏筛）在 O(n log log n) 内求出 [2,n] 内的所有素数。",[14199,14195,14200,14201],"素数筛","标记合数","isPrime",[14203,14208],{"title":10197,"content":14204,"codePy":14205,"codeCpp":14206,"tip":14207},"从小到大，若 i 是素数，则把 i 的倍数都标记为合数。","is_prime = [True] * (n + 1)\nis_prime[0] = is_prime[1] = False\nfor i in range(2, n + 1):\n    if is_prime[i]:\n        for j in range(i * i, n + 1, i):\n            is_prime[j] = False","vector\u003Cbool> p(n+1,true); p[0]=p[1]=false;\nfor(int i=2;i*i\u003C=n;i++) if(p[i]) for(int j=i*i;j\u003C=n;j+=i) p[j]=false;","从 i*i 开始标记即可（更小的倍数已被标过）。",{"title":6191,"content":14209,"codePy":14210,"codeCpp":14211},"总标记次数约为 n\u002F2+n\u002F3+... = n·log log n，远快于逐个试除。","# 适合 n ≤ 1e7 规模","\u002F\u002F 适合 1e7 内",[14213],{"title":14214,"prompt":14215,"analysis":14216,"codePy":14217,"codeCpp":14218,"run":14219},"例 1：素数","n=10 的素数有哪些？","2,3,5,7。","print([i for i in range(2,11) if is_prime[i]])","\u002F\u002F 2 3 5 7","[2, 3, 5, 7]",{"choice":14221,"coding":14239},[14222,14228,14234],{"id":38,"stem":14223,"options":14224,"answer":9,"explanation":14227},"埃氏筛标记的是？",[14021,14225,14226,877],"合数","偶数","倍数标记成合数。",{"id":47,"stem":14229,"options":14230,"answer":63,"explanation":14233},"从哪个数起标记 i 的倍数？",[9934,14231,14232,9989],"2*i","i*i","更小倍数已被标。",{"id":56,"stem":14235,"options":14236,"answer":63,"explanation":14238},"埃氏筛复杂度？",[2338,2564,14237,2339],"O(n log log n)","调和级数变体。",[14240],{"id":67,"title":14241,"prompt":14242,"inputFormat":1456,"outputFormat":6275,"samples":14243,"starterPy":14245,"starterCpp":14246,"hint":14247,"referencePy":14245,"referenceCpp":14246},"素数个数","输入 n（1≤n≤10^7），输出 [2,n] 中素数的个数。",[14244],{"input":883,"output":1663},"n=int(input())\nis_p=[True]*(n+1)\nif n>=1: is_p[0]=is_p[1]=False\nfor i in range(2,n+1):\n    if is_p[i]:\n        for j in range(i*i,n+1,i): is_p[j]=False\nprint(sum(is_p))","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cbool> p(n+1,true); if(n>=1)p[0]=p[1]=false; for(int i=2;i*i\u003C=n;i++) if(p[i]) for(int j=i*i;j\u003C=n;j+=i) p[j]=false; int c=0; for(int i=2;i\u003C=n;i++) c+=p[i]; cout\u003C\u003Cc; }","布尔数组标记，最后统计 true。","埃氏筛：从 2 起，每遇素数就把其倍数标记为合数（从 i*i 起），O(n log log n) 求出所有素数。","24基础数论-A2-3","线性筛",{"id":14249,"title":14250,"level":2505,"module":13842,"seq":703,"goal":14252,"keywords":14253,"explanation":14256,"examples":14268,"exercises":14274,"summary":14301,"next":14302,"nextTitle":14303},"理解线性筛（欧拉筛）：每个合数只被其最小质因子筛一次，达到 O(n) 求素数。",[14250,14254,14255,2338],"欧拉筛","最小质因子",[14257,14262],{"title":14258,"content":14259,"codePy":14260,"codeCpp":14261},"1. 核心技巧","维护质数列表 primes。对每个 i，用已有的素数 p 筛掉 i*p；一旦 p 整除 i 就停止（保证最小质因子）。","primes = []\nis_p = [True] * (n + 1)\nfor i in range(2, n + 1):\n    if is_p[i]: primes.append(i)\n    for p in primes:\n        if i * p > n: break\n        is_p[i * p] = False\n        if i % p == 0: break","\u002F\u002F 同上逻辑，primes 数组 + break",{"title":14263,"content":14264,"codePy":14265,"codeCpp":14266,"tip":14267},"2. 为什么线性","合数 i*p 被其最小质因子 p 筛掉且仅一次，总共 O(n) 次操作。","# 比埃氏筛更快、适合更大 n","\u002F\u002F 适合大规模","线性筛还能顺便记录每个数的最小质因子，用于唯一分解。",[14269],{"title":14270,"prompt":14271,"analysis":14272,"codePy":14273,"codeCpp":14218,"run":14219},"例 1：前几个素数","线性筛 n=10 得到？","primes=[2,3,5,7]。","print(primes)",{"choice":14275,"coding":14292},[14276,14283,14288],{"id":38,"stem":14277,"options":14278,"answer":9,"explanation":14282},"线性筛每个合数被筛几次？",[14279,14280,264,14281],"多次","一次（最小质因子）","两次","保证唯一性。",{"id":47,"stem":14284,"options":14285,"answer":9,"explanation":14287},"i % p == 0 时？",[8678,438,6553,14286],"加一","避免更大质因子筛。",{"id":56,"stem":14289,"options":14290,"answer":607,"explanation":14291},"线性筛复杂度？",[2338,2564,2339,1246],"线性时间。",[14293],{"id":67,"title":14294,"prompt":14295,"inputFormat":1456,"outputFormat":6275,"samples":14296,"starterPy":14298,"starterCpp":14299,"hint":14300,"referencePy":14298,"referenceCpp":14299},"线性筛素数","输入 n（1≤n≤10^7），用线性筛输出 [2,n] 的素数个数。",[14297],{"input":883,"output":1663},"n=int(input())\nprimes=[]; is_p=[True]*(n+1)\nfor i in range(2,n+1):\n    if is_p[i]: primes.append(i)\n    for p in primes:\n        if i*p>n: break\n        is_p[i*p]=False\n        if i%p==0: break\nprint(len(primes))","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> pr; vector\u003Cbool> ip(n+1,true); for(int i=2;i\u003C=n;i++){ if(ip[i]) pr.push_back(i); for(int p:pr){ if(i*p>n) break; ip[i*p]=false; if(i%p==0) break; } } cout\u003C\u003Cpr.size(); }","primes 表 + break 即可。","线性筛用质数列表 + \"i%p==0 即 break\" 保证每个合数仅被最小质因子筛一次，O(n) 求素数。","24基础数论-A2-4","唯一分解定理",{"id":14302,"title":14303,"level":2505,"module":13842,"seq":770,"goal":14305,"keywords":14306,"explanation":14310,"examples":14321,"exercises":14329,"summary":14357,"next":14358,"nextTitle":14359},"理解算术基本定理：任一大于 1 的整数可唯一分解为若干素数幂的乘积。",[14090,14307,14308,14309],"素因子","质因数","指数",[14311,14316],{"title":14312,"content":14313,"codePy":14314,"codeCpp":14315},"1. 定理内容","n = p1^e1 · p2^e2 · ... ，其中 pi 为素数且唯一（不计顺序）。","def factorize(n):\n    f = {}\n    d = 2\n    while d * d \u003C= n:\n        while n % d == 0:\n            f[d] = f.get(d, 0) + 1; n \u002F\u002F= d\n        d += 1\n    if n > 1: f[n] = f.get(n, 0) + 1\n    return f","\u002F\u002F 试除到 sqrt(n) 收集因子与指数",{"title":4654,"content":14317,"codePy":14318,"codeCpp":14319,"tip":14320},"求因数个数：(e1+1)(e2+1)...；求因数和等。结合线性筛记录最小质因子可更快分解。","def div_count(f):\n    c = 1\n    for e in f.values(): c *= (e + 1)\n    return c","\u002F\u002F 指数+1 连乘","分解是很多数论题的第一步。",[14322],{"title":14323,"prompt":14324,"analysis":14325,"codePy":14326,"codeCpp":14327,"run":14328},"例 1：分解","12 的素因子分解？","12=2^2·3^1。","print(factorize(12))","\u002F\u002F {2:2, 3:1}","{2: 2, 3: 1}",{"choice":14330,"coding":14348},[14331,14339,14343],{"id":38,"stem":14332,"options":14333,"answer":9,"explanation":14338},"12 的唯一分解？",[14334,14335,14336,14337],"2·6","2²·3","4·3","3²·2","素数幂乘积。",{"id":47,"stem":14340,"options":14341,"answer":63,"explanation":14342},"n=2^3·3^1 的因数个数？",[1663,624,148,884],"(3+1)(1+1)=8。",{"id":56,"stem":14344,"options":14345,"answer":9,"explanation":14347},"分解试除上界？",[3054,14346,3053,3055],"sqrt(n)","到根号即可。",[14349],{"id":67,"title":14350,"prompt":14351,"inputFormat":1456,"outputFormat":6275,"samples":14352,"starterPy":14354,"starterCpp":14355,"hint":14356,"referencePy":14354,"referenceCpp":14355},"因数个数","输入 n（1≤n≤10^12），输出 n 的正因数个数。",[14353],{"input":884,"output":624},"n=int(input())\nc=1; d=2\nwhile d*d\u003C=n:\n    e=0\n    while n%d==0: n\u002F\u002F=d; e+=1\n    if e: c*=e+1\n    d+=1\nif n>1: c*=2\nprint(c)","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long n; cin>>n; long long c=1,d=2; while(d*d\u003C=n){ int e=0; while(n%d==0){n\u002F=d;e++;} if(e) c*=(e+1); d++; } if(n>1) c*=2; cout\u003C\u003Cc; }","试除到 sqrt，指数+1 连乘。","唯一分解定理：n 可唯一写成素数幂乘积；试除到 sqrt(n) 收集指数，因数个数=(e1+1)(e2+1)...。","24基础数论-A2-5","快速幂",{"id":14358,"title":14359,"level":2505,"module":13842,"seq":838,"goal":14361,"keywords":14362,"explanation":14366,"examples":14378,"exercises":14385,"summary":14414,"next":14415,"nextTitle":14416},"掌握二进制快速幂，在 O(log b) 内计算 a^b（或 a^b mod m）。",[14359,14363,14364,14365],"二进制取幂","模幂","O(log b)",[14367,14372],{"title":14368,"content":14369,"codePy":14370,"codeCpp":14371},"1. 二分思想","把指数 b 二进制拆分：b 为偶数时 a^b=(a²)^(b\u002F2)；为奇数时多乘一个 a。","def qpow(a, b, m):\n    res = 1 % m\n    while b:\n        if b & 1: res = res * a % m\n        a = a * a % m\n        b >>= 1\n    return res","long long qpow(long long a,long long b,long long m){ long long r=1%m; while(b){ if(b&1) r=r*a%m; a=a*a%m; b>>=1; } return r; }",{"title":14373,"content":14374,"codePy":14375,"codeCpp":14376,"tip":14377},"2. 为什么快","每轮指数减半，共 O(log b) 次乘法；取模防止溢出，是模运算与逆元的基础。","# 例：qpow(2, 10, 1000) = 24","\u002F\u002F 常用于模幂","看到 a^b mod m 立刻想到快速幂。",[14379],{"title":14380,"prompt":14381,"analysis":14382,"codePy":14383,"codeCpp":14384,"run":13989},"例 1：取模","2^10 mod 1000 = ?","1024 mod 1000 = 24。","print(qpow(2,10,1000))","cout \u003C\u003C qpow(2,10,1000);",{"choice":14386,"coding":14404},[14387,14393,14400],{"id":38,"stem":14388,"options":14389,"answer":9,"explanation":14392},"快速幂复杂度？",[14390,14365,14391,1245],"O(b)","O(√b)","指数减半。",{"id":47,"stem":14394,"options":14395,"answer":9,"explanation":14399},"b 为奇数时？",[14396,14397,14398,505],"直接返回","res*=a 再减半","b+=1","多乘一个底数。",{"id":56,"stem":14401,"options":14402,"answer":607,"explanation":14403},"2^10 mod 1000 是？",[13989,2785,2548,891],"1024%1000。",[14405],{"id":67,"title":14364,"prompt":14406,"inputFormat":14407,"outputFormat":6275,"samples":14408,"starterPy":14411,"starterCpp":14412,"hint":14413,"referencePy":14411,"referenceCpp":14412},"输入 a,b,m（1≤a,b≤10^18, 1≤m≤10^18），输出 (a^b) mod m。","一行 a b m。",[14409],{"input":14410,"output":13989},"2 10 1000","a,b,m=map(int,input().split())\nr=1%m\nwhile b:\n    if b&1: r=r*a%m\n    a=a*a%m; b>>=1\nprint(r)","#include \u003Ciostream>\nusing namespace std;\nlong long qpow(long long a,long long b,long long m){ long long r=1%m; while(b){ if(b&1) r=r*a%m; a=a*a%m; b>>=1; } return r; }\nint main(){ long long a,b,m; cin>>a>>b>>m; cout\u003C\u003Cqpow(a,b,m); }","二进制拆分，边乘边取模。","二进制快速幂：把指数按位拆分，每轮 a 平方、b 右移，奇数位时累乘，O(log b) 求 a^b mod m。","24基础数论-A2-6","模运算",{"id":14415,"title":14416,"level":2505,"module":13842,"seq":936,"goal":14418,"keywords":14419,"explanation":14422,"examples":14434,"exercises":14441,"summary":14474,"next":14475,"nextTitle":14476},"掌握模运算基本律：加、减、乘可分别在模内运算，注意减法与除法（逆元）的处理。",[14416,14420,14421,1818],"同余","模加减乘",[14423,14428],{"title":14424,"content":14425,"codePy":14426,"codeCpp":14427},"1. 基本律","(a+b)%m = (a%m + b%m)%m；乘法同理。减法注意补正：((a-b)%m + m)%m。","def modsub(a,b,m): return (a - b) % m   # Python 自动非负\n# C++ 需 +m 再 %m","long long modsub(long long a,long long b,long long m){ return ((a-b)%m + m)%m; }",{"title":14429,"content":14430,"codePy":14431,"codeCpp":14432,"tip":14433},"2. 除与逆元","模意义下没有普通除法，(a\u002Fb)%m 应写成 a * b⁻¹ mod m，b⁻¹ 为 b 的模逆元（下一节）。","# a \u002F b (mod m) = a * inv(b, m) % m","\u002F\u002F a * inv(b,m) % m","中间结果随时取模，防止溢出。",[14435],{"title":14436,"prompt":14437,"analysis":14438,"codePy":14439,"codeCpp":14440,"run":1663},"例 1：负模","(5 - 8) mod 7 = ?","-3 mod 7 = 4。","print((5 - 8) % 7)","cout \u003C\u003C ((5-8)%7+7)%7;",{"choice":14442,"coding":14465},[14443,14450,14457],{"id":38,"stem":14444,"options":14445,"answer":607,"explanation":14449},"(a+b)%m 等于？",[14446,14004,14447,14448],"(a%m+b%m)%m","(a+b)\u002Fm","a%m+b","先模再运算。",{"id":47,"stem":14451,"options":14452,"answer":9,"explanation":14456},"C++ 中 (a-b)%m 为负时应？",[3560,14453,14454,14455],"加 m 再取模","取绝对值","乘 -1","保证非负。",{"id":56,"stem":14458,"options":14459,"answer":607,"explanation":14464},"模意义下除法应转为？",[14460,14461,14462,14463],"乘法逆元","减运算","加运算","开根号","用逆元替代。",[14466],{"id":67,"title":14416,"prompt":14467,"inputFormat":14407,"outputFormat":6275,"samples":14468,"starterPy":14471,"starterCpp":14472,"hint":14473,"referencePy":14471,"referenceCpp":14472},"输入 a,b,m（0≤a,b≤10^18, 1≤m≤10^18），输出 ((a+b)·a - b) mod m，保证结果非负。",[14469],{"input":14470,"output":891},"5 3 7","a,b,m=map(int,input().split())\nr=(a+b)%m\nr=(r*a)%m\nr=(r-b)%m\nprint(r)","#include \u003Ciostream>\nusing namespace std;\nint main(){ unsigned long long a,b,m; cin>>a>>b>>m; unsigned long long r=(a+b)%m; r=(r*a)%m; r=(r+m-b)%m; cout\u003C\u003Cr; }","每步取模，减法后 +m。","模运算满足加法、乘法同余律；减法需 (x+m)%m 保非负；除法要换成乘以模逆元。","24基础数论-A2-7","模逆元基础",{"id":14475,"title":14476,"level":2505,"module":13842,"seq":998,"goal":14478,"keywords":14479,"explanation":14484,"examples":14496,"exercises":14503,"summary":14535,"next":14536,"nextTitle":14537},"理解模逆元：在模 m 下，若 gcd(a,m)=1，则存在 a⁻¹ 使 a·a⁻¹ ≡ 1 (mod m)；当 m 为素数可用费马小定理。",[14480,14481,14482,14483],"模逆元","逆元","费马小定理","a^(m-2)",[14485,14490],{"title":14486,"content":14487,"codePy":14488,"codeCpp":14489},"1. 定义与存在性","a·x ≡ 1 (mod m) 的解 x 称为 a 的逆元，存在当且仅当 gcd(a,m)=1。","from math import gcd\nprint(gcd(3, 7) == 1)  # True，逆元存在","\u002F\u002F gcd(a,m)==1 才有逆元",{"title":14491,"content":14492,"codePy":14493,"codeCpp":14494,"tip":14495},"2. 费马小定理求逆","当 m 为素数且 a 不被 m 整除时，a^(m-2) ≡ a⁻¹ (mod m)，用快速幂求。","def inv(a, p): return qpow(a, p - 2, p)   # p 为素数\n# 例 inv(3,7)=5，因为 3*5=15≡1 mod 7","long long inv(long long a,long long p){ return qpow(a,p-2,p); }","若 m 非素数，可用扩展欧几里得求逆。",[14497],{"title":14498,"prompt":14499,"analysis":14500,"codePy":14501,"codeCpp":14502,"run":290},"例 1：逆元","模 7 下 3 的逆元是？","3·5=15≡1 mod 7，逆元为 5。","print(inv(3,7))","cout \u003C\u003C inv(3,7);",{"choice":14504,"coding":14525},[14505,14513,14521],{"id":38,"stem":14506,"options":14507,"answer":9,"explanation":14512},"a 在模 m 下逆元存在条件？",[14508,14509,14510,14511],"a>m","gcd(a,m)=1","a 为偶数","m 为偶数","互质才有逆元。",{"id":47,"stem":14514,"options":14515,"answer":9,"explanation":14520},"素数 p 下 a⁻¹ 用？",[14516,14517,14518,14519],"a^(p-1)","a^(p-2)","a^p","a^(p+1)","费马小定理。",{"id":56,"stem":14522,"options":14523,"answer":63,"explanation":14524},"模 7 下 3 的逆元？",[759,890,290,624],"3*5≡1。",[14526],{"id":67,"title":14527,"prompt":14528,"inputFormat":14529,"outputFormat":6275,"samples":14530,"starterPy":14532,"starterCpp":14533,"hint":14534,"referencePy":14532,"referenceCpp":14533},"求逆元","输入 a,p（2≤p≤10^9 且 p 为素数，1≤a\u003Cp），输出 a 在模 p 下的逆元。","一行 a p。",[14531],{"input":13719,"output":290},"def qpow(a,b,m):\n    r=1%m\n    while b:\n        if b&1: r=r*a%m\n        a=a*a%m; b>>=1\n    return r\na,p=map(int,input().split())\nprint(qpow(a,p-2,p))","#include \u003Ciostream>\nusing namespace std;\nlong long qpow(long long a,long long b,long long m){ long long r=1%m; while(b){ if(b&1) r=r*a%m; a=a*a%m; b>>=1; } return r; }\nint main(){ long long a,p; cin>>a>>p; cout\u003C\u003Cqpow(a,p-2,p); }","费马小定理：a^(p-2) mod p。","模逆元 a⁻¹ 满足 a·a⁻¹≡1(mod m)，存在当 gcd(a,m)=1；素数 p 下可由费马小定理用快速幂求 a^(p-2)。","25-A1-1","加法原理",{"id":14536,"title":14537,"level":2505,"module":14539,"seq":9,"goal":14540,"keywords":14541,"explanation":14544,"examples":14556,"exercises":14563,"summary":14592,"next":14593,"nextTitle":14594},"组合数学","理解加法原理：完成一件事有若干类互斥的方法，总方法数等于各类方法数之和。",[14537,14542,14543,4165],"分类计数","互斥",[14545,14550],{"title":14546,"content":14547,"codePy":14548,"codeCpp":14549},"1. 分类相加","若方法分为互不相容的若干类，每类分别有 n₁,n₂,... 种，则总数为 n₁+n₂+...。","total = ways_a + ways_b + ways_c   # 各类互斥","long long total = a + b + c;",{"title":14551,"content":14552,"codePy":14553,"codeCpp":14554,"tip":14555},"2. 与乘法区别","加法用于\"或\"（分类），乘法用于\"且\"（分步）。分清\"一步到位\"还是\"多步串联\"。","# 从北京到上海：飞机(3种) 或 高铁(5种) -> 3+5=8\nprint(3 + 5)","\u002F\u002F 3+5","先判断分类还是分步，再决定加还是乘。",[14557],{"title":14558,"prompt":14559,"analysis":14560,"codePy":14561,"codeCpp":14562,"run":885},"例 1：选择","从红、蓝球各选一个？不，从红(3个)或蓝(4个)中选 1 个，几种选法？","两类互斥，3+4=7。","print(3 + 4)","cout \u003C\u003C 3+4;",{"choice":14564,"coding":14583},[14565,14572,14577],{"id":38,"stem":14566,"options":14567,"answer":9,"explanation":14571},"加法原理用于？",[14568,14569,3117,14570],"分步","分类互斥","求平均","\"或\"的关系。",{"id":47,"stem":14573,"options":14574,"answer":607,"explanation":14576},"3 种飞机或 5 种火车去某地，共？",[148,291,759,14575],"35","3+5。",{"id":56,"stem":14578,"options":14579,"answer":9,"explanation":14582},"分类时必须？",[14580,14581,3952,3117],"重叠","互斥且不重不漏","不重不漏。",[14584],{"id":67,"title":14542,"prompt":14585,"inputFormat":14586,"outputFormat":6275,"samples":14587,"starterPy":14589,"starterCpp":14590,"hint":14591,"referencePy":14589,"referenceCpp":14590},"输入三类互不相交的方案数 a,b,c（0≤a,b,c≤10^9），输出总数。","一行 a b c。",[14588],{"input":3499,"output":884},"a,b,c=map(int,input().split())\nprint(a+b+c)","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long a,b,c; cin>>a>>b>>c; cout\u003C\u003Ca+b+c; }","直接相加。","加法原理：互斥的各类方法数直接相加（\"或\"）；先分清分类还是分步，再决定加还是乘。","25-A1-2","乘法原理",{"id":14593,"title":14594,"level":2505,"module":14539,"seq":63,"goal":14596,"keywords":14597,"explanation":14601,"examples":14613,"exercises":14621,"summary":14648,"next":14649,"nextTitle":14650},"理解乘法原理：完成一件事需若干步，每步方法数相乘得总数。",[14594,14598,14599,14600],"分步计数","相乘","独立",[14602,14607],{"title":14603,"content":14604,"codePy":14605,"codeCpp":14606},"1. 分步相乘","若一件事分 k 步，第 i 步有 nᵢ 种独立选择，则总数为 n₁×n₂×...×nₖ。","total = n1 * n2 * n3   # 各步独立","long long total = n1 * n2 * n3;",{"title":14608,"content":14609,"codePy":14610,"codeCpp":14611,"tip":14612},"2. 注意独立性","各步选择必须彼此独立；若后续步依赖前面选择，需按情况分类再乘。","# 密码：3 位，每位 0-9 -> 10^3 = 1000\nprint(10 ** 3)","\u002F\u002F 10*10*10","分步且每步独立 → 相乘。",[14614],{"title":14615,"prompt":14616,"analysis":14617,"codePy":14618,"codeCpp":14619,"run":14620},"例 1：密码","2 位字母密码（每位 26 种），共几种？","26×26=676。","print(26 * 26)","cout \u003C\u003C 26*26;","676",{"choice":14622,"coding":14638},[14623,14629,14633],{"id":38,"stem":14624,"options":14625,"answer":9,"explanation":14628},"乘法原理用于？",[13845,14626,4356,14627],"分步独立","求最大","\"且\"的关系。",{"id":47,"stem":14630,"options":14631,"answer":9,"explanation":14632},"3 步分别 2,3,4 种，共？",[2707,13989,884,624],"2*3*4。",{"id":56,"stem":14634,"options":14635,"answer":9,"explanation":14637},"各步必须？",[11966,14600,14636,3117],"递减","独立才相乘。",[14639],{"id":67,"title":14598,"prompt":14640,"inputFormat":14641,"outputFormat":6275,"samples":14642,"starterPy":14645,"starterCpp":14646,"hint":14647,"referencePy":14645,"referenceCpp":14646},"输入 k（1≤k≤12）和 k 个正整数（每步方案数，≤10^9），输出总数（模 10^9+7）。","第一行 k；第二行 k 个整数。",[14643],{"input":14644,"output":13989},"3\n2 3 4","k=int(input()); a=list(map(int,input().split())); M=10**9+7; r=1\nfor x in a: r=r*x%M\nprint(r)","#include \u003Ciostream>\nusing namespace std;\nint main(){ int k; cin>>k; const long long M=1000000007; long long r=1,x; while(k--){ cin>>x; r=r*x%M; } cout\u003C\u003Cr; }","连乘并取模防溢出。","乘法原理：分步且各步独立，方法数相乘（\"且\"）；注意每步独立性与大数取模。","25-A1-3","基础排列",{"id":14649,"title":14650,"level":2505,"module":14539,"seq":703,"goal":14652,"keywords":14653,"explanation":14658,"examples":14670,"exercises":14678,"summary":14705,"next":14706,"nextTitle":14707},"掌握排列数：从 n 个不同元素中取 k 个排成一列的方法数 P(n,k)=n!\u002F(n-k)!。",[14654,14655,14656,14657],"排列","排列数","P(n,k)","阶乘",[14659,14664],{"title":14660,"content":14661,"codePy":14662,"codeCpp":14663},"1. 公式","P(n,k)=n×(n-1)×...×(n-k+1)=n!\u002F(n-k)!。第 1 位 n 种，第 2 位 n-1 种……共 k 位。","def P(n, k):\n    res = 1\n    for i in range(k): res *= (n - i)\n    return res","long long P(int n,int k){ long long r=1; for(int i=0;i\u003Ck;i++) r*=n-i; return r; }",{"title":14665,"content":14666,"codePy":14667,"codeCpp":14668,"tip":14669},"2. 与组合区别","排列考虑顺序（ABC≠ACB），组合不计顺序。后续组合会除以 k!。","# P(5,3)=5*4*3=60","\u002F\u002F 60","有顺序 → 排列；无顺序 → 组合。",[14671],{"title":14672,"prompt":14673,"analysis":14674,"codePy":14675,"codeCpp":14676,"run":14677},"例 1：排列","从 5 人中选 3 人排座，几种？","P(5,3)=5×4×3=60。","print(P(5,3))","cout \u003C\u003C P(5,3);","60",{"choice":14679,"coding":14697},[14680,14685,14693],{"id":38,"stem":14681,"options":14682,"answer":9,"explanation":14684},"P(5,3) 等于？",[883,14677,1186,14683],"125","5*4*3。",{"id":47,"stem":14686,"options":14687,"answer":9,"explanation":14692},"排列与组合最大区别？",[14688,14689,14690,14691],"数量","是否考虑顺序","公式相同","都无序","排列有序。",{"id":56,"stem":14694,"options":14695,"answer":9,"explanation":14696},"P(n,0) 是？",[891,892,3054,14179],"空排列=1。",[14698],{"id":67,"title":14655,"prompt":14699,"inputFormat":7151,"outputFormat":6275,"samples":14700,"starterPy":14702,"starterCpp":14703,"hint":14704,"referencePy":14702,"referenceCpp":14703},"输入 n,k（0≤k≤n≤20），输出 P(n,k)。",[14701],{"input":13282,"output":14677},"n,k=map(int,input().split())\nr=1\nfor i in range(k): r*=n-i\nprint(r)","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; long long r=1; for(int i=0;i\u003Ck;i++) r*=n-i; cout\u003C\u003Cr; }","连续乘 k 项。","排列 P(n,k)=n!\u002F(n-k)!=n·(n-1)·…·(n-k+1)，强调顺序；从 5 取 3 排有 60 种。","25-A1-4","基础组合",{"id":14706,"title":14707,"level":2505,"module":14539,"seq":770,"goal":14709,"keywords":14710,"explanation":14713,"examples":14725,"exercises":14732,"summary":14760,"next":6177,"nextTitle":2627},"掌握组合数：从 n 个不同元素中取 k 个（不计顺序）的方法数 C(n,k)=n!\u002F(k!(n-k)!)，并知 C(n,k)=C(n,n-k)。",[1473,14711,8593,14712],"组合数","杨辉三角",[14714,14719],{"title":14715,"content":14716,"codePy":14717,"codeCpp":14718},"1. 公式与对称","C(n,k)=P(n,k)\u002Fk!；且 C(n,k)=C(n,n-k)，取较小 k 计算更快。","def C(n, k):\n    if k > n - k: k = n - k\n    res = 1\n    for i in range(k): res = res * (n - i) \u002F\u002F (i + 1)\n    return res","long long C(int n,int k){ if(k>n-k)k=n-k; long long r=1; for(int i=0;i\u003Ck;i++) r=r*(n-i)\u002F(i+1); return r; }",{"title":14720,"content":14721,"codePy":14722,"codeCpp":14723,"tip":14724},"2. 递推（杨辉三角）","C(n,k)=C(n-1,k-1)+C(n-1,k)；可递推\u002FDP 求多组合数值。","C = [[0]*(n+1) for _ in range(n+1)]\nfor i in range(n+1):\n    C[i][0]=C[i][i]=1\n    for j in range(1,i): C[i][j]=C[i-1][j-1]+C[i-1][j]","\u002F\u002F C[i][j]=C[i-1][j-1]+C[i-1][j]","C(n,k) 用于\"选而不排\"的计数。",[14726],{"title":14727,"prompt":14728,"analysis":14729,"codePy":14730,"codeCpp":14731,"run":883},"例 1：组合","从 5 人中选 3 人组队（不计顺序），几种？","C(5,3)=C(5,2)=10。","print(C(5,3))","cout \u003C\u003C C(5,3);",{"choice":14733,"coding":14752},[14734,14738,14744],{"id":38,"stem":14735,"options":14736,"answer":9,"explanation":14737},"C(5,3) 等于？",[624,883,1186,14677],"选而不排。",{"id":47,"stem":14739,"options":14740,"answer":607,"explanation":14743},"C(n,k) 与 C(n,n-k) 关系？",[13941,14741,14742,9929],"互为倒数","差 1","对称性。",{"id":56,"stem":14745,"options":14746,"answer":9,"explanation":14751},"杨辉三角递推式？",[14747,14748,14749,14750],"C=C+1","C(n,k)=C(n-1,k-1)+C(n-1,k)","C=C*2","C=k*n","经典递推。",[14753],{"id":67,"title":14711,"prompt":14754,"inputFormat":7151,"outputFormat":6275,"samples":14755,"starterPy":14757,"starterCpp":14758,"hint":14759,"referencePy":14757,"referenceCpp":14758},"输入 n,k（0≤k≤n≤30），输出 C(n,k)。",[14756],{"input":13282,"output":883},"n,k=map(int,input().split())\nif k>n-k: k=n-k\nr=1\nfor i in range(k): r=r*(n-i)\u002F\u002F(i+1)\nprint(r)","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; if(k>n-k)k=n-k; long long r=1; for(int i=0;i\u003Ck;i++) r=r*(n-i)\u002F(i+1); cout\u003C\u003Cr; }","取较小 k，逐项乘并整除。","组合 C(n,k)=n!\u002F(k!(n-k)!)=P(n,k)\u002Fk!，不计顺序；具对称性 C(n,k)=C(n,n-k)，可由杨辉三角递推。",{"id":14145,"title":14146,"level":4885,"module":14762,"seq":9,"goal":14763,"keywords":14764,"explanation":14767,"examples":14779,"exercises":14787,"summary":14822,"next":14823,"nextTitle":14824},"数学优化","学会用闭式公式替代\"累加\u002F累乘\"循环，把 O(n) 计算降到 O(1)，避免超时与手误。",[14765,1245,14766,12594],"公式替代","等差数列求和",[14768,14773],{"title":14769,"content":14770,"codePy":14771,"codeCpp":14772},"1. 1+2+...+n 用公式","求前 n 个正整数和，不必循环累加：直接用 n*(n+1)\u002F2。循环在 n 很大时会超时，公式一步到位。","def sum_n(n):\n    return n * (n + 1) \u002F\u002F 2   # 公式 O(1)\n# 而不是 for i in range(1, n+1): s += i","long long sum_n(long long n) {\n    return n * (n + 1) \u002F 2;   \u002F\u002F 公式\n}",{"title":14774,"content":14775,"codePy":14776,"codeCpp":14777,"tip":14778},"2. 先想\"有没有公式\"，再写循环","遇到\"求和、计数、平方和、组合数\"等问题，先判断是否存在数学闭式或规律；用公式既快又不易错。注意整数溢出：先转 long long 再乘。","# 1^2+2^2+...+n^2 = n(n+1)(2n+1)\u002F6\ndef sq_sum(n):\n    return n * (n + 1) * (2 * n + 1) \u002F\u002F 6","\u002F\u002F 平方和公式：n(n+1)(2n+1)\u002F6","竞赛里\"n 很大但只求和\"几乎必有公式，先推公式再写。",[14780],{"title":14781,"prompt":14782,"analysis":14783,"codePy":14784,"codeCpp":14785,"run":14786},"例 1：公式求和","求 1+2+...+100。","100*101\u002F2 = 5050。","print(100 * 101 \u002F\u002F 2)","cout \u003C\u003C 100LL * 101 \u002F 2;","5050",{"choice":14788,"coding":14811},[14789,14796,14804],{"id":38,"stem":14790,"options":14791,"answer":9,"explanation":14795},"1+2+...+n 的公式是？",[14792,14793,14794,9861],"n²","n(n+1)\u002F2","n(n-1)\u002F2","前 n 项和公式。",{"id":47,"stem":14797,"options":14798,"answer":9,"explanation":14803},"用公式替代循环的主要好处是？",[14799,14800,14801,14802],"代码长","O(1) 更快更稳","更费内存","更易错","常数时间、避免超时。",{"id":56,"stem":14805,"options":14806,"answer":9,"explanation":14810},"n*(n+1)\u002F2 计算时应注意？",[14807,14808,14809,5515],"用 float","先转 long long 防溢出","不用括号","大 n 相乘防溢出。",[14812],{"id":67,"title":14813,"prompt":14814,"inputFormat":1456,"outputFormat":14815,"samples":14816,"starterPy":14818,"starterCpp":14819,"hint":14820,"referencePy":1838,"referenceCpp":14821},"公式求和","输入 n（1≤n≤10^12），用公式输出 1+2+...+n。","一行和（可能很大）。",[14817],{"input":816,"output":14786},"n = int(input())\n# 公式 O(1)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    long long n; cin >> n;\n    \u002F\u002F 公式\n    return 0;\n}","用 n*(n+1)\u002F2，先转 long long。","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    long long n; cin >> n;\n    cout \u003C\u003C n * (n + 1) \u002F 2;\n    return 0;\n}","遇到求和\u002F计数先想闭式公式（如 1+..+n = n(n+1)\u002F2、平方和 n(n+1)(2n+1)\u002F6），用 O(1) 公式替代循环，又快又不易错，注意先转 long long 防溢出。","26-A1-2","等差数列",{"id":14823,"title":14824,"level":4885,"module":14762,"seq":63,"goal":14826,"keywords":14827,"explanation":14830,"examples":14842,"exercises":14849,"summary":14884,"next":14885,"nextTitle":14886},"理解等差数列：相邻项差为常数 d，掌握通项与求和公式，并能在题目中识别等差结构。",[14824,14828,14829,4165],"公差","通项",[14831,14836],{"title":14832,"content":14833,"codePy":14834,"codeCpp":14835},"1. 通项与求和公式","首项 a1、公差 d 的等差数列：第 n 项 an = a1 + (n-1)d；前 n 项和 Sn = n*(a1+an)\u002F2 = n*[2a1+(n-1)d]\u002F2。","def a_n(a1, d, n): return a1 + (n - 1) * d\ndef s_n(a1, d, n): return n * (2 * a1 + (n - 1) * d) \u002F\u002F 2","long long a_n(long long a1, long long d, long long n){ return a1 + (n-1)*d; }\nlong long s_n(long long a1, long long d, long long n){ return n*(2*a1+(n-1)*d)\u002F2; }",{"title":14837,"content":14838,"codePy":14839,"codeCpp":14840,"tip":14841},"2. 在题目中识别等差","当\"每层 +固定值\"\"第 i 个比第 i-1 个多 k\"时就是等差；先求项数 n 再用公式，避免逐项加。注意项数 n 可能要由末项反推：n=(an-a1)\u002Fd+1。","# 从第 3 项起每项多 2，求前 10 项和\ns = s_n(3, 2, 10)","\u002F\u002F 先定 a1,d,n 再代入公式","题目给的是\"范围\u002F区间\"时，先算出项数 n，再算和。",[14843],{"title":14844,"prompt":14845,"analysis":14846,"codePy":14847,"codeCpp":14848,"run":1460},"例 1：等差求和","首项 3、公差 2 的前 10 项和是多少？","S10 = 10*(2*3+9*2)\u002F2 = 10*(6+18)\u002F2 = 120。","print(s_n(3, 2, 10))","cout \u003C\u003C s_n(3, 2, 10);",{"choice":14850,"coding":14871},[14851,14859,14867],{"id":38,"stem":14852,"options":14853,"answer":9,"explanation":14858},"等差数列第 n 项 an = ?",[14854,14855,14856,14857],"a1+n*d","a1+(n-1)*d","a1*d","a1-n","首项后加 n-1 个公差。",{"id":47,"stem":14860,"options":14861,"answer":9,"explanation":14866},"前 n 项和 Sn 公式？",[14862,14863,14864,14865],"n*a1","n*(a1+an)\u002F2","n*d","(a1+an)*d","平均项乘项数。",{"id":56,"stem":14868,"options":14869,"answer":607,"explanation":14870},"已知首项3公差2，第4项是？",[2707,7594,148,290],"3+3*2=9。",[14872],{"id":67,"title":14873,"prompt":14874,"inputFormat":14875,"outputFormat":2898,"samples":14876,"starterPy":14879,"starterCpp":14880,"hint":14881,"referencePy":14882,"referenceCpp":14883},"等差求和","输入首项 a1、公差 d 和项数 n（均 ≤10^9），输出前 n 项和。","一行三个整数 a1 d n。",[14877],{"input":14878,"output":1460},"3 2 10","a1, d, n = map(int, input().split())\n# 等差求和公式\n","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    long long a1, d, n; cin >> a1 >> d >> n;\n    \u002F\u002F 公式\n    return 0;\n}","S = n*(2*a1+(n-1)*d)\u002F2，用 long long。","a1, d, n = map(int, input().split())\nprint(n * (2 * a1 + (n - 1) * d) \u002F\u002F 2)","#include \u003Ciostream>\nusing namespace std;\nint main(){\n    long long a1, d, n; cin >> a1 >> d >> n;\n    cout \u003C\u003C n * (2 * a1 + (n - 1) * d) \u002F 2;\n    return 0;\n}","等差数列公差 d 恒定：通项 an=a1+(n-1)d，前 n 项和 Sn=n(a1+an)\u002F2=n(2a1+(n-1)d)\u002F2。先识别等差结构、确定项数 n，再用公式 O(1) 求和。","26-A1-3","基础几何计算",{"id":14885,"title":14886,"level":4885,"module":14762,"seq":703,"goal":14888,"keywords":14889,"explanation":14893,"examples":14905,"exercises":14913,"summary":14952,"next":2504,"nextTitle":1246},"掌握常见平面图形的面积\u002F周长\u002F体积公式（矩形、三角形、圆等），并能在程序中正确计算与取整。",[14890,14891,14892,1888],"几何公式","面积","周长",[14894,14899],{"title":14895,"content":14896,"codePy":14897,"codeCpp":14898},"1. 常用公式","矩形面积=长×宽，周长=2(长+宽)；三角形面积=底×高\u002F2；圆面积=π×r²，周长=2πr。用 π≈3.141592653589793（或常量 M_PI）。","import math\nrect = w * h\ntri = b * h \u002F 2.0\ncircle = math.pi * r * r","#include \u003Ccmath>\ndouble rect = w * h;\ndouble tri = b * h \u002F 2.0;\ndouble circle = M_PI * r * r;",{"title":14900,"content":14901,"codePy":14902,"codeCpp":14903,"tip":14904},"2. 精度与取整","几何题常要求保留小数（用 printf(\"%.2f\") 或 f\"{x:.2f}\"）或向下取整。涉及除法先转浮点；判断相等用 EPS 容忍误差，不要直接 ==。","print(f\"{circle:.2f}\")","printf(\"%.2f\", circle);","用常量 π 而非手写的 3.14，精度更高；输出按要求保留位数。",[14906],{"title":14907,"prompt":14908,"analysis":14909,"codePy":14910,"codeCpp":14911,"run":14912},"例 1：圆面积","输入半径 r，输出圆面积（保留 2 位小数）。","area = π r²，用常量 π。","import math\nr = float(input())\nprint(f\"{math.pi * r * r:.2f}\")","double r; cin >> r;\nprintf(\"%.2f\", M_PI * r * r);","输入 2 -> 12.57",{"choice":14914,"coding":14938},[14915,14923,14931],{"id":38,"stem":14916,"options":14917,"answer":9,"explanation":14922},"三角形面积公式？",[14918,14919,14920,14921],"底×高","底×高\u002F2","底+高","底²","底乘高除以 2。",{"id":47,"stem":14924,"options":14925,"answer":63,"explanation":14930},"圆面积用？",[14926,14927,14928,14929],"πd","πr","πr²","2πr","π 乘半径平方。",{"id":56,"stem":14932,"options":14933,"answer":9,"explanation":14937},"几何题输出小数应注意？",[14934,14935,14936,5515],"直接 == 比较","用 EPS 容差、按要求保留","都用 int","浮点用 EPS，按要求保留位数。",[14939],{"id":67,"title":14940,"prompt":14941,"inputFormat":14942,"outputFormat":14943,"samples":14944,"starterPy":14947,"starterCpp":14948,"hint":14949,"referencePy":14950,"referenceCpp":14951},"几何计算","输入长方形的长 w 和宽 h（1≤w,h≤1000，实数），输出面积与周长（均保留 2 位小数，空格分隔）。","一行两个实数 w h。","一行：面积 周长（保留 2 位）。",[14945],{"input":1256,"output":14946},"12.00 14.00","w, h = map(float, input().split())\n# 面积与周长\n","#include \u003Ciostream>\n#include \u003Ciomanip>\n#include \u003Ccmath>\nusing namespace std;\nint main(){\n    double w, h; cin >> w >> h;\n    \u002F\u002F 面积、周长\n    return 0;\n}","面积=w*h，周长=2*(w+h)，用 fixed\u003C\u003Csetprecision(2)。","w, h = map(float, input().split())\nprint(f\"{w*h:.2f} {2*(w+h):.2f}\")","#include \u003Ciostream>\n#include \u003Ciomanip>\n#include \u003Ccmath>\nusing namespace std;\nint main(){\n    double w, h; cin >> w >> h;\n    cout \u003C\u003C fixed \u003C\u003C setprecision(2) \u003C\u003C w*h \u003C\u003C \" \" \u003C\u003C 2*(w+h);\n    return 0;\n}","常用几何公式：矩形面积=长×宽、周长=2(长+宽)；三角形=底×高\u002F2；圆=πr²。用常量 π，输出按要求保留小数，浮点比较用 EPS 容差。",{"id":12428,"title":12429,"level":6178,"module":14954,"seq":9,"goal":14955,"keywords":14956,"explanation":14957,"examples":14967,"exercises":14973,"summary":15003,"next":15004,"nextTitle":15005},"数学与数论进阶","掌握快速幂：用二进制拆分指数，把 a^b 在 O(log b) 内算出，并支持模运算防止溢出。",[14359,14364,1601,1246],[14958,14962],{"title":13735,"content":14959,"codePy":14960,"codeCpp":14961},"b 二进制，a^(2k)=(a^k)^2；遇 b 的当前位为 1 就把当前底数乘入答案，底数不断平方。","def powmod(a,b,mod):\n    res=1; a%=mod\n    while b:\n        if b&1: res=res*a%mod\n        a=a*a%mod; b>>=1\n    return res","\u002F\u002F O(log b)",{"title":14963,"content":14964,"codePy":14965,"codeCpp":14966},"2. 为何模","中间平方极易溢出，每一步取模保持数值可控（模运算下乘法可分配）。","print(\"每步取模防溢出\")","\u002F\u002F 注意模为合数时费马不一定适用",[14968],{"title":14969,"prompt":14970,"analysis":14382,"codePy":14971,"codeCpp":14972,"run":13989},"例 1：幂","2^10 mod 1000？","print(24)","\u002F\u002F 24",{"choice":14974,"coding":14993},[14975,14979,14986],{"id":38,"stem":14388,"options":14976,"answer":9,"explanation":14978},[14390,14365,1245,14977],"O(b²)","二进制拆分。",{"id":47,"stem":14980,"options":14981,"answer":9,"explanation":14985},"b 当前位为 1 时？",[505,14982,14983,14984],"答案乘底数","平方","除以2","累乘当前底数。",{"id":56,"stem":14987,"options":14988,"answer":9,"explanation":14992},"模幂每步？",[14989,14990,14991,1568],"不取模","取模防溢出","平方不取模","保持可控。",[14994],{"id":67,"title":14359,"prompt":14995,"inputFormat":14996,"outputFormat":6275,"samples":14997,"starterPy":14999,"starterCpp":15000,"hint":15001,"referencePy":15002,"referenceCpp":15000},"输入 a,b,mod，输出 a^b mod mod。","一行 a b mod。",[14998],{"input":14410,"output":13989},"a,b,mod=map(int,input().split())\nres=1; a%=mod\nwhile b:\n    if b&1: res=res*a%mod\n    a=a*a%mod; b>>=1\nprint(res)\n","#include \u003Ciostream>\nusing namespace std;\nlong long powmod(long long a,long long b,long long mod){ long long r=1; a%=mod; while(b){ if(b&1) r=r*a%mod; a=a*a%mod; b>>=1; } return r; }\nint main(){ long long a,b,m; cin>>a>>b>>m; cout\u003C\u003Cpowmod(a,b,m); }","二进制位驱动，平方+取模。","a,b,mod=map(int,input().split())\nres=1; a%=mod\nwhile b:\n    if b&1: res=res*a%mod\n    a=a*a%mod; b>>=1\nprint(res)","快速幂按指数二进制拆分：当前位为 1 就把底数乘入答案，底数不断平方、指数右移，O(log b) 完成；模幂每步取模防溢出。","26-A2-2","欧几里得与扩展欧几里得",{"id":15004,"title":15005,"level":6178,"module":14954,"seq":63,"goal":15007,"keywords":15008,"explanation":15012,"examples":15024,"exercises":15028,"summary":15056,"next":15057,"nextTitle":15058},"掌握 GCD（辗转相除）与扩展欧几里得（求 ax+by=gcd(a,b) 的一组整数解），用于同余与逆元。",[15009,13903,15010,15011],"GCD","exgcd","贝祖",[15013,15018],{"title":15014,"content":15015,"codePy":15016,"codeCpp":15017},"1. GCD","gcd(a,b)=gcd(b, a mod b)，直到 b=0。","def gcd(a,b):\n    while b: a,b=b,a%b\n    return a","\u002F\u002F 辗转相除",{"title":15019,"content":15020,"codePy":15021,"codeCpp":15022,"tip":15023},"2. exgcd","递归：gcd(a,b) 回溯时由 gcd(b, a%b) 的解推出 x,y 满足 ax+by=g。用于求逆元、解线性同余。","def exgcd(a,b):\n    if b==0: return a,1,0\n    g,x,y=exgcd(b,a%b)\n    return g,y,x-(a\u002F\u002Fb)*y","\u002F\u002F 返回 g,x,y 使 ax+by=g","tip: exgcd 是数论工具基石。",[15025],{"title":14164,"prompt":15026,"analysis":15027,"codePy":4786,"codeCpp":4432,"run":624},"gcd(12,18)？","6。",{"choice":15029,"coding":15044},[15030,15035,15042],{"id":38,"stem":15031,"options":15032,"answer":9,"explanation":15034},"gcd 用？",[15033,13903,3117,1542],"减法","gcd(b,a%b)。",{"id":47,"stem":15036,"options":15037,"answer":9,"explanation":15041},"exgcd 求的是？",[15038,15039,6839,15040],"质数","ax+by=gcd 的解","逆","贝祖等式。",{"id":56,"stem":15026,"options":15043,"answer":9,"explanation":15027},[890,624,759,884],[15045],{"id":67,"title":15046,"prompt":15047,"inputFormat":14187,"outputFormat":15048,"samples":15049,"starterPy":15052,"starterCpp":15053,"hint":15054,"referencePy":15055,"referenceCpp":15053},"gcd 与 exgcd","输入 a,b，输出 gcd(a,b) 与一组满足 ax+by=gcd 的整数解 (x,y)。","一行：g x y。",[15050],{"input":2076,"output":15051},"6 -1 1","def exgcd(a,b):\n    if b==0: return a,1,0\n    g,x,y=exgcd(b,a%b)\n    return g,y,x-(a\u002F\u002Fb)*y\na,b=map(int,input().split())\ng,x,y=exgcd(a,b)\nprint(g,x,y)\n","#include \u003Ciostream>\nusing namespace std;\nlong long exgcd(long long a,long long b,long long& x,long long& y){ if(b==0){ x=1; y=0; return a; } long long g=exgcd(b,a%b,y,x); y-=(a\u002Fb)*x; return g; }\nint main(){ long long a,b,x,y; cin>>a>>b; long long g=exgcd(a,b,x,y); cout\u003C\u003Cg\u003C\u003C\" \"\u003C\u003Cx\u003C\u003C\" \"\u003C\u003Cy; }","回溯时 x,y 互换并修正。","def exgcd(a,b):\n    if b==0: return a,1,0\n    g,x,y=exgcd(b,a%b)\n    return g,y,x-(a\u002F\u002Fb)*y\na,b=map(int,input().split())\ng,x,y=exgcd(a,b)\nprint(g,x,y)","GCD 用辗转相除 gcd(a,b)=gcd(b,a%b)；扩展欧几里得回溯求出满足 ax+by=gcd 的整数解 (x,y)，是求逆元、解线性同余的基石。","26-A2-3","素数筛与质因数分解",{"id":15057,"title":15058,"level":6178,"module":14954,"seq":703,"goal":15060,"keywords":15061,"explanation":15062,"examples":15073,"exercises":15079,"summary":15104,"next":15105,"nextTitle":15106},"掌握埃氏筛与欧拉筛（线性筛）求区间内素数，并用以做质因数分解，支持数论类题目。",[14199,14195,14254,14308],[15063,15068],{"title":15064,"content":15065,"codePy":15066,"codeCpp":15067},"1. 埃氏筛","从 2 起，每个素数划掉其倍数；复杂度 O(n log log n)。","def sieve(n):\n    isp=[True]*(n+1); isp[0]=isp[1]=False\n    for i in range(2,n+1):\n        if isp[i]:\n            for j in range(i*i,n+1,i): isp[j]=False\n    return isp","\u002F\u002F 划掉倍数",{"title":15069,"content":15070,"codePy":15071,"codeCpp":15072},"2. 欧拉筛","每个合数被其最小质因子划掉一次，O(n) 且每数只删一次。质因数分解：不断除以素因子。","def factor(x):\n    res=[]\n    for p in primes:\n        if p*p>x: break\n        while x%p==0: res.append(p); x\u002F\u002F=p\n    if x>1: res.append(x)\n    return res","\u002F\u002F 线性筛 O(n)",[15074],{"title":14323,"prompt":15075,"analysis":15076,"codePy":15077,"codeCpp":15078,"run":14139},"12 的质因数？","2,2,3。","print([2,2,3])","\u002F\u002F 2 2 3",{"choice":15080,"coding":15095},[15081,15084,15090],{"id":38,"stem":14235,"options":15082,"answer":9,"explanation":15083},[2338,14237,2339,1245],"划倍数。",{"id":47,"stem":15085,"options":15086,"answer":9,"explanation":15089},"欧拉筛特点？",[9178,15087,1568,15088],"每个合数仅删一次 O(n)","需排序","最小质因子删。",{"id":56,"stem":15091,"options":15092,"answer":9,"explanation":15094},"12 质因数含？",[1663,15093,624,892],"2,3","2×2×3。",[15096],{"id":67,"title":14241,"prompt":15097,"inputFormat":1456,"outputFormat":6275,"samples":15098,"starterPy":15100,"starterCpp":15101,"hint":15102,"referencePy":15103,"referenceCpp":15101},"输出 1..n 中素数个数。",[15099],{"input":883,"output":1663},"n=int(input()); isp=[True]*(n+1); isp[0]=isp[1]=False\nfor i in range(2,n+1):\n    if isp[i]:\n        for j in range(i*i,n+1,i): isp[j]=False\nprint(sum(isp))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cbool> isp(n+1,1); isp[0]=isp[1]=0; for(int i=2;i\u003C=n;i++) if(isp[i]) for(int j=i*i;j\u003C=n;j+=i) isp[j]=0; int c=0; for(int i=2;i\u003C=n;i++) c+=isp[i]; cout\u003C\u003Cc; }","埃氏筛划倍数。","n=int(input()); isp=[True]*(n+1); isp[0]=isp[1]=False\nfor i in range(2,n+1):\n    if isp[i]:\n        for j in range(i*i,n+1,i): isp[j]=False\nprint(sum(isp))","埃氏筛划掉每个素数的倍数（O(n log log n)）；欧拉筛让每个合数仅被最小质因子划掉一次（O(n)）。素因子表可用于质因数分解，支撑数论题。","26-A2-4","逆元与同余",{"id":15105,"title":15106,"level":6178,"module":14954,"seq":770,"goal":15108,"keywords":15109,"explanation":15111,"examples":15123,"exercises":15127,"summary":15157,"next":6348,"nextTitle":6349},"理解乘法逆元：在模 p（素数）下，a 的逆元满足 a·a⁻¹≡1，可用费马小定理（a^(p-2)）或 exgcd 求得，用于“除法取模”。",[14481,14420,14482,15110],"模除",[15112,15117],{"title":15113,"content":15114,"codePy":15115,"codeCpp":15116},"1. 为何需要","模运算没有除法：(x\u002Fy) mod p 要写成 x·y⁻¹ mod p，其中 y⁻¹ 是 y 模 p 的逆元。","print(\"(x\u002Fy)%p = x * y^-1 % p\")","\u002F\u002F 逆元让“除法”合法",{"title":15118,"content":15119,"codePy":15120,"codeCpp":15121,"tip":15122},"2. 求逆元","p 为素数且 gcd(y,p)=1：y⁻¹≡y^(p-2) (mod p)（费马小定理）；或用 exgcd 解 y·t≡1。","def inv(y,p): return pow(y,p-2,p)","\u002F\u002F 快速幂求逆","tip: 模数必须是与 y 互素的素数才存在逆元。",[15124],{"title":14498,"prompt":14522,"analysis":15125,"codePy":15126,"codeCpp":7706,"run":290},"3×5=15≡1 mod7，故 5。","print(pow(3,5,7))",{"choice":15128,"coding":15149},[15129,15137,15143],{"id":38,"stem":15130,"options":15131,"answer":9,"explanation":15136},"模 p 下 a 的逆元满足？",[15132,15133,15134,15135],"a·a⁻¹≡0","a·a⁻¹≡1","a⁻¹≡0","a≡a⁻¹","乘积模 p 为 1。",{"id":47,"stem":15138,"options":15139,"answer":9,"explanation":15142},"p 为素数时求逆元用？",[13902,15140,15141,7747],"费马小定理 a^(p-2)","筛","快速幂。",{"id":56,"stem":15144,"options":15145,"answer":9,"explanation":15148},"(x\u002Fy)%p 怎么算？",[15146,15147,3560,11849],"直接除","x·y⁻¹%p","乘逆元。",[15150],{"id":67,"title":14480,"prompt":15151,"inputFormat":14529,"outputFormat":6275,"samples":15152,"starterPy":15154,"starterCpp":15155,"hint":14534,"referencePy":15156,"referenceCpp":15155},"输入 a,p（p 为素数且 gcd(a,p)=1），输出 a 模 p 的乘法逆元。",[15153],{"input":13719,"output":290},"a,p=map(int,input().split())\nprint(pow(a,p-2,p))\n","#include \u003Ciostream>\nusing namespace std;\nlong long powmod(long long a,long long b,long long mod){ long long r=1; a%=mod; while(b){ if(b&1) r=r*a%mod; a=a*a%mod; b>>=1; } return r; }\nint main(){ long long a,p; cin>>a>>p; cout\u003C\u003Cpowmod(a,p-2,p); }","a,p=map(int,input().split())\nprint(pow(a,p-2,p))","模 p（素数）下 a 的乘法逆元满足 a·a⁻¹≡1，可用费马小定理 a^(p-2) mod p（快速幂）或 exgcd 求得；它把“除法取模”转化为乘逆元。",{"id":4408,"title":4409,"level":3510,"module":15159,"seq":9,"goal":15160,"keywords":15161,"explanation":15163,"examples":15175,"exercises":15183,"summary":15221,"next":15222,"nextTitle":15223},"字符串算法","掌握字符串遍历：逐字符处理字符串，能按位置取字符、按条件筛选与拼接。",[775,3651,15162],"索引",[15164,15170],{"title":15165,"content":15166,"codePy":15167,"codeCpp":15168,"tip":15169},"1. 字符串可以当字符数组","字符串本质上是一串字符，可用下标访问第 i 个字符。Python 中 s[i]；C++ 中 s[i]（或用范围 for 遍历）。遍历就是从左到右看每个字符。","s = \"hello\"\nfor i, ch in enumerate(s):\n    print(i, ch)","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s=\"hello\";\n    for(int i=0;i\u003Cs.size();i++) cout\u003C\u003Cs[i];\n    return 0;\n}","C++ 用 s.size() 或 s.length() 取长度；越界访问同样危险。",{"title":15171,"content":15172,"codePy":15173,"codeCpp":15174},"2. 遍历时构建新串","很多处理是\"看一个字符，决定放不放进结果\"。用结果变量一边遍历一边拼接，最后得到新字符串。","res = \"\"\nfor ch in s:\n    if ch.isalpha(): res += ch","string s,res; cin>>s;\nfor(char ch:s) if(isalpha(ch)) res+=ch;",[15176],{"title":15177,"prompt":15178,"analysis":15179,"codePy":15180,"codeCpp":15181,"run":15182},"例 1：只保留字母","读入一个字符串（可能含空格以外的符号），输出其中所有英文字母（保持原顺序）。","遍历每个字符，是字母就拼到结果。","s=input()\nprint(\"\".join(c for c in s if c.isalpha()))","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Ccctype>\nusing namespace std;\nint main(){\n    string s,res; cin>>s;\n    for(char ch:s) if(isalpha(ch)) res+=ch;\n    cout\u003C\u003Cres; return 0;\n}","a1b2c3\nabc",{"choice":15184,"coding":15206},[15185,15193,15201],{"id":38,"stem":15186,"options":15187,"answer":9,"explanation":15192},"C++ 中取字符串长度用？",[15188,15189,15190,15191],"s.len()","s.size()","s.length2()","strlen(s)","std::string 用 size() 或 length()；strlen 用于 C 风格字符数组。",{"id":47,"stem":15194,"options":15195,"answer":9,"explanation":15200},"Python 中既要下标又要字符，用？",[15196,15197,15198,15199],"for ch in s","for i,ch in enumerate(s)","for i in s","while s","enumerate 同时给下标和字符。",{"id":56,"stem":15202,"options":15203,"answer":9,"explanation":15205},"\"边遍历边构造新串\"常用什么操作？",[9602,15204,3117,9812],"拼接（+=）","用 += 把需要的字符追加到结果串。",[15207],{"id":67,"title":15208,"prompt":15209,"inputFormat":15210,"outputFormat":15211,"samples":15212,"starterPy":15216,"starterCpp":15217,"hint":15218,"referencePy":15219,"referenceCpp":15220},"反转字符串","读入一个不含空格的字符串，输出它的反转结果。","一行一个字符串（长度 ≤ 1000）。","一行反转后的字符串。",[15213],{"input":15214,"output":15215},"abcde","edcba","s=input()\n# 反转输出\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){\n    string s; cin>>s;\n    \u002F\u002F 反转\n    return 0;\n}","Python 用切片 s[::-1]；C++ 用 reverse(s.begin(), s.end()) 或倒序拼。","s=input()\nprint(s[::-1])","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){\n    string s; cin>>s;\n    reverse(s.begin(), s.end());\n    cout\u003C\u003Cs; return 0;\n}","字符串遍历把串当成字符序列，用下标或范围 for 逐个处理；边遍历边用 += 构造新串是常见手法。","27-A1-2","字符统计",{"id":15222,"title":15223,"level":3510,"module":15159,"seq":63,"goal":15225,"keywords":15226,"explanation":15227,"examples":15239,"exercises":15247,"summary":15284,"next":15285,"nextTitle":3647},"掌握字符统计：统计字符串中字母、数字、各类字符的出现次数与频率。",[15223,4166,1955],[15228,15233],{"title":15229,"content":15230,"codePy":15231,"codeCpp":15232},"1. 分类判断 + 计数","遍历字符串，用 isalpha\u002Fisdigit 等判断字符类别，分别累加计数。注意 C++ 要 include \u003Ccctype>，且只对单字节字符有效。","letters=digits=0\nfor ch in s:\n    if ch.isalpha(): letters+=1\n    elif ch.isdigit(): digits+=1","#include \u003Ccctype>\nint letters=0,digits=0;\nfor(char ch:s){ if(isalpha(ch)) letters++; else if(isdigit(ch)) digits++; }",{"title":15234,"content":15235,"codePy":15236,"codeCpp":15237,"tip":15238},"2. 用计数数组统计每个字母","要统计每个字母出现几次，直接上计数数组：cnt[ch-\"a\"]++，最后遍历 26 个字母输出频率。","cnt=[0]*26\nfor ch in s.lower(): cnt[ord(ch)-97]+=1","int cnt[26]={0}; for(char ch:s) if(islower(ch)) cnt[ch-\"a\"]++;","统一转小写再统计，避免 a 与 A 被当成两个。",[15240],{"title":15241,"prompt":15242,"analysis":15243,"codePy":15244,"codeCpp":15245,"run":15246},"例 1：字母与数字个数","读入一个字符串，输出其中英文字母的个数和数字的个数（空格分隔）。","遍历分类计数。","s=input()\nl=d=0\nfor ch in s:\n    if ch.isalpha(): l+=1\n    elif ch.isdigit(): d+=1\nprint(l,d)","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Ccctype>\nusing namespace std;\nint main(){\n    string s; getline(cin,s);\n    int l=0,d=0; for(char ch:s){\n        if(isalpha(ch)) l++; else if(isdigit(ch)) d++;\n    }\n    cout\u003C\u003Cl\u003C\u003C\" \"\u003C\u003Cd; return 0;\n}","ab12 cd3\n4 3",{"choice":15248,"coding":15271},[15249,15257,15265],{"id":38,"stem":15250,"options":15251,"answer":9,"explanation":15256},"判断字符 ch 是否为字母（C++）用？",[15252,15253,15254,15255],"ch>=\"a\"","isalpha(ch)","isdigit(ch)","ch==\"a\"","isalpha 处理大小写更稳妥。",{"id":47,"stem":15258,"options":15259,"answer":9,"explanation":15264},"统计每个字母频率，下标应写？",[15260,15261,15262,15263],"cnt[ch]","cnt[ch-\"a\"]","cnt[ch-97]","cnt[0]","ch-\"a\" 把 a..z 映射 0..25；注意要先把大写转小写。",{"id":56,"stem":15266,"options":15267,"answer":9,"explanation":15270},"统计时想忽略大小写，应先？",[3117,15268,15269,8968],"统一转小写\u002F大写","删除字母","转成同一大小写再计数，避免重复。",[15272],{"id":67,"title":15273,"prompt":15274,"inputFormat":15275,"outputFormat":15276,"samples":15277,"starterPy":15281,"starterCpp":15282,"hint":15283,"referencePy":15281,"referenceCpp":15282},"最多字母","读入一个仅含小写字母的字符串（长度 ≤ 1000），输出出现次数最多的字母及其次数（若并列，输出字典序最小的）。","一行一个字符串。","一行：字母 次数。",[15278],{"input":15279,"output":15280},"abcaabb","a 3","s=input()\ncnt=[0]*26\nfor ch in s: cnt[ord(ch)-97]+=1\nbest=0\nfor i in range(26):\n    if cnt[i]>cnt[best]: best=i\nprint(chr(best+97), cnt[best])","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; cin>>s; int cnt[26]={0};\n    for(char ch:s) cnt[ch-\"a\"]++;\n    int best=0; for(int i=1;i\u003C26;i++) if(cnt[i]>cnt[best]) best=i;\n    cout\u003C\u003Cchar(best+\"a\")\u003C\u003C\" \"\u003C\u003Ccnt[best]; return 0;\n}","计数数组记录频率；用 > 更新可天然保留字典序小的并列项。","字符统计=遍历 + 分类判断 + 计数；统计每个字母用计数数组 cnt[ch-\"a\"]，并先统一大小写。","27-A1-3",{"id":15285,"title":3647,"level":3510,"module":15159,"seq":703,"goal":15287,"keywords":15288,"explanation":15290,"examples":15302,"exercises":15310,"summary":15346,"next":15347,"nextTitle":15348},"掌握字符串模拟：把过程建模为对字符串\u002F字符序列的逐步变换与判断。",[775,3513,15289],"变换",[15291,15296],{"title":15292,"content":15293,"codePy":15294,"codeCpp":15295},"1. 把规则应用到每个字符","字符串模拟通常是\"对原串每个字符按规则生成新串\"。例如把串里的每个数字替换成它的英文单词长度，或把连续空格压缩成一个。","res = \"\"\nfor ch in s:\n    res += \"@\" if ch==\" \" else ch","string res; for(char ch:s) res += (ch==\" \"?\"@\":string(1,ch));",{"title":15297,"content":15298,"codePy":15299,"codeCpp":15300,"tip":15301},"2. 字符与数值的来回转换","数字字符转数值用 ch-\"0\"；数值转字符用 v+\"0\"。在\"把整数写进字符串\"\"统计数字\"等模拟里频繁用到，是字符串与数组处理的桥梁。","val = val*10 + (ord(ch)-ord(\"0\"))","val = val*10 + (ch - \"0\");","参考 Materials 2.4：char 与 int 互转正是靠 ASCII 偏移。",[15303],{"title":15304,"prompt":15305,"analysis":15306,"codePy":15307,"codeCpp":15308,"run":15309},"例 1：替换空格","读入一个字符串，把所有空格替换成下划线 \"_\"，输出结果。","遍历字符，空格换成下划线，其余原样保留。","s=input()\nprint(s.replace(\" \", \"_\"))","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; getline(cin,s);\n    for(char &ch:s) if(ch==\" \") ch=\"_\"[0];\n    for(char &ch:s) if(ch==32) ch=\"_\"[0];\n    for(char &ch:s) if(ch==\" \") ch=\"_\"[0];\n    cout\u003C\u003Cs; return 0;\n}","hello world\nhello_world",{"choice":15311,"coding":15331},[15312,15317,15324],{"id":38,"stem":15313,"options":15314,"answer":9,"explanation":15316},"数字字符 ch 转整数用？",[3677,4335,15315,3680],"ch+0","ASCII 偏移法。",{"id":47,"stem":15318,"options":15319,"answer":9,"explanation":15323},"字符串模拟\"逐字符变换\"常用？",[15320,15321,15322,3117],"删除原串","边遍历边拼新串","只改最后一个","遍历源串、构造结果串最直观。",{"id":56,"stem":15325,"options":15326,"answer":607,"explanation":15330},"把整数 5 转成字符 \"5\" 用？",[15327,290,15328,15329],"5+\"0\"","chr(5)","str()","v+\"0\" 得到对应数字字符（C++）；Python 用 str(v)。",[15332],{"id":67,"title":15333,"prompt":15334,"inputFormat":15335,"outputFormat":15336,"samples":15337,"starterPy":15341,"starterCpp":15342,"hint":15343,"referencePy":15344,"referenceCpp":15345},"压缩空格","读入一个字符串，把其中连续的多个空格压缩成单个空格，开头结尾的空格删除。","一行一个字符串（可能含连续空格）。","一行处理后的字符串。",[15338],{"input":15339,"output":15340},"  a   b  c  ","a b c","s=input()\n# 压缩空格\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; getline(cin,s);\n    \u002F\u002F 压缩空格\n    return 0;\n}","Python 可用 \" \".join(s.split())；C++ 遍历，遇到非空格就输出，遇到空格仅当\"上一个不是空格\"才输出一个。","s=input()\nprint(\" \".join(s.split()))","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){\n    string s; getline(cin,s);\n    bool space=false; string res;\n    for(char ch:s){\n        if(ch==\" \"){ if(!space && !res.empty()){ res+=\" \"; space=true; } }\n        else { res+=ch; space=false; }\n    }\n    cout\u003C\u003Cres; return 0;\n}","字符串模拟把规则逐字符应用到串上，边遍历边构造新串；字符与数值互转（ch-\"0\" \u002F v+\"0\"）是连接字符串与数字处理的桥梁。","27-A1-4","字符串排序",{"id":15347,"title":15348,"level":3510,"module":15159,"seq":770,"goal":15350,"keywords":15351,"explanation":15352,"examples":15365,"exercises":15373,"summary":15409},"掌握字符串排序：对字符串中的字符排序，以及对一组字符串按字典序排序。",[3117,7624,1956],[15353,15359],{"title":15354,"content":15355,"codePy":15356,"codeCpp":15357,"tip":15358},"1. 字符串内字符排序","把字符串的字符排成升序（字典序），本质是把字符序列排序。Python 用 sorted(s) 再拼接；C++ 把串变成字符数组 sort 一下。","s = \"cba\"\nprint(\"\".join(sorted(s)))","#include \u003Calgorithm>\nstring s=\"cba\"; sort(s.begin(), s.end());\ncout \u003C\u003C s;  \u002F\u002F abc","排序后相同的字符会聚在一起，常用于\"变位词\"判断。",{"title":15360,"content":15361,"codePy":15362,"codeCpp":15363,"tip":15364},"2. 一组字符串排序","对多个字符串按字典序排序：Python 直接 sort；C++ 用 vector\u003Cstring> 再 sort（string 的 \u003C 就是字典序）。","words = [\"banana\",\"apple\",\"cherry\"]\nwords.sort()","vector\u003Cstring> w={\"banana\",\"apple\",\"cherry\"};\nsort(w.begin(), w.end());","字典序比较的是逐字符的 ASCII 大小。",[15366],{"title":15367,"prompt":15368,"analysis":15369,"codePy":15370,"codeCpp":15371,"run":15372},"例 1：字符排序","读入一个字符串，输出其字符按升序排列后的结果。","排序字符再拼接。","s=input()\nprint(\"\".join(sorted(s)))","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){\n    string s; cin>>s;\n    sort(s.begin(), s.end());\n    cout\u003C\u003Cs; return 0;\n}","dcba\nabcd",{"choice":15374,"coding":15396},[15375,15383,15390],{"id":38,"stem":15376,"options":15377,"answer":9,"explanation":15382},"Python 对字符串字符排序并还原用？",[15378,15379,15380,15381],"s.sort()","\"\".join(sorted(s))","sorted(s)","s[::-1]","sorted 返回字符列表，join 还原成串。",{"id":47,"stem":15384,"options":15385,"answer":9,"explanation":15389},"C++ 对 string 排序用？",[15386,15387,15378,15388],"sort(s)","sort(s.begin(), s.end())","reverse(s)","string 可当作随机访问区间。",{"id":56,"stem":15391,"options":15392,"answer":9,"explanation":15395},"字典序比较基于？",[6269,15393,15394,1568],"逐字符 ASCII 大小","首字符","从左到右逐字符比 ASCII。",[15397],{"id":67,"title":15398,"prompt":15399,"inputFormat":15400,"outputFormat":8414,"samples":15401,"starterPy":15406,"starterCpp":15407,"hint":15408,"referencePy":15406,"referenceCpp":15407},"变位词判断","读入两个字符串，判断它们是否为\"变位词\"（字符种类和数量完全相同，只是顺序不同）。是输出 YES，否则 NO。","一行两个字符串，空格分隔。",[15402,15404],{"input":15403,"output":8418},"listen silent",{"input":15405,"output":1943},"hello world","a,b=input().split()\nprint(\"YES\" if sorted(a)==sorted(b) else \"NO\")","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){\n    string a,b; cin>>a>>b;\n    if(a.size()!=b.size()){ cout\u003C\u003C\"NO\"; return 0; }\n    sort(a.begin(),a.end()); sort(b.begin(),b.end());\n    cout\u003C\u003C(a==b?\"YES\":\"NO\"); return 0;\n}","长度不同直接 NO；否则把两个串各自排序后比较是否相等。","字符串排序包括对\"串内字符\"排序（变位词判断常用）和对\"一组串\"按字典序排序；字典序基于逐字符 ASCII 比较。",{"id":12648,"title":12649,"level":6350,"module":3259,"seq":9,"goal":15411,"keywords":15412,"explanation":15417,"examples":15429,"exercises":15433,"summary":15466,"next":15467,"nextTitle":15468},"理解哈希表：通过哈希函数把键映射到桶；不同键映射到同桶即冲突，介绍链地址法与开放寻址法两种解决思路。",[15413,15414,15415,15416],"哈希表","哈希函数","冲突","负载因子",[15418,15423],{"title":15419,"content":15420,"codePy":15421,"codeCpp":15422},"1. 哈希与映射","h(key) 把任意键映射到 [0,m) 的桶下标；理想情况下 O(1) 查找\u002F插入。","h = lambda x: x % m  # 整数取模\nprint(h(12345))","\u002F\u002F 键 -> 桶下标",{"title":15424,"content":15425,"codePy":15426,"codeCpp":15427,"tip":15428},"2. 冲突与解决","冲突不可避免（鸽巢原理）。链地址法把同桶元素挂成链表；开放寻址法线性\u002F二次探测寻找下一个空桶。","print(\"冲突靠链地址\u002F探测\")","\u002F\u002F 负载因子=元素数\u002F桶数","tip: 负载因子越大冲突越多；重建更大表可降因子。",[15430],{"title":14380,"prompt":15431,"analysis":15432,"codePy":4786,"codeCpp":4432,"run":624},"键 13，桶数 7，桶下标？","13%7=6。",{"choice":15434,"coding":15452},[15435,15439,15446],{"id":38,"stem":15436,"options":15437,"answer":9,"explanation":15438},"哈希表查找期望？",[2338,1245,1246,2339],"理想 O(1)。",{"id":47,"stem":15440,"options":15441,"answer":9,"explanation":15445},"冲突能否完全避免？",[1367,15442,15443,15444],"不能(鸽巢)","看运气","能若桶无限","有限桶必冲突。",{"id":56,"stem":15447,"options":15448,"answer":9,"explanation":15451},"链地址法把冲突元素？",[8461,15449,6586,15450],"挂成链表","重算","同桶成链。",[15453],{"id":67,"title":15454,"prompt":15455,"inputFormat":15456,"outputFormat":15457,"samples":15458,"starterPy":15462,"starterCpp":15463,"hint":15464,"referencePy":15465,"referenceCpp":15463},"手写链地址哈希表","实现一个整数哈希表（链地址法）：支持插入与查询。给定 q 次操作，add x 插入，find x 输出 YES\u002FNO。","第一行 q；接下来 q 行操作。","对每个 find 输出 YES 或 NO。",[15459],{"input":15460,"output":15461},"4\nadd 3\nadd 10\nfind 3\nfind 4","YES\nNO","q=int(input()); m=10007; buckets=[[] for _ in range(m)]\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x); b=x%m\n    if op==\"add\":\n        if x not in buckets[b]: buckets[b].append(x)\n    else:\n        print(\"YES\" if x in buckets[b] else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst int M=10007; vector\u003Cint> bk[M];\nint main(){ int q; cin>>q; while(q--){ string op; int x; cin>>op>>x; int b=x%M; if(op==\"add\"){ bool has=false; for(int v:bk[b]) if(v==x){has=true;break;} if(!has) bk[b].push_back(x);} else { bool has=false; for(int v:bk[b]) if(v==x){has=true;break;} cout\u003C\u003C(has?\"YES\":\"NO\")\u003C\u003Cendl; } } }","整数取模分桶，同桶挂链。","q=int(input()); m=10007; buckets=[[] for _ in range(m)]\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x); b=x%m\n    if op==\"add\":\n        if x not in buckets[b]: buckets[b].append(x)\n    else:\n        print(\"YES\" if x in buckets[b] else \"NO\")","哈希表用哈希函数 h(key) 把键映射到桶下标，实现近似 O(1) 的查找\u002F插入；因鸽巢原理冲突不可避免，常用链地址法（同桶挂链表）或开放寻址法（探测下一个空桶）解决，负载因子过高需扩容重建。","28-A1-2","开放寻址与链地址对比",{"id":15467,"title":15468,"level":6350,"module":3259,"seq":63,"goal":15470,"keywords":15471,"explanation":15476,"examples":15488,"exercises":15493,"summary":15526,"next":15527,"nextTitle":15528},"对比哈希冲突的两种解法：链地址法（桶挂链表，删除方便）与开放寻址法（线性\u002F二次\u002F双重探测，缓存友好但易聚集）。",[15472,15473,15474,15475],"开放寻址","链地址","探测","聚集",[15477,15482],{"title":15478,"content":15479,"codePy":15480,"codeCpp":15481},"1. 开放寻址","插入时若 h(key) 被占，按探测序列 (h+i·c) % m 找下一个空位；查找沿同序列直到命中或遇空。","def probe(h, m, i): return (h + i) % m  # 线性探测","\u002F\u002F 探测序列",{"title":15483,"content":15484,"codePy":15485,"codeCpp":15486,"tip":15487},"2. 对比","链地址法实现简单、删除易、受负载因子影响温和；开放寻址缓存友好但易一次聚集，删除用墓碑标记。","print(\"链地址 vs 开放寻址\")","\u002F\u002F 各自适用场景","tip: 实际语言（如 Python dict、Java HashMap）多用链地址\u002F红黑树结合的开放方案。",[15489],{"title":15490,"prompt":15491,"analysis":15492,"codePy":7878,"codeCpp":7879,"run":892},"例 1：线性探测","桶 0 被占，键映射 0，下一个探测？","探测 1。",{"choice":15494,"coding":15513},[15495,15500,15507],{"id":38,"stem":15496,"options":15497,"answer":9,"explanation":15499},"开放寻址删除用？",[10499,15498,3560,10191],"墓碑标记","避免断探测链。",{"id":47,"stem":15501,"options":15502,"answer":9,"explanation":15506},"线性探测问题？",[9178,15503,15504,15505],"一次聚集","丢数据","无法查找","连续占用成块。",{"id":56,"stem":15508,"options":15509,"answer":9,"explanation":15512},"链地址法删除？",[15510,15511,5810,15450],"难","易","从链表摘除。",[15514],{"id":67,"title":15515,"prompt":15516,"inputFormat":15456,"outputFormat":15517,"samples":15518,"starterPy":15521,"starterCpp":15522,"hint":15523,"referencePy":15524,"referenceCpp":15525},"开放寻址哈希表","实现开放寻址（线性探测 + 墓碑）的整数哈希表：add x 插入（已存在则忽略），find x 输出 YES\u002FNO。桶数组固定大小 M=10007。","每个 find 输出 YES\u002FNO。",[15519],{"input":15520,"output":15461},"4\nadd 3\nadd 3\nfind 3\nfind 5","q=int(input()); M=10007; tbl=[None]*M; DELETED=object()\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x); i=x%M\n    if op==\"add\":\n        j=i\n        while tbl[j] is not None and tbl[j]!=DELETED:\n            if tbl[j]==x: break\n            j=(j+1)%M\n        else:\n            tbl[j]=x\n    else:\n        j=i; found=False\n        while tbl[j] is not None:\n            if tbl[j]==x: found=True; break\n            j=(j+1)%M\n        print(\"YES\" if found else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst int M=10007; const int EMPTY=-1, DEL=-2, NO=2147483647; vector\u003Cint> tbl(M,EMPTY);\nint find_slot(int x){ int i=x%M; while(tbl[i]!=EMPTY){ if(tbl[i]!=DEL && tbl[i]==x) return i; i=(i+1)%M;} return -1; }\nint main(){ int q; cin>>q; while(q--){ string op; int x; cin>>op>>x; if(op==\"add\"){ if(find_slot(x)!=-1) continue; int i=x%M; while(tbl[i]!=EMPTY) i=(i+1)%M; tbl[i]=x; } else { cout\u003C\u003C(find_slot(x)!=-1?\"YES\":\"NO\")\u003C\u003Cendl; } } }","线性探测遇空\u002F墓碑插入，查找沿序列。","q=int(input()); M=10007; tbl=[None]*M; DELETED=object()\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x); i=x%M\n    if op==\"add\":\n        j=i\n        while tbl[j] is not None and tbl[j]!=DELETED:\n            if tbl[j]==x: break\n            j=(j+1)%M\n        else:\n            tbl[j]=x\n    else:\n        j=i; found=False\n        while tbl[j] is not None:\n            if tbl[j]==x: found=True; break\n            j=(j+1)%M\n        print(\"YES\" if found else \"NO\")","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nconst int M=10007; const int EMPTY=-1, DEL=-2; vector\u003Cint> tbl(M,EMPTY);\nint find_slot(int x){ int i=x%M; while(tbl[i]!=EMPTY){ if(tbl[i]!=DEL && tbl[i]==x) return i; i=(i+1)%M;} return -1; }\nint main(){ int q; cin>>q; while(q--){ string op; int x; cin>>op>>x; if(op==\"add\"){ if(find_slot(x)!=-1) continue; int i=x%M; while(tbl[i]!=EMPTY) i=(i+1)%M; tbl[i]=x; } else { cout\u003C\u003C(find_slot(x)!=-1?\"YES\":\"NO\")\u003C\u003Cendl; } } }","开放寻址法在冲突时按探测序列（线性 (h+i) 等）寻找下一个空桶，缓存友好但易一次聚集，删除要用墓碑标记以维持探测链；链地址法把冲突元素挂成链表，删除简单、对负载因子更鲁棒。","28-A1-3","字符串哈希（滚动哈希）",{"id":15527,"title":15528,"level":6350,"module":3259,"seq":703,"goal":15530,"keywords":15531,"explanation":15536,"examples":15547,"exercises":15555,"summary":15585,"next":15586,"nextTitle":15587},"掌握字符串滚动哈希：把字符串视作 P 进制数模大素数，配合前缀哈希可在 O(1) 比较任意子串是否相等，用于 Rabin-Karp 与字符串判重。",[15532,15533,15534,15535],"字符串哈希","滚动哈希","Rabin-Karp","双模",[15537,15542],{"title":15538,"content":15539,"codePy":15540,"codeCpp":15541},"1. 滚动计算","h[i]=h[i-1]*P + s[i]，子串 [l,r] 哈希=(h[r]-h[l-1]*P^(r-l+1)) % MOD；用双模或自然溢出降低碰撞。","def hash_str(s, P=131, MOD=10**9+7):\n    h=[0]; pw=[1]\n    for c in s:\n        h.append((h[-1]*P + (ord(c)-97)) % MOD)\n        pw.append(pw[-1]*P % MOD)\n    def sub(l,r):  # 0-index\n        return (h[r+1]-h[l]*pw[r-l+1]) % MOD\n    return sub","\u002F\u002F 前缀哈希 + 幂",{"title":4421,"content":15543,"codePy":15544,"codeCpp":15545,"tip":15546},"在 O(n) 内求所有子串哈希即可判重\u002F查回文\u002F模式匹配；为防碰撞常用两个不同模数。","print(\"O(1) 子串比较\")","\u002F\u002F 双模更稳","tip: 用 P=131\u002F13331 与自然溢出或双模，实践中极少碰撞。",[15548],{"title":15549,"prompt":15550,"analysis":15551,"codePy":15552,"codeCpp":15553,"run":15554},"例 1：子串哈希","s=\"abc\"，P=131，h 前缀第三个值=?","(a*131+b)*131+c 模后。","print(\"取决于MOD\")","\u002F\u002F depends","depends",{"choice":15556,"coding":15573},[15557,15562,15568],{"id":38,"stem":15558,"options":15559,"answer":9,"explanation":15561},"字符串哈希用于？",[3117,15560,10078,6040],"O(1)子串比较","前缀哈希求子串。",{"id":47,"stem":15563,"options":15564,"answer":9,"explanation":15567},"防碰撞常用？",[15565,15566,1568,6810],"单模","双模\u002F自然溢出","两模数降低概率。",{"id":56,"stem":15569,"options":15570,"answer":9,"explanation":15572},"滚动哈希基础？",[12859,15571,2570,13348],"P进制模","视作 P 进制数。",[15574],{"id":67,"title":15575,"prompt":15576,"inputFormat":15577,"outputFormat":8414,"samples":15578,"starterPy":15581,"starterCpp":15582,"hint":15583,"referencePy":15584,"referenceCpp":15582},"字符串哈希判重","给定字符串 s 与长度 L，判断是否存在两个长度为 L 的相同子串（仅输出 YES\u002FNO）。","第一行 s；第二行 L。",[15579],{"input":15580,"output":8418},"ababa\n2","s=input().strip(); L=int(input()); n=len(s); P=131; MOD=10**9+7\nh=[0]*(n+1); pw=[1]*(n+1)\nfor i in range(n):\n    h[i+1]=(h[i]*P+(ord(s[i])-97))%MOD\n    pw[i+1]=pw[i]*P%MOD\nseen=set()\nfor l in range(L, n+1):\n    r=l-1; cur=(h[l]-h[r-L+1]*pw[L])%MOD\n    if cur in seen: print(\"YES\"); break\n    seen.add(cur)\nelse: print(\"NO\")\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\n#include \u003Cunordered_set>\nusing namespace std;\nint main(){ string s; int L; cin>>s>>L; int n=s.size(); const long long P=131,MOD=1000000007; vector\u003Clong long> h(n+1,0),pw(n+1,1); for(int i=0;i\u003Cn;i++){ h[i+1]=(h[i]*P+(s[i]-'a'))%MOD; pw[i+1]=pw[i]*P%MOD; } unordered_set\u003Clong long> seen; for(int l=L;l\u003C=n;l++){ int r=l-1; long long cur=(h[l]-h[r-L+1]*pw[L]%MOD+MOD)%MOD; if(seen.count(cur)){ cout\u003C\u003C\"YES\"; return 0;} seen.insert(cur);} cout\u003C\u003C\"NO\"; }","前缀哈希 + 同余幂求子串哈希，查重。","s=input().strip(); L=int(input()); n=len(s); P=131; MOD=10**9+7\nh=[0]*(n+1); pw=[1]*(n+1)\nfor i in range(n):\n    h[i+1]=(h[i]*P+(ord(s[i])-97))%MOD\n    pw[i+1]=pw[i]*P%MOD\nseen=set()\nfor l in range(L, n+1):\n    r=l-1; cur=(h[l]-h[r-L+1]*pw[L])%MOD\n    if cur in seen: print(\"YES\"); break\n    seen.add(cur)\nelse: print(\"NO\")","字符串滚动哈希把字符串视为 P 进制数模大素数，用前缀哈希 h[i]=h[i-1]*P+s[i] 与幂表，可在 O(1) 得到任意子串哈希从而比较相等；常用双模或自然溢出降低碰撞概率，是 Rabin-Karp 与子串判重的基础。","28-A2-1","哈希函数设计与扰动",{"id":15586,"title":15587,"level":6350,"module":3259,"seq":770,"goal":15589,"keywords":15590,"explanation":15594,"examples":15606,"exercises":15610,"summary":15643,"next":15644,"nextTitle":15645},"理解哈希函数的设计目标（均匀、稳定、快速）与常见方法：除留余数、乘法哈希、以及字符串\u002F结构的组合哈希；介绍位扰动减少低位聚集。",[15414,15591,15592,15593],"除留余数","乘法哈希","扰动",[15595,15600],{"title":15596,"content":15597,"codePy":15598,"codeCpp":15599},"1. 常见方法","整数除留余数（h=x%m）；乘法哈希（h=floor(m*(x*A mod 1))）；字符串多项式累积。","def hash_int(x, m):\n    A=0.6180339887\n    return int(m*(x*A - int(x*A)))","\u002F\u002F 乘黄金比例",{"title":15601,"content":15602,"codePy":15603,"codeCpp":15604,"tip":15605},"2. 扰动","当键的高位变化、低位固定时（如地址），简单取模会聚集；用移位异或把高位比特混入低位（如 Java HashMap 的扰动）。","print(\"让分布更均匀\")","\u002F\u002F 防低位聚集","tip: 好的哈希函数应让键的每一位都参与，输出分布均匀。",[15607],{"title":14380,"prompt":15608,"analysis":15609,"codePy":5966,"codeCpp":4490,"run":1663},"m=16，x=0x1234，简单取模得？","0x1234%16=4（仅低位参与）。",{"choice":15611,"coding":15629},[15612,15616,15623],{"id":38,"stem":15613,"options":15614,"answer":9,"explanation":15615},"除留余数用？",[12859,843,2570,13348],"x % m。",{"id":47,"stem":15617,"options":15618,"answer":9,"explanation":15622},"简单取模低位聚集时？",[15619,15620,3560,15621],"更好","需扰动","更均匀","高位未参与。",{"id":56,"stem":15624,"options":15625,"answer":607,"explanation":15628},"乘法哈希用？",[15626,14021,3265,15627],"黄金比例","对数","A≈0.618。",[15630],{"id":67,"title":15631,"prompt":15632,"inputFormat":15633,"outputFormat":15634,"samples":15635,"starterPy":15639,"starterCpp":15640,"hint":15641,"referencePy":15642,"referenceCpp":15640},"带扰动哈希","实现一个带扰动的整型哈希：h = (x ^ (x>>16)) % M，用于演示高位混入低位。给定若干键，输出各自桶下标。","第一行 M 与 q；接下来 q 行整数键。","q 行，每行一个桶下标。",[15636],{"input":15637,"output":15638},"16 2\n65536\n131072","0\n0","M,q=map(int,input().split()); import sys\nfor _ in range(q):\n    x=int(sys.stdin.readline()); print((x ^ (x>>16)) % M)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int M,q; cin>>M>>q; while(q--){ long long x; cin>>x; cout\u003C\u003C((x ^ (x>>16)) % M)\u003C\u003Cendl; } }","高位右移异或低位再取模。","M,q=map(int,input().split()); import sys\nfor _ in range(q):\n    x=int(sys.stdin.readline()); print((x ^ (x>>16)) % M)","哈希函数要快速、稳定且输出均匀：整数常用除留余数或乘法哈希（乘黄金比例后取小数部分×m）；当键低位固定、高位变化（如内存地址）时简单取模会聚集，需用移位异或扰动把高位比特混入低位（如 Java HashMap 的 (h^(h>>>16))）。","28-A2-2","双哈希与布隆过滤器",{"id":15644,"title":15645,"level":6350,"module":3259,"seq":838,"goal":15647,"keywords":15648,"explanation":15653,"examples":15665,"exercises":15673,"summary":15706,"next":15707,"nextTitle":15708},"掌握双哈希（用两个独立哈希函数降低冲突\u002F碰撞）与布隆过滤器（多个哈希位构成的位数组，支持高效近似“可能存在\u002F一定不存在”）。",[15649,15650,15651,15652],"双哈希","布隆过滤器","位数组","误判",[15654,15659],{"title":15655,"content":15656,"codePy":15657,"codeCpp":15658},"1. 双哈希","为同一键计算 h1、h2；开放寻址的步长用 h2 避免聚集；或字符串哈希用两个模数防止碰撞。","def double_hash(x, m1, m2):\n    return (x % m1, x % m2)","\u002F\u002F 两独立哈希",{"title":15660,"content":15661,"codePy":15662,"codeCpp":15663,"tip":15664},"2. 布隆过滤器","用 k 个哈希函数把键映射到位数组的 k 位并置 1；查询时若任意一位为 0 则一定不存在，全 1 则可能存在于（有误判）。","print(\"存在性近似判断\")","\u002F\u002F 有误判无漏判","tip: 布隆过滤器空间极小，常用于缓存穿透防护、爬虫去重。",[15666],{"title":15667,"prompt":15668,"analysis":15669,"codePy":15670,"codeCpp":15671,"run":15672},"例 1：布隆","布隆查询某位为 0 说明？","一定不存在（无漏判）。","print(\"一定不存在\")","\u002F\u002F absent","absent",{"choice":15674,"coding":15693},[15675,15682,15687],{"id":38,"stem":15676,"options":15677,"answer":9,"explanation":15681},"布隆过滤器查询全1说明？",[15678,15679,15680,1568],"一定存在","可能存在","一定不存在","有误判可能。",{"id":47,"stem":15683,"options":15684,"answer":9,"explanation":15686},"布隆某位为0说明？",[15679,15680,15685,1568],"存在","无漏判。",{"id":56,"stem":15688,"options":15689,"answer":9,"explanation":15692},"双哈希目的？",[6446,15690,3117,15691],"降低聚集\u002F碰撞","压缩","两函数更分散。",[15694],{"id":67,"title":15695,"prompt":15696,"inputFormat":15697,"outputFormat":15698,"samples":15699,"starterPy":15702,"starterCpp":15703,"hint":15704,"referencePy":15705,"referenceCpp":15703},"布隆过滤器（模拟）","模拟一个布隆过滤器：位数组大小 N=1024，用 3 个简单哈希函数（h_i(x)=(x*coef[i])%N）。q 次操作，add 置位，query 输出 YES（可能）\u002FNO（一定不）。","第一行 q；接下来 q 行 op x。","每个 query 输出 YES\u002FNO。",[15700],{"input":15701,"output":8418},"3\nadd 5\nadd 9\nquery 5","q=int(input()); N=1024; bit=[0]*N; coef=[101,103,107]\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x)\n    hs=[(x*c)%N for c in coef]\n    if op==\"add\":\n        for b in hs: bit[b]=1\n    else:\n        print(\"NO\" if any(bit[b]==0 for b in hs) else \"YES\")\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int q; cin>>q; const int N=1024; vector\u003Cbool> bit(N,false); int coef[3]={101,103,107}; while(q--){ string op; int x; cin>>op>>x; int hs[3]; for(int i=0;i\u003C3;i++) hs[i]=(x*coef[i])%N; if(op==\"add\"){ for(int i=0;i\u003C3;i++) bit[hs[i]]=true; } else { bool ok=true; for(int i=0;i\u003C3;i++) if(!bit[hs[i]]) ok=false; cout\u003C\u003C(ok?\"YES\":\"NO\")\u003C\u003Cendl; } } }","多哈希位并查，查到0即不存在。","q=int(input()); N=1024; bit=[0]*N; coef=[101,103,107]\nimport sys\nfor _ in range(q):\n    op,x=sys.stdin.readline().split(); x=int(x)\n    hs=[(x*c)%N for c in coef]\n    if op==\"add\":\n        for b in hs: bit[b]=1\n    else:\n        print(\"NO\" if any(bit[b]==0 for b in hs) else \"YES\")","双哈希使用两个独立哈希函数（如开放寻址的不同步长、或字符串的双模数）降低聚集与碰撞概率。布隆过滤器用 k 个哈希函数把键映射到位数组的 k 位：查询时若任一对应位为 0 则一定不存在（无漏判），全 1 则可能存在（有误判），空间极小。","28-A2-3","Rabin-Karp 字符串匹配实战",{"id":15707,"title":15708,"level":6350,"module":3259,"seq":936,"goal":15710,"keywords":15711,"explanation":15714,"examples":15725,"exercises":15729,"summary":15759,"next":15760,"nextTitle":15761},"掌握 Rabin-Karp：对模式串与文本每个长度为 m 的窗口计算滚动哈希，哈希相等再逐字符确认，从而在平均 O(n) 完成多模式\u002F子串匹配。",[15534,15712,15533,15713],"模式匹配","多模式",[15715,15720],{"title":15716,"content":15717,"codePy":15718,"codeCpp":15719},"1. 滚动匹配","预计算模式哈希与首个窗口哈希；之后每个新窗口用 (旧-h[0]*P^m + 新字符) 滚动得到，O(1) 转移。","def rabin_karp(t, p):\n    n,m=len(t),len(p); P=131; MOD=10**9+7\n    hp=0; ht=0; pw=P**m%MOD\n    for i in range(m):\n        hp=(hp*P+ord(p[i])-97)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\n    if hp==ht and t[:m]==p: return 0\n    for i in range(m,n):\n        ht=(ht-(ord(t[i-m])-97)*pw)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\n        if ht==hp and t[i-m+1:i+1]==p: return i-m+1\n    return -1","\u002F\u002F 哈希相等再校验",{"title":4833,"content":15721,"codePy":15722,"codeCpp":15723,"tip":15724},"哈希相等才逐字符核对，避免每个位置都 O(m) 比较；特别适合多模式同时匹配。","print(\"先哈希后校验\")","\u002F\u002F 平均 O(n)","tip: 必须哈希相等后再逐字符比对，否则有碰撞误报。",[15726],{"title":9114,"prompt":15727,"analysis":15728,"codePy":7878,"codeCpp":7879,"run":892},"文本 \"abac\"，模式 \"ba\"，首次位置？","索引 1。",{"choice":15730,"coding":15747},[15731,15736,15741],{"id":38,"stem":15732,"options":15733,"answer":9,"explanation":15735},"Rabin-Karp 平均复杂度？",[4504,2338,2339,15734],"O(m)","滚动哈希 O(n)。",{"id":47,"stem":15737,"options":15738,"answer":9,"explanation":15740},"哈希相等后需？",[14396,15739,12626,3117],"逐字符校验","防碰撞误报。",{"id":56,"stem":15742,"options":15743,"answer":9,"explanation":15746},"Rabin-Karp 适合？",[15744,15745,3117,6040],"单模式","多模式同时匹配","一次扫可查多模式。",[15748],{"id":67,"title":15749,"prompt":15750,"inputFormat":15751,"outputFormat":6275,"samples":15752,"starterPy":15755,"starterCpp":15756,"hint":15757,"referencePy":15758,"referenceCpp":15756},"Rabin-Karp 子串匹配","给定文本串 t 与模式串 p（仅小写字母），返回 p 在 t 中首次出现的起始下标（0 起），不存在输出 -1。","两行字符串 t 和 p（|t|≤10^5）。",[15753],{"input":15754,"output":892},"abac\nba","t=input().strip(); p=input().strip(); n,m=len(t),len(p)\nif m>n: print(-1); exit()\nP=131; MOD=10**9+7; pw=pow(P,m,MOD)\nhp=0; ht=0\nfor i in range(m):\n    hp=(hp*P+ord(p[i])-97)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\nif hp==ht and t[:m]==p: print(0); exit()\nfor i in range(m,n):\n    ht=(ht-(ord(t[i-m])-97)*pw)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\n    if ht==hp and t[i-m+1:i+1]==p: print(i-m+1); exit()\nprint(-1)\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nint main(){ string t,p; cin>>t>>p; int n=t.size(),m=p.size(); if(m>n){cout\u003C\u003C-1; return 0;} const long long P=131,MOD=1000000007; long long hp=0,ht=0,pw=1; for(int i=0;i\u003Cm;i++){ hp=(hp*P+(p[i]-'a'))%MOD; ht=(ht*P+(t[i]-'a'))%MOD; if(i\u003Cm-1) pw=pw*P%MOD; } if(hp==ht && t.substr(0,m)==p){cout\u003C\u003C0; return 0;} for(int i=m;i\u003Cn;i++){ ht=(ht-(t[i-m]-'a')*pw%MOD+MOD)%MOD; ht=(ht*P+(t[i]-'a'))%MOD; if(ht==hp && t.substr(i-m+1,m)==p){cout\u003C\u003Ci-m+1; return 0;} } cout\u003C\u003C-1; }","滚动哈希 + 逐字符校验。","t=input().strip(); p=input().strip(); n,m=len(t),len(p)\nif m>n: print(-1); exit()\nP=131; MOD=10**9+7; pw=pow(P,m,MOD)\nhp=0; ht=0\nfor i in range(m):\n    hp=(hp*P+ord(p[i])-97)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\nif hp==ht and t[:m]==p: print(0); exit()\nfor i in range(m,n):\n    ht=(ht-(ord(t[i-m])-97)*pw)%MOD; ht=(ht*P+ord(t[i])-97)%MOD\n    if ht==hp and t[i-m+1:i+1]==p: print(i-m+1); exit()\nprint(-1)","Rabin-Karp 用滚动哈希在文本上滑动：先算模式哈希和首窗口哈希，之后每移一格用 (旧 - 首位×P^m + 新字符) 在 O(1) 得新窗口哈希；哈希相等时再逐字符校验以防碰撞。平均 O(n) 完成匹配，且天然适合多模式同时搜索。","28-A2-4","一致性哈希",{"id":15760,"title":15761,"level":6350,"module":3259,"seq":998,"goal":15763,"keywords":15764,"explanation":15768,"examples":15780,"exercises":15788,"summary":15822,"next":15823,"nextTitle":15824},"理解一致性哈希：把节点与键都哈希到同一个圆环上，键沿顺时针找最近节点；引入虚拟节点使负载均衡，常用于分布式缓存扩容。",[15761,15765,15766,15767],"哈希环","虚拟节点","分布式",[15769,15774],{"title":15770,"content":15771,"codePy":15772,"codeCpp":15773},"1. 哈希环","将节点与键通过同一哈希函数映射到一个 0~2^32 圆环；键归属顺时针方向最近的节点。","def locate(key, nodes):\n    h=hash_ring(key); \n    nxt=min(n for n in nodes if n>=h)\n    return nxt","\u002F\u002F 顺时针最近",{"title":15775,"content":15776,"codePy":15777,"codeCpp":15778,"tip":15779},"2. 虚拟节点","每个物理节点映射成多个虚拟节点分散在环上，扩容\u002F缩容时只迁移邻近部分数据，且负载更均匀。","print(\"少量迁移+均衡\")","\u002F\u002F 分布式缓存基石","tip: 一致性哈希使节点增减只影响环上相邻区间，避免全量重映射。",[15781],{"title":15782,"prompt":15783,"analysis":15784,"codePy":15785,"codeCpp":15786,"run":15787},"例 1：扩容","一致性哈希加一个节点，影响范围？","仅相邻区间的键迁移。","print(\"局部\")","\u002F\u002F local","local",{"choice":15789,"coding":15808},[15790,15796,15802],{"id":38,"stem":15791,"options":15792,"answer":9,"explanation":15795},"一致性哈希键归属？",[1568,15793,843,15794],"顺时针最近节点","最小哈希","环上顺时针。",{"id":47,"stem":15797,"options":15798,"answer":9,"explanation":15801},"虚拟节点作用？",[6446,15799,15800,4356],"均衡负载","加密","分散更均匀。",{"id":56,"stem":15803,"options":15804,"answer":9,"explanation":15807},"加节点影响？",[15805,15806,6810,1568],"全量","局部区间","仅相邻数据迁移。",[15809],{"id":67,"title":15810,"prompt":15811,"inputFormat":15812,"outputFormat":15813,"samples":15814,"starterPy":15818,"starterCpp":15819,"hint":15820,"referencePy":15821,"referenceCpp":15819},"一致性哈希（模拟）","模拟一致性哈希：给定节点数 N 与虚拟节点倍数 R，输入若干键，输出每个键顺时针最近的物理节点编号（用简单哈希 h=x%M 映射到环）。","第一行 N R M q；接下来 q 行键整数。","q 行，每行键所属物理节点编号（0..N-1）。",[15815],{"input":15816,"output":15817},"3 5 100 3\n10\n20\n30","0\n1\n2","import sys\nN,R,M,q=map(int,sys.stdin.readline().split())\n# 虚拟节点位置: 物理 i 的第 k 个虚拟 = (i*R+k) % M\nvirtual=[]\nfor i in range(N):\n    for k in range(R): virtual.append(((i*R+k)%M, i))\nvirtual.sort()\nfor _ in range(q):\n    x=int(sys.stdin.readline()); x%=M\n    lo,hi=0,len(virtual)\n    # 找第一个 >= x 的虚拟位置\n    import bisect\n    pos=bisect.bisect_left(virtual, (x, -1))\n    if pos==len(virtual): pos=0\n    print(virtual[pos][1])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\n#include \u003Ctuple>\nusing namespace std;\nint main(){ int N,R,M,q; cin>>N>>R>>M>>q; vector\u003Cpair\u003Cint,int>> vir; for(int i=0;i\u003CN;i++) for(int k=0;k\u003CR;k++) vir.push_back({ (i*R+k)%M, i }); sort(vir.begin(),vir.end()); while(q--){ int x; cin>>x; x%=M; int pos=lower_bound(vir.begin(),vir.end(),make_pair(x,-1))-vir.begin(); if(pos==(int)vir.size()) pos=0; cout\u003C\u003Cvir[pos].second\u003C\u003Cendl; } }","虚拟节点排序，二分找顺时针最近。","import sys\nN,R,M,q=map(int,sys.stdin.readline().split())\nvirtual=[]\nfor i in range(N):\n    for k in range(R): virtual.append(((i*R+k)%M, i))\nvirtual.sort()\nimport bisect\nfor _ in range(q):\n    x=int(sys.stdin.readline()); x%=M\n    pos=bisect.bisect_left(virtual, (x, -1))\n    if pos==len(virtual): pos=0\n    print(virtual[pos][1])","一致性哈希把节点与键经同一哈希函数映射到一个哈希环，键沿顺时针方向归入最近的节点；为每个物理节点设置多个虚拟节点可使负载更均匀，且节点增减时只迁移环上相邻区间的数据——是分布式缓存扩容的基石。","28-A2-5","哈希综合应用",{"id":15823,"title":15824,"level":6350,"module":3259,"seq":1066,"goal":15826,"keywords":15827,"explanation":15831,"examples":15843,"exercises":15850,"summary":15876,"next":15877,"nextTitle":15878},"综合运用哈希：在查重、计数、缓存、字符串判等、集合判同等场景中正确选择哈希结构，并理解其时间\u002F空间权衡与碰撞应对。",[15828,15829,4166,15830],"哈希综合","查重","权衡",[15832,15837],{"title":15833,"content":15834,"codePy":15835,"codeCpp":15836},"1. 选结构","去重\u002F集合用哈希表（O(1) 期望）；计数用哈希映射；频率 TopK 用哈希+堆。","from collections import Counter\ndef topk(a, k):\n    c=Counter(a)\n    return [x for x,_ in c.most_common(k)]","\u002F\u002F 哈希计数+堆",{"title":15838,"content":15839,"codePy":15840,"codeCpp":15841,"tip":15842},"2. 权衡","哈希表以少量空间换 O(1) 期望；要注意负载因子与冲突；字符串判等优先用滚动哈希，集合判同优先排序或哈希集合。","print(\"O(1) 期望的代价\")","\u002F\u002F 负载因子\u002F碰撞","tip: 综合题常是哈希与其他算法（堆、并查集、DP）的组合。",[15844],{"title":15845,"prompt":15846,"analysis":15847,"codePy":15848,"codeCpp":15849,"run":9077},"例 1：查重","数组去重用？","哈希集合 O(n)。","print(\"set\")","\u002F\u002F set",{"choice":15851,"coding":15866},[15852,15857,15860],{"id":38,"stem":15853,"options":15854,"answer":9,"explanation":15856},"TopK 频率用什么？",[3117,15855,7901,6565],"哈希计数+堆","计数再取前k。",{"id":47,"stem":15858,"options":15859,"answer":9,"explanation":15438},"哈希表期望时间？",[2338,1245,1246,2339],{"id":56,"stem":15861,"options":15862,"answer":9,"explanation":15865},"数组去重推荐？",[15863,15864,2509,6040],"排序后双指针","哈希集合","set 去重 O(n)。",[15867],{"id":67,"title":4339,"prompt":15868,"inputFormat":1801,"outputFormat":6275,"samples":15869,"starterPy":15872,"starterCpp":15873,"hint":15874,"referencePy":15875,"referenceCpp":15873},"给定 n 个整数，若存在出现次数超过 n\u002F2 的元素，输出它（否则输出 -1）。用哈希计数实现。",[15870],{"input":15871,"output":759},"5\n2 2 1 2 2","n=int(input()); a=list(map(int,input().split())); c={}\nfor x in a: c[x]=c.get(x,0)+1\nans=-1\nfor k,v in c.items():\n    if v>n\u002F\u002F2: ans=k\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cunordered_map>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> a(n); for(int i=0;i\u003Cn;i++) cin>>a[i]; unordered_map\u003Cint,int> c; for(int x:a) c[x]++; int ans=-1; for(auto& kv:c) if(kv.second>n\u002F2) ans=kv.first; cout\u003C\u003Cans; }","哈希计数，超半数即可。","n=int(input()); a=list(map(int,input().split())); c={}\nfor x in a: c[x]=c.get(x,0)+1\nans=-1\nfor k,v in c.items():\n    if v>n\u002F\u002F2: ans=k\nprint(ans)","哈希的综合应用包括：去重\u002F集合（哈希集合、期望 O(1)）、计数（哈希映射）、频率 TopK（哈希+堆）、字符串判等（滚动哈希）。选择哈希结构时要权衡空间与冲突，多数场景以少量空间换取期望 O(1) 的查找\u002F插入。","30-A1-1","前缀和与差分",{"id":15877,"title":15878,"level":6350,"module":15880,"seq":9,"goal":15881,"keywords":15882,"explanation":15884,"examples":15896,"exercises":15901,"summary":15933,"next":15934,"nextTitle":15935},"区间与离线算法","掌握一维\u002F二维前缀和（O(1) 求区间和）与差分（O(1) 区间加），理解“前缀预处理 + 相减”与“差分数组还原”的互逆技巧。",[2622,4521,4212,15883],"区间加",[15885,15890],{"title":15886,"content":15887,"codePy":15888,"codeCpp":15889},"1. 前缀和","pref[i]=pref[i-1]+a[i]；区间 [l,r] 和=pref[r]-pref[l-1]，O(1) 查询。","def prefix(a):\n    n=len(a); p=[0]*(n+1)\n    for i in range(n): p[i+1]=p[i]+a[i]\n    return p","\u002F\u002F 相减得区间",{"title":15891,"content":15892,"codePy":15893,"codeCpp":15894,"tip":15895},"2. 差分","差分数组 d，区间 [l,r] 加 v：d[l]+=v, d[r+1]-=v；最后前缀和还原即每次更新后的数组。","def range_add(n, ops):\n    d=[0]*(n+1)\n    for l,r,v in ops: d[l]+=v; d[r+1]-=v\n    return list(accumulate(d[:n]))","\u002F\u002F 区间加 O(1)","tip: 多次区间加、少量查询用差分；多次查询、少量修改用前缀和（或树状数组）。",[15897],{"title":15898,"prompt":15899,"analysis":15900,"codePy":7942,"codeCpp":7943,"run":2707},"例 1：前缀和","a=[1,2,3,4]，区间 [2,4] 和？","2+3+4=9。",{"choice":15902,"coding":15920},[15903,15907,15913],{"id":38,"stem":15904,"options":15905,"answer":9,"explanation":15906},"前缀和区间查询？",[2338,1245,1246,2339],"相减即可。",{"id":47,"stem":15908,"options":15909,"answer":9,"explanation":15912},"差分用于？",[15910,15911,3117,15829],"单点改","多次区间加","区间加 O(1)。",{"id":56,"stem":15914,"options":15915,"answer":9,"explanation":15919},"pref[r]-pref[l-1] 得？",[15916,4212,15917,15918],"点值","前缀","差","区间 [l,r] 和。",[15921],{"id":67,"title":4453,"prompt":15922,"inputFormat":15923,"outputFormat":15924,"samples":15925,"starterPy":15929,"starterCpp":15930,"hint":15931,"referencePy":15932,"referenceCpp":15930},"给定 n 个整数与 q 次区间求和询问 [l,r]（1 起闭区间），用前缀和离线\u002F在线回答。","第一行 n q；第二行 n 个整数；接下来 q 行 l r。","q 行，每行区间和。",[15926],{"input":15927,"output":15928},"4 2\n1 2 3 4\n2 4\n1 4","9\n10","import sys\ninp=sys.stdin.read().split(); it=iter(inp)\nn,q=int(next(it)),int(next(it)); a=[0]+[int(next(it)) for _ in range(n)]\nfor i in range(1,n+1): a[i]+=a[i-1]\nfor _ in range(q):\n    l,r=int(next(it)),int(next(it)); print(a[r]-a[l-1])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,q; cin>>n>>q; vector\u003Clong long> a(n+1,0); for(int i=1;i\u003C=n;i++){ cin>>a[i]; a[i]+=a[i-1]; } while(q--){ int l,r; cin>>l>>r; cout\u003C\u003Ca[r]-a[l-1]\u003C\u003Cendl; } }","前缀和相减。","import sys\ninp=sys.stdin.read().split(); it=iter(inp)\nn,q=int(next(it)),int(next(it)); a=[0]+[int(next(it)) for _ in range(n)]\nfor i in range(1,n+1): a[i]+=a[i-1]\nfor _ in range(q):\n    l,r=int(next(it)),int(next(it)); print(a[r]-a[l-1])","前缀和通过 pref[i]=pref[i-1]+a[i] 预处理，使区间和查询 O(1)（pref[r]-pref[l-1]）；差分则在差分数组上用 d[l]+=v、d[r+1]-=v 实现 O(1) 区间加、最后前缀和还原。二者互逆，分别适合多查询少更新与多更新少查询。","30-A1-2","离线算法（扫描线）",{"id":15934,"title":15935,"level":6350,"module":15880,"seq":63,"goal":15937,"keywords":15938,"explanation":15943,"examples":15955,"exercises":15960,"summary":15991,"next":15992,"nextTitle":15993},"理解离线思想：先把所有询问\u002F事件收集，按某种顺序（如坐标、时间）统一处理，常比逐个在线处理更高效。以扫描线求矩形并面积、区间计数引入。",[15939,15940,15941,15942],"离线算法","扫描线","排序事件","时间换序",[15944,15949],{"title":15945,"content":15946,"codePy":15947,"codeCpp":15948},"1. 离线思路","在线难以维护时，把所有操作按关键字排序一次性处理（如差分前缀和统计区间覆盖次数）。","def offline_count(ops):\n    ev=[]\n    for l,r in ops: ev.append((l,1)); ev.append((r+1,-1))\n    ev.sort()\n    cur=0; res=[]\n    for x,d in ev: cur+=d; res.append((x,cur))\n    return res","\u002F\u002F 事件排序",{"title":15950,"content":15951,"codePy":15952,"codeCpp":15953,"tip":15954},"2. 扫描线","把区间加\u002F减拆成事件点，按坐标扫描累计，可在 O(n log n) 求覆盖、矩形并等；配合离散化。","print(\"按序处理事件\")","\u002F\u002F 离线减复杂度","tip: 离线常需要“所有查询已知”，若强制在线则需树状数组\u002F线段树。",[15956],{"title":15957,"prompt":15958,"analysis":15959,"codePy":6253,"codeCpp":5851,"run":759},"例 1：覆盖","区间 [1,3]、[2,4]，点 2 被覆盖几次？","2 次。",{"choice":15961,"coding":15980},[15962,15969,15975],{"id":38,"stem":15963,"options":15964,"answer":9,"explanation":15968},"离线算法特点？",[15965,15966,1568,15967],"实时","先收全部再统一处理","在线","已知所有询问。",{"id":47,"stem":15970,"options":15971,"answer":9,"explanation":15974},"扫描线把区间拆成？",[15972,15973,7901,6210],"点","事件(±1)","加\u002F减事件。",{"id":56,"stem":15976,"options":15977,"answer":9,"explanation":15979},"离线常用？",[7901,15978,2509,6040],"排序+扫描","事件排序处理。",[15981],{"id":67,"title":15982,"prompt":15983,"inputFormat":7495,"outputFormat":6275,"samples":15984,"starterPy":15987,"starterCpp":15988,"hint":15989,"referencePy":15990,"referenceCpp":15988},"区间覆盖次数","给定 n 个闭区间 [l,r]（1 起），求所有整数点上被覆盖区间数的最大值。",[15985],{"input":15986,"output":759},"2\n1 3\n2 4","n=int(input()); ev=[]\nfor _ in range(n):\n    l,r=map(int,input().split()); ev.append((l,1)); ev.append((r+1,-1))\nev.sort(); cur=ans=0\nfor x,d in ev:\n    cur+=d; ans=max(ans,cur)\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cpair\u003Cint,int>> ev; for(int i=0,l,r;i\u003Cn;i++){ cin>>l>>r; ev.push_back({l,1}); ev.push_back({r+1,-1}); } sort(ev.begin(),ev.end()); int cur=0,ans=0; for(auto& e:ev){ cur+=e.second; ans=max(ans,cur); } cout\u003C\u003Cans; }","差分事件 + 前缀累计。","n=int(input()); ev=[]\nfor _ in range(n):\n    l,r=map(int,input().split()); ev.append((l,1)); ev.append((r+1,-1))\nev.sort(); cur=ans=0\nfor x,d in ev:\n    cur+=d; ans=max(ans,cur)\nprint(ans)","离线算法先把所有操作\u002F询问收集并按关键字（坐标、时间）排序后统一处理，常比在线更简单高效；扫描线把每个区间拆成“+1\u002F-1”事件，按坐标扫描累计，可在 O(n log n) 求区间覆盖次数、矩形并面积等，配合离散化处理大坐标。","30-A1-3","区间综合与复习",{"id":15992,"title":15993,"level":6350,"module":15880,"seq":703,"goal":15995,"keywords":15996,"explanation":16000,"examples":16012,"exercises":16020,"summary":16047,"next":16048,"nextTitle":16049},"综合区间类问题：区分“多查询少更新”（前缀和）、“多更新少查询”（差分）、“在线动态”（树状数组\u002F线段树），并能离线处理复杂区间统计。",[15997,13732,15998,15999],"区间综合","线段树","方法选择",[16001,16006],{"title":16002,"content":16003,"codePy":16004,"codeCpp":16005},"1. 方法选择","静态区间和→前缀和；区间加+点查询→差分；动态区间和\u002F修改→树状数组或线段树（O(log n)）。","print(\"按读写模式选型\")","\u002F\u002F 静态\u002F差分\u002F动态",{"title":16007,"content":16008,"codePy":16009,"codeCpp":16010,"tip":16011},"2. 进阶","线段树还能维护区间最值、区间赋值、懒标记；离线排序能把许多区间题转为扫描线。复习前两个单元完成本阶收尾。","print(\"线段树覆盖更广\")","\u002F\u002F 懒标记","tip: 区间问题是 GESP-7 的收尾，下接 GESP-8 的更复杂数据结构。",[16013],{"title":16014,"prompt":16015,"analysis":16016,"codePy":16017,"codeCpp":16018,"run":16019},"例 1：选型","q 次区间加、每次问某点值，用？","差分 O(1)+前缀还原。","print(\"差分\")","\u002F\u002F diff","diff",{"choice":16021,"coding":16034},[16022,16026,16030],{"id":38,"stem":16023,"options":16024,"answer":9,"explanation":16025},"动态区间和在线修改用？",[2622,13732,4521,3117],"BIT\u002F线段树。",{"id":47,"stem":16027,"options":16028,"answer":9,"explanation":16029},"区间加+点查用？",[15998,4521,3259,6210],"差分 O(1)。",{"id":56,"stem":16031,"options":16032,"answer":9,"explanation":16033},"静态区间和用？",[15998,2622,11309,8488],"O(1) 查询。",[16035],{"id":67,"title":16036,"prompt":16037,"inputFormat":16038,"outputFormat":16039,"samples":16040,"starterPy":16043,"starterCpp":16044,"hint":16045,"referencePy":16046,"referenceCpp":16044},"树状数组（动态区间和）","n 个元素，q 次操作：add i v 单点加 v；sum l r 查询区间和（1 起）。演示树状数组。","第一行 n q；第二行 n 个初始值；接下来 q 行操作。","每个 sum 输出一行整数。",[16041],{"input":16042,"output":7594},"3 2\n1 2 3\nadd 2 5\nsum 1 3","import sys\ninp=sys.stdin.read().split(); it=iter(inp)\nn,q=int(next(it)),int(next(it)); a=[0]+[int(next(it)) for _ in range(n)]\nclass BIT:\n    def __init__(self,n): self.t=[0]*(n+1)\n    def add(self,i,v):\n        while i\u003C=n: self.t[i]+=v; i+=i&-i\n    def q(self,i):\n        s=0\n        while i>0: s+=self.t[i]; i-=i&-i\n        return s\nbit=BIT(n)\nfor i in range(1,n+1): bit.add(i,a[i])\nfor _ in range(q):\n    op=next(it)\n    if op==\"add\": i,v=int(next(it)),int(next(it)); bit.add(i,v)\n    else: l,r=int(next(it)),int(next(it)); print(bit.q(r)-bit.q(l-1))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct BIT{ vector\u003Clong long> t; int n; BIT(int n):t(n+1,0),n(n){} void add(int i,long long v){ for(;i\u003C=n;i+=i&-i) t[i]+=v;} long long q(int i){ long long s=0; for(;i>0;i-=i&-i) s+=t[i]; return s;} };\nint main(){ int n,q; cin>>n>>q; BIT bit(n); for(int i=1;i\u003C=n;i++){ long long x; cin>>x; bit.add(i,x);} while(q--){ string op; cin>>op; if(op==\"add\"){ int i; long long v; cin>>i>>v; bit.add(i,v);} else { int l,r; cin>>l>>r; cout\u003C\u003Cbit.q(r)-bit.q(l-1)\u003C\u003Cendl; } } }","树状数组单点改、区间查。","import sys\ninp=sys.stdin.read().split(); it=iter(inp)\nn,q=int(next(it)),int(next(it)); a=[0]+[int(next(it)) for _ in range(n)]\nclass BIT:\n    def __init__(self,n): self.t=[0]*(n+1)\n    def add(self,i,v):\n        while i\u003C=n: self.t[i]+=v; i+=i&-i\n    def q(self,i):\n        s=0\n        while i>0: s+=self.t[i]; i-=i&-i\n        return s\nbit=BIT(n)\nfor i in range(1,n+1): bit.add(i,a[i])\nfor _ in range(q):\n    op=next(it)\n    if op==\"add\": i,v=int(next(it)),int(next(it)); bit.add(i,v)\n    else: l,r=int(next(it)),int(next(it)); print(bit.q(r)-bit.q(l-1))","区间类问题按读写模式选型：静态区间和用前缀和 O(1) 查询；区间加+点查询用差分；在线动态单点修改\u002F区间和用树状数组或线段树（O(log n)，线段树还能处理区间赋值与懒标记）；许多复杂区间统计可离线转扫描线。至此 GESP-7 区间与离线单元收尾，下接 GESP-8 更高级数据结构。","39-A1-1","线段树基础（建树\u002F单点改\u002F区间和）",{"id":16051,"title":16052,"level":16053,"module":16054,"seq":9,"goal":16055,"keywords":16056,"explanation":16060,"examples":16071,"exercises":16077,"summary":16107,"next":16108,"nextTitle":16109},"32-A1-1","Treap（旋转平衡）","Lv.8","平衡树与可持久化","掌握 Treap：给每个节点一个随机优先级，按“堆序（优先级）保持平衡、BST 序维护查找”通过旋转保持平衡，支持插入\u002F删除\u002F排名等。",[16057,16058,16059,10363],"Treap","旋转","随机优先级",[16061,16066],{"title":16062,"content":16063,"codePy":16064,"codeCpp":16065},"1. 双序约束","节点同时满足：左子树 key 小、右子树 key 大（BST）；父优先级优于子（堆）。插入新节点时按 BST 找到位置，再用旋转把它“旋”到满足堆序。","def rotate():  # 维持 堆序\n    if cur.pri \u003C parent.pri: 旋转","\u002F\u002F 优先级小则上旋",{"title":16067,"content":16068,"codePy":16069,"codeCpp":16070},"2. 旋转修复","插入按 BST 落位，回溯时若子节点优先级更高则向父旋转（右孩子高则左旋、左孩子高则右旋），使堆序恢复；平均 O(log n)。","def insert(t,k):\n    if not t: return Node(k)\n    if k\u003Ct.k: t.l=insert(t.l,k); t=rotateR_if(t)\n    else: t.r=insert(t.r,k); t=rotateL_if(t)\n    return t","\u002F\u002F 插完沿路旋转修正",[16072],{"title":16073,"prompt":16074,"analysis":16075,"codePy":16076,"codeCpp":5315,"run":74},"例 1：插入序列","依次插入 3,1,2，中序遍历？","BST 中序有序：1,2,3。","print(\"1 2 3\")",{"choice":16078,"coding":16095},[16079,16086,16091],{"id":38,"stem":16080,"options":16081,"answer":9,"explanation":16085},"Treap 平衡靠？",[16082,16059,16083,16084],"手动调平","AVL 因子","红黑","优先级构成堆。",{"id":47,"stem":16087,"options":16088,"answer":9,"explanation":16090},"违反堆序时？",[16089,16058,9602,3560],"重排","旋到正确位置。",{"id":56,"stem":16092,"options":16093,"answer":9,"explanation":16094},"Treap 期望复杂度？",[2338,1246,1245,2339],"随机期望平衡。",[16096],{"id":67,"title":16097,"prompt":16098,"inputFormat":1801,"outputFormat":16099,"samples":16100,"starterPy":16103,"starterCpp":16104,"hint":16105,"referencePy":16106,"referenceCpp":16104},"Treap 插入与第 k 小","依次插入 n 个整数，每次插入后输出当前所有数中的第 k 小（k=插入序号，即第 i 次输出第 i 小）。","n 行，每行第 i 小。",[16101],{"input":6278,"output":16102},"3\n1\n2","import random\nclass Node:\n    def __init__(s,k): s.k=k; s.pri=random.randint(1,10**9); s.l=s.r=None; s.c=1\ndef sz(t): return t.c if t else 0\ndef upd(t):\n    if t: t.c=1+sz(t.l)+sz(t.r)\ndef rotR(y):\n    x=y.l; y.l=x.r; x.r=y; upd(y); upd(x); return x\ndef rotL(x):\n    y=x.r; x.r=y.l; y.l=x; upd(x); upd(y); return y\ndef insert(t,k):\n    if not t: return Node(k)\n    if k\u003Ct.k: t.l=insert(t.l,k);  t=rotR(t) if t.l and t.l.pri>t.pri else t\n    else: t.r=insert(t.r,k); t=rotL(t) if t.r and t.r.pri>t.pri else t\n    upd(t); return t\ndef kth(t,k):\n    while t:\n        if sz(t.l)>=k: t=t.l\n        elif sz(t.l)+1==k: return t.k\n        else: k-=sz(t.l)+1; t=t.r\n    return 0\nroot=None\nfor i,x in enumerate(map(int,input().split())):\n    root=insert(root,x); print(kth(root,i+1))\n","#include \u003Ciostream>\n#include \u003Ccstdlib>\nusing namespace std;\nstruct Node{int key,pri,c;Node*l,*r;Node(int k):key(k),pri(rand()),c(1),l(0),r(0){}};\nint sz(Node*t){return t?t->c:0;}\nvoid upd(Node*t){if(t)t->c=1+sz(t->l)+sz(t->r);}\nNode* rotR(Node*y){Node*x=y->l;y->l=x->r;x->r=y;upd(y);upd(x);return x;}\nNode* rotL(Node*x){Node*y=x->r;x->r=y->l;y->l=x;upd(x);upd(y);return y;}\nNode* insert(Node*t,int k){ if(!t)return new Node(k); if(k\u003Ct->key){t->l=insert(t->l,k);if(t->l&&t->l->pri>t->pri)t=rotR(t);}else{t->r=insert(t->r,k);if(t->r&&t->r->pri>t->pri)t=rotL(t);}upd(t);return t;}\nint kth(Node*t,int k){while(t){if(sz(t->l)>=k)t=t->l;else if(sz(t->l)+1==k)return t->key;else{k-=sz(t->l)+1;t=t->r;}}return 0;}\nint main(){srand(12345);int n;cin>>n;Node*root=0;for(int i=0;i\u003Cn;i++){int x;cin>>x;root=insert(root,x);cout\u003C\u003Ckth(root,i+1)\u003C\u003C\"\\n\";}}","旋转 Treap：插入按 BST，回溯旋转修正堆序。","import random\nclass Node:\n    def __init__(s,k): s.k=k; s.pri=random.randint(1,10**9); s.l=s.r=None; s.c=1\ndef sz(t): return t.c if t else 0\ndef upd(t):\n    if t: t.c=1+sz(t.l)+sz(t.r)\ndef rotR(y):\n    x=y.l; y.l=x.r; x.r=y; upd(y); upd(x); return x\ndef rotL(x):\n    y=x.r; x.r=y.l; y.l=x; upd(x); upd(y); return y\ndef insert(t,k):\n    if not t: return Node(k)\n    if k\u003Ct.k: t.l=insert(t.l,k);  t=rotR(t) if t.l and t.l.pri>t.pri else t\n    else: t.r=insert(t.r,k); t=rotL(t) if t.r and t.r.pri>t.pri else t\n    upd(t); return t\ndef kth(t,k):\n    while t:\n        if sz(t.l)>=k: t=t.l\n        elif sz(t.l)+1==k: return t.k\n        else: k-=sz(t.l)+1; t=t.r\n    return 0\nroot=None\nfor i,x in enumerate(map(int,input().split())):\n    root=insert(root,x); print(kth(root,i+1))","Treap 用随机优先级+堆序保平衡，BST 序保证可查找，插入\u002F删除靠旋转维持；平均 O(log n)，可支持排名、前驱后继等。","32-A1-2","无旋 Treap（fhq Treap）",{"id":16108,"title":16109,"level":16053,"module":16054,"seq":63,"goal":16111,"keywords":16112,"explanation":16117,"examples":16128,"exercises":16133,"summary":16166,"next":16167,"nextTitle":16168},"掌握无旋 Treap：用 split（按值\u002F排名切分）与 merge（按优先级合并）完成所有操作，无需旋转，代码简洁且天然支持可持久化。",[16113,16114,16115,16116],"fhq Treap","split","merge","无旋",[16118,16123],{"title":16119,"content":16120,"codePy":16121,"codeCpp":16122},"1. split 与 merge","split(t,k) 按 key≤k 把树分成左右两棵；merge(a,b) 要求 a 全小 b，按优先级把根连成堆。两函数递归实现，是 fhq 的全部基础。","def split(t,k):\n    if not t: return (None,None)\n    if t.k\u003C=k: l,r=split(t.r,k); t.r=l; return (t,r)\n    l,r=split(t.l,k); t.l=r; return (l,t)","\u002F\u002F 按 key 切分两棵树",{"title":16124,"content":16125,"codePy":16126,"codeCpp":16127},"2. 操作构造","插入：split 出 \u003Cx 与 ≥x，merge(merge(L,新节点),R)；删除：split 出 x，再 split 出 >x，丢掉中间的 x；第 k 小即 kth。","def insert(root,x):\n    L,R=split(root,x-1); M,RR=split(R,x)\n    return merge(merge(L,Node(x)),RR)","\u002F\u002F split\u002Fmerge 拼出插入",[16129],{"title":16130,"prompt":16131,"analysis":16132,"codePy":7705,"codeCpp":7706,"run":290},"例 1：插入删除","插入 2,5，删除 2，剩余？","只剩 5。",{"choice":16134,"coding":16153},[16135,16142,16147],{"id":38,"stem":16136,"options":16137,"answer":9,"explanation":16141},"fhq Treap 核心函数？",[16058,16138,16139,16140],"split\u002Fmerge","splay","旋转+调整","切分与合并。",{"id":47,"stem":16143,"options":16144,"answer":9,"explanation":16146},"插入的实现？",[16058,16145,9602,10191],"split 出两段再 merge","拼接新节点。",{"id":56,"stem":16148,"options":16149,"answer":9,"explanation":16152},"fhq 相比旋转 Treap 优点？",[1448,16150,4864,16151],"代码简、易持久化","无差别","无旋易扩展。",[16154],{"id":67,"title":16155,"prompt":16156,"inputFormat":15456,"outputFormat":16157,"samples":16158,"starterPy":16162,"starterCpp":16163,"hint":16164,"referencePy":16165,"referenceCpp":16163},"fhq Treap 基本操作","依次执行操作：1 x 插入 x；2 x 删除一个 x（若存在）。最后中序输出所有数。","一行中序序列（升序）。",[16159],{"input":16160,"output":16161},"4\n1 2\n1 5\n2 2\n1 3","3 5","import random\nclass Node:\n    def __init__(s,k): s.k=k; s.pri=random.randint(1,10**9); s.l=s.r=None; s.c=1\ndef sz(t): return t.c if t else 0\ndef upd(t):\n    if t: t.c=1+sz(t.l)+sz(t.r)\ndef split(t,k):\n    if not t: return (None,None)\n    if t.k\u003C=k: l,r=split(t.r,k); t.r=l; upd(t); return (t,r)\n    l,r=split(t.l,k); t.l=r; upd(t); return (l,t)\ndef merge(a,b):\n    if not a or not b: return a or b\n    if a.pri>b.pri: a.r=merge(a.r,b); upd(a); return a\n    b.l=merge(a,b.l); upd(b); return b\ndef inorder(t):\n    if t: inorder(t.l); res.append(t.k); inorder(t.r)\nroot=None; res=[]\nfor _ in range(int(input())):\n    op,x=map(int,input().split())\n    if op==1: L,R=split(root,x-1); M,RR=split(R,x); root=merge(merge(L,Node(x)),RR)\n    else: L,R=split(root,x-1); M,RR=split(R,x); root=merge(L,RR)\ninorder(root); print(*res)\n","#include \u003Ciostream>\n#include \u003Ccstdlib>\nusing namespace std;\nstruct Node{int k,pri,c;Node*l,*r;Node(int x):k(x),pri(rand()),c(1),l(0),r(0){}};\nint sz(Node*t){return t?t->c:0;}\nvoid upd(Node*t){if(t)t->c=1+sz(t->l)+sz(t->r);}\nvoid split(Node*t,int k,Node*&a,Node*&b){if(!t){a=b=0;return;}if(t->k\u003C=k){split(t->r,k,t->r,b);a=t;}else{split(t->l,k,a,t->l);b=t;}upd(a);upd(b);}\nNode* merge(Node*a,Node*b){if(!a||!b)return a?a:b;if(a->pri>b->pri){a->r=merge(a->r,b);upd(a);return a;}b->l=merge(a,b->l);upd(b);return b;}\nvoid inorder(Node*t){if(t){inorder(t->l);cout\u003C\u003Ct->k\u003C\u003C\" \";inorder(t->r);}}\nint main(){srand(12345);int q;cin>>q;Node*root=0;while(q--){int op,x;cin>>op>>x;Node*a,*b,*m,*rr;if(op==1){split(root,x-1,a,b);split(b,x,m,rr);root=merge(merge(a,new Node(x)),rr);}else{split(root,x-1,a,b);split(b,x,m,rr);root=merge(a,rr);}}inorder(root);}","split\u002Fmerge 实现插入与删除。","import random\nclass Node:\n    def __init__(s,k): s.k=k; s.pri=random.randint(1,10**9); s.l=s.r=None; s.c=1\ndef sz(t): return t.c if t else 0\ndef upd(t):\n    if t: t.c=1+sz(t.l)+sz(t.r)\ndef split(t,k):\n    if not t: return (None,None)\n    if t.k\u003C=k: l,r=split(t.r,k); t.r=l; upd(t); return (t,r)\n    l,r=split(t.l,k); t.l=r; upd(t); return (l,t)\ndef merge(a,b):\n    if not a or not b: return a or b\n    if a.pri>b.pri: a.r=merge(a.r,b); upd(a); return a\n    b.l=merge(a,b.l); upd(b); return b\ndef inorder(t):\n    if t: inorder(t.l); res.append(t.k); inorder(t.r)\nroot=None; res=[]\nfor _ in range(int(input())):\n    op,x=map(int,input().split())\n    if op==1: L,R=split(root,x-1); M,RR=split(R,x); root=merge(merge(L,Node(x)),RR)\n    else: L,R=split(root,x-1); M,RR=split(R,x); root=merge(L,RR)\ninorder(root); print(*res)","无旋 Treap 用 split\u002Fmerge 二元操作完成插入、删除、第 k 小等，无需旋转、代码简洁，并且因为每次操作只“新建”节点而不改旧节点，天然支持可持久化。","32-A1-3","可持久化线段树",{"id":16167,"title":16168,"level":16053,"module":16054,"seq":703,"goal":16170,"keywords":16171,"explanation":16175,"examples":16186,"exercises":16191,"summary":16228,"next":16229,"nextTitle":16230},"掌握可持久化线段树：每次修改只复制受影响的节点路径（而非整棵树），从而得到每个历史版本的线段树，支持“回到旧版本”查询。",[16172,15998,16173,16174],"可持久化","版本","节点复用",[16176,16181],{"title":16177,"content":16178,"codePy":16179,"codeCpp":16180},"1. 只复制路径","更新位置 p 时，从根往下到叶子新建沿途节点（复制旧节点值再改），未访问的子树直接指向旧版本子树，空间 O(修改次数·log n)。","def update(pre,pos,val):\n    u=clone(pre)\n    if l==r: u.v=val; return u\n    if pos\u003C=m: u.lc=update(pre.lc,pos,val)\n    else: u.rc=update(pre.rc,pos,val)\n    return u","\u002F\u002F 复制路径，复用未改子树",{"title":16182,"content":16183,"codePy":16184,"codeCpp":16185},"2. 多版本管理","用一个 roots 数组保存每次操作后的根；查询第 t 个版本即从此根出发，旧版本完全不受影响，可随时回退。","roots=[build()]\nroots.append(update(roots[-1],p,v))","\u002F\u002F roots[t] 即版本 t 根",[16187],{"title":16188,"prompt":16189,"analysis":16190,"codePy":7942,"codeCpp":7943,"run":2707},"例 1：两版本","a=[5,5]，版本1把 a[1] 改为 9，版本1 的 a[1]？","9（版本0仍为 5）。",{"choice":16192,"coding":16214},[16193,16201,16206],{"id":38,"stem":16194,"options":16195,"answer":9,"explanation":16200},"可持久化更新复制？",[16196,16197,16198,16199],"整棵树","仅受影响路径","不复制","全部清空","路径新建，子树复用。",{"id":47,"stem":16202,"options":16203,"answer":9,"explanation":16205},"空间复杂度？",[2338,16204,2339,1245],"O(操作·log n)","每版只加 log 节点。",{"id":56,"stem":16207,"options":16208,"answer":9,"explanation":16213},"旧版本查询？",[16209,16210,16211,16212],"被覆盖","从对应根访问","失效","需重建","每版本根都保留。",[16215],{"id":67,"title":16216,"prompt":16217,"inputFormat":16218,"outputFormat":16219,"samples":16220,"starterPy":16224,"starterCpp":16225,"hint":16226,"referencePy":16227,"referenceCpp":16225},"可持久化点修改","数组 a[1..n]，q 次操作：1 p v 把位置 p 改成 v 形成新版本；2 t p 查询版本 t 的位置 p 的值（版本 0 为初始数组）。","第一行 n；第二行 a；第三行 q；接下来 q 行操作。","每个 2 操作一行整数。",[16221],{"input":16222,"output":16223},"2\n5 5\n3\n1 1 9\n2 1 1\n2 0 1","9\n5","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nN=4*n+5\ndef build(l,r):\n    u=len(T); T.append([0,0,0])\n    if l==r: T[u][0]=a[l]; return u\n    m=(l+r)\u002F\u002F2; T[u][1]=build(l,m); T[u][2]=build(m+1,r); return u\ndef update(pre,pos,val,l,r):\n    u=len(T); T.append(T[pre][:]); \n    if l==r: T[u][0]=val; return u\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: T[u][1]=update(T[pre][1],pos,val,l,m)\n    else: T[u][2]=update(T[pre][2],pos,val,m+1,r)\n    return u\ndef query(u,pos,l,r):\n    if l==r: return T[u][0]\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: return query(T[u][1],pos,l,m)\n    return query(T[u][2],pos,m+1,r)\nT=[]; root=build(1,n); roots=[root]\nq=int(input())\nfor _ in range(q):\n    op=list(map(int,input().split()))\n    if op[0]==1: roots.append(update(roots[-1],op[1],op[2],1,n))\n    else: print(query(roots[op[1]],op[2],1,n))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{int v,lc,rc;}; vector\u003CNode> T;\nint build(int l,int r,const vector\u003Cint>& a){ int u=T.size(); T.push_back({0,0,0}); if(l==r){T[u].v=a[l];return u;} int m=(l+r)\u002F2; T[u].lc=build(l,m,a); T[u].rc=build(m+1,r,a); return u; }\nint update(int pre,int l,int r,int pos,int val){ int u=T.size(); T.push_back(T[pre]); if(l==r){T[u].v=val;return u;} int m=(l+r)\u002F2; if(pos\u003C=m) T[u].lc=update(T[u].lc,l,m,pos,val); else T[u].rc=update(T[u].rc,m+1,r,pos,val); return u; }\nint query(int u,int l,int r,int pos){ if(l==r) return T[u].v; int m=(l+r)\u002F2; if(pos\u003C=m) return query(T[u].lc,l,m,pos); return query(T[u].rc,m+1,r,pos); }\nint main(){ int n; cin>>n; vector\u003Cint> a(n+1); for(int i=1;i\u003C=n;i++) cin>>a[i]; T.push_back({0,0,0}); int root=build(1,n,a); vector\u003Cint> roots; roots.push_back(root); int q; cin>>q; while(q--){ int op; cin>>op; if(op==1){ int p,v; cin>>p>>v; roots.push_back(update(roots.back(),1,n,p,v)); } else { int t,p; cin>>t>>p; cout\u003C\u003Cquery(roots[t],1,n,p)\u003C\u003C\"\\n\"; } } }","每次修改克隆路径，roots 保存各版本根。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nT=[]\ndef build(l,r):\n    u=len(T); T.append([0,0,0])\n    if l==r: T[u][0]=a[l]; return u\n    m=(l+r)\u002F\u002F2; T[u][1]=build(l,m); T[u][2]=build(m+1,r); return u\ndef update(pre,pos,val,l,r):\n    u=len(T); T.append(T[pre][:]); \n    if l==r: T[u][0]=val; return u\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: T[u][1]=update(T[pre][1],pos,val,l,m)\n    else: T[u][2]=update(T[pre][2],pos,val,m+1,r)\n    return u\ndef query(u,pos,l,r):\n    if l==r: return T[u][0]\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: return query(T[u][1],pos,l,m)\n    return query(T[u][2],pos,m+1,r)\nroot=build(1,n); roots=[root]\nq=int(input())\nfor _ in range(q):\n    op=list(map(int,input().split()))\n    if op[0]==1: roots.append(update(roots[-1],op[1],op[2],1,n))\n    else: print(query(roots[op[1]],op[2],1,n))","可持久化线段树每次修改只复制从根到目标的路径节点、未变子树直接复用旧版本，从而以 O(修改·log n) 空间保存全部历史版本，可随时回退查询。","32-A1-4","主席树（区间第 k 小）",{"id":16229,"title":16230,"level":16053,"module":16054,"seq":770,"goal":16232,"keywords":16233,"explanation":16237,"examples":16248,"exercises":16253,"summary":16283,"next":16284,"nextTitle":16285},"掌握主席树：对静态数组每个前缀建一棵权值线段树（可持久化），查询区间 [l,r] 第 k 小时用 roots[r]−roots[l−1] 两版本相减定位，O(log V)。",[16234,16235,16172,16236],"主席树","区间第k小","前缀版本",[16238,16243],{"title":16239,"content":16240,"codePy":16241,"codeCpp":16242},"1. 前缀建树","把数组值离散化到 [1,M]；roots[i] 为前 i 个数构成的可持久化权值线段树。插入第 i 个数只新建一条路径，版本间复用子树。","roots=[build()]\nfor i in 1..n: roots.append(insert(roots[i-1], val[i]))","\u002F\u002F 每个前缀一棵可持久化权值树",{"title":16244,"content":16245,"codePy":16246,"codeCpp":16247},"2. 区间查询","区间 [l,r] 第 k 小 = 在 roots[r] 与 roots[l-1] 上同时走：左子树“数量差”≥k 进左，否则进右并减差。","def qry(u,v,k):\n    if l==r: return l\n    left=cnt[u.lc]-cnt[v.lc]\n    if left>=k: return qry(u.lc,v.lc,k)\n    return qry(u.rc,v.rc,k-left)","\u002F\u002F 两版本相减定位第 k 小",[16249],{"title":16250,"prompt":16251,"analysis":16252,"codePy":7878,"codeCpp":7879,"run":892},"例 1：区间第 2 小","a=[3,1,4,1,5]，区间[1,5] 第 2 小？","排序 1,1,3,4,5，第 2 小=1。",{"choice":16254,"coding":16270},[16255,16260,16265],{"id":38,"stem":16256,"options":16257,"answer":9,"explanation":16259},"主席树每个版本对应？",[16258,15917,4797,5833],"单个数","前缀可持久化权值树。",{"id":47,"stem":16261,"options":16262,"answer":607,"explanation":16264},"区间 [l,r] 第 k 小用？",[16263,3117,3259,2509],"两版本相减","roots[r]-roots[l-1]。",{"id":56,"stem":16266,"options":16267,"answer":9,"explanation":16269},"主席树查询复杂度？",[2338,16268,11317,1245],"O(log V)","树高 log 值域。",[16271],{"id":67,"title":16272,"prompt":16273,"inputFormat":16274,"outputFormat":16275,"samples":16276,"starterPy":16279,"starterCpp":16280,"hint":16281,"referencePy":16282,"referenceCpp":16280},"静态区间第 k 小","给定静态数组 a[1..n]，q 次查询：对区间 [l,r] 求第 k 小。","第一行 n；第二行 a；第三行 q；接下来 q 行 l r k。","每个查询一行整数。",[16277],{"input":16278,"output":892},"5\n3 1 4 1 5\n1\n1 5 2","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nb=sorted(set(a)); M=len(b)\ndef yid(x): return b.index(x)+1\nT=[]\ndef build(l,r):\n    u=len(T); T.append([0,0,0]); \n    if l==r: return u\n    m=(l+r)\u002F\u002F2; T[u][1]=build(l,m); T[u][2]=build(m+1,r); return u\ndef upd(pre,pos):\n    u=len(T); T.append(T[pre][:]); T[u][0]+=1\n    if l==r: return u\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: T[u][1]=upd(T[pre][1],pos)\n    else: T[u][2]=upd(T[pre][2],pos)\n    return u\ndef qry(u,v,k,l,r):\n    if l==r: return l\n    m=(l+r)\u002F\u002F2\n    left=T[T[u][1]][0]-T[T[v][1]][0]\n    if left>=k: return qry(T[u][1],T[v][1],k,l,m)\n    return qry(T[u][2],T[v][2],k-left,m+1,r)\nroot=build(1,M); roots=[root]\nfor i in range(1,n+1): roots.append(upd(roots[i-1],yid(a[i])))\nq=int(input())\nfor _ in range(q):\n    l,r,k=map(int,input().split()); print(b[qry(roots[r],roots[l-1],k,1,M)-1])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> a,b;\nstruct Node{int c,lc,rc;}; vector\u003CNode> T;\nint build(int l,int r){ int u=T.size(); T.push_back({0,0,0}); if(l==r) return u; int m=(l+r)\u002F2; T[u].lc=build(l,m); T[u].rc=build(m+1,r); return u; }\nint upd(int pre,int l,int r,int pos){ int u=T.size(); T.push_back(T[pre]); T[u].c++; if(l==r) return u; int m=(l+r)\u002F2; if(pos\u003C=m) T[u].lc=upd(T[u].lc,l,m,pos); else T[u].rc=upd(T[u].rc,m+1,r,pos); return u; }\nint qry(int u,int v,int l,int r,int k){ if(l==r) return l; int m=(l+r)\u002F2; int left=T[T[u].lc].c-T[T[v].lc].c; if(left>=k) return qry(T[u].lc,T[v].lc,l,m,k); return qry(T[u].rc,T[v].rc,m+1,r,k-left); }\nint main(){ cin>>n; a.resize(n+1); b.resize(n+1); for(int i=1;i\u003C=n;i++){cin>>a[i];b[i]=a[i];} sort(b.begin()+1,b.end()); b.erase(unique(b.begin()+1,b.end()),b.end()); int M=b.size()-1; vector\u003Cint> root(M+1,0); T.push_back({0,0,0}); root[0]=build(1,M); for(int i=1;i\u003C=n;i++){ int x=lower_bound(b.begin()+1,b.end(),a[i])-b.begin(); root[i]=upd(root[i-1],1,M,x); } int q; cin>>q; while(q--){ int l,r,k; cin>>l>>r>>k; int id=qry(root[r],root[l-1],1,M,k); cout\u003C\u003Cb[id]\u003C\u003C\"\\n\"; } }","前缀可持久化权值树，区间查询两版本相减。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nb=sorted(set(a)); M=len(b)\ndef yid(x): return b.index(x)+1\nT=[]\ndef build(l,r):\n    u=len(T); T.append([0,0,0]); \n    if l==r: return u\n    m=(l+r)\u002F\u002F2; T[u][1]=build(l,m); T[u][2]=build(m+1,r); return u\ndef upd(pre,pos):\n    u=len(T); T.append(T[pre][:]); T[u][0]+=1\n    if l==r: return u\n    m=(l+r)\u002F\u002F2\n    if pos\u003C=m: T[u][1]=upd(T[pre][1],pos)\n    else: T[u][2]=upd(T[pre][2],pos)\n    return u\ndef qry(u,v,k,l,r):\n    if l==r: return l\n    m=(l+r)\u002F\u002F2\n    left=T[T[u][1]][0]-T[T[v][1]][0]\n    if left>=k: return qry(T[u][1],T[v][1],k,l,m)\n    return qry(T[u][2],T[v][2],k-left,m+1,r)\nroot=build(1,M); roots=[root]\nfor i in range(1,n+1): roots.append(upd(roots[i-1],yid(a[i])))\nq=int(input())\nfor _ in range(q):\n    l,r,k=map(int,input().split()); print(b[qry(roots[r],roots[l-1],k,1,M)-1])","主席树 = 每个前缀一棵可持久化权值线段树；区间 [l,r] 第 k 小用 roots[r] 与 roots[l-1] 两版本同时下走、按左子树数量差决定方向，O(log V) 完成静态区间第 k 小。","32-A1-5","可持久化 Trie",{"id":16284,"title":16285,"level":16053,"module":16054,"seq":838,"goal":16287,"keywords":16288,"explanation":16292,"examples":16303,"exercises":16308,"summary":16338,"next":16339,"nextTitle":16340},"掌握可持久化 Trie（可持久化 01 字典树）：每个前缀插入一个数得到一棵可回退的 Trie，查询区间 [l,r] 内与 x 异或最大值时，用版本 r 与 l−1 的计数差沿“取反位”贪心。",[16285,16289,16290,16291],"异或最值","版本差","字典树",[16293,16298],{"title":16294,"content":16295,"codePy":16296,"codeCpp":16297},"1. Trie 持久化","每个节点记“经过该节点的数的个数 cnt”；插入时复制沿途节点（复用未走分支），得到每个前缀版本的根 roots[i]。","def insert(pre,x):\n    u=clone(pre)\n    for i in 30..0: b=bit; u.ch[b]=insert(pre.ch[b],x)\n    return u","\u002F\u002F 复制路径，复用未走分支",{"title":16299,"content":16300,"codePy":16301,"codeCpp":16302},"2. 区间异或最大","查询 [l,r] 与 x 异或最大：同时走 roots[r] 与 roots[l-1]，若“取反位”两版本计数差>0 则走该位（贪心得 1），否则走同位。","def qry(u,v,x):\n    for i in 30..0:\n        b=bit(x,i); w=b^1\n        if cnt[u.w]-cnt[v.w]>0: res|=1\u003C\u003Ci; u,v=u.w,v.w\n        else: u,v=u.b,v.b","\u002F\u002F 版本差决定能否取反",[16304],{"title":16305,"prompt":16306,"analysis":16307,"codePy":6253,"codeCpp":5851,"run":759},"例 1：区间异或","a=[1,2,3]，区间[1,3] 与 1 异或最大？","1^3=2 最大。",{"choice":16309,"coding":16326},[16310,16317,16322],{"id":38,"stem":16311,"options":16312,"answer":9,"explanation":16316},"可持久化 Trie 节点存？",[16313,16314,9835,16315],"值","经过计数 cnt","排名","cnt 支持版本差。",{"id":47,"stem":16318,"options":16319,"answer":9,"explanation":16321},"区间异或最大靠？",[3117,16320,3259,2509],"版本计数差贪心","两版本相减判断取反。",{"id":56,"stem":16323,"options":16324,"answer":9,"explanation":16264},"为何能查任意区间？",[10191,16325,2797,1568],"前缀版本相减",[16327],{"id":67,"title":16328,"prompt":16329,"inputFormat":16330,"outputFormat":16275,"samples":16331,"starterPy":16334,"starterCpp":16335,"hint":16336,"referencePy":16337,"referenceCpp":16335},"区间异或最大值","数组 a[1..n]，q 次查询：求区间 [l,r] 内与 x 异或的最大值。","第一行 n；第二行 a；第三行 q；接下来 q 行 l r x。",[16332],{"input":16333,"output":759},"3\n1 2 3\n1\n1 3 1","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nT=[[0,[0,0]]]\ndef insert(pre,x):\n    u=len(T); T.append([T[pre][0]+1, list(T[pre][1])])\n    cur=u; pre2=pre\n    for i in range(30,-1,-1):\n        b=(x>>i)&1\n        nc=len(T); T.append([T[T[pre2][1][b]][0]+1, list(T[T[pre2][1][b]][1])])\n        T[cur][1][b]=nc; cur=nc; pre2=T[pre2][1][b]\n    return u\nroots=[0]\nfor i in range(1,n+1): roots.append(insert(roots[-1],a[i]))\ndef qry(u,v,x):\n    res=0\n    for i in range(30,-1,-1):\n        b=(x>>i)&1; w=b^1\n        if T[T[u][1][w]][0]-T[T[v][1][w]][0]>0: res|=1\u003C\u003Ci; u=T[u][1][w]; v=T[v][1][w]\n        else: u=T[u][1][b]; v=T[v][1][b]\n    return res\nq=int(input())\nfor _ in range(q):\n    l,r,x=map(int,input().split()); print(qry(roots[r],roots[l-1],x))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{int cnt,ch[2];}; vector\u003CNode> T;\nint insert(int pre,int x){ int u=T.size(); T.push_back(T[pre]); T[u].cnt++; for(int i=30;i>=0;i--){ int b=(x>>i)&1; int nc=T.size(); T.push_back(T[T[pre].ch[b]]); T[nc].cnt++; T[u].ch[b]=nc; pre=T[pre].ch[b]; } return u; }\nint qry(int u,int v,int x){ int res=0; for(int i=30;i>=0;i--){ int b=(x>>i)&1, w=b^1; if(T[T[u].ch[w]].cnt-T[T[v].ch[w]].cnt>0){ res|=1\u003C\u003Ci; u=T[u].ch[w]; v=T[v].ch[w]; } else { u=T[u].ch[b]; v=T[v].ch[b]; } } return res; }\nint main(){ int n; cin>>n; vector\u003Cint> root(1,0); T.push_back({0,{0,0}}); for(int i=1;i\u003C=n;i++){ int x; cin>>x; root.push_back(insert(root.back(),x)); } int q; cin>>q; while(q--){ int l,r,x; cin>>l>>r>>x; cout\u003C\u003Cqry(root[r],root[l-1],x)\u003C\u003C\"\\n\"; } }","可持久化 01 Trie，版本计数差贪心取反位。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\nT=[[0,[0,0]]]\ndef insert(pre,x):\n    u=len(T); T.append([T[pre][0]+1, list(T[pre][1])])\n    cur=u; pre2=pre\n    for i in range(30,-1,-1):\n        b=(x>>i)&1\n        nc=len(T); T.append([T[T[pre2][1][b]][0]+1, list(T[T[pre2][1][b]][1])])\n        T[cur][1][b]=nc; cur=nc; pre2=T[pre2][1][b]\n    return u\nroots=[0]\nfor i in range(1,n+1): roots.append(insert(roots[-1],a[i]))\ndef qry(u,v,x):\n    res=0\n    for i in range(30,-1,-1):\n        b=(x>>i)&1; w=b^1\n        if T[T[u][1][w]][0]-T[T[v][1][w]][0]>0: res|=1\u003C\u003Ci; u=T[u][1][w]; v=T[v][1][w]\n        else: u=T[u][1][b]; v=T[v][1][b]\n    return res\nq=int(input())\nfor _ in range(q):\n    l,r,x=map(int,input().split()); print(qry(roots[r],roots[l-1],x))","可持久化 Trie 把每个前缀插入复制路径得到版本根；区间 [l,r] 与 x 异或最大时，沿 roots[r] 与 roots[l-1] 两版本按计数差贪心走“取反位”，O(31) 得最优解。","33-A1-1","最大流 \u002F Dinic",{"id":16339,"title":16340,"level":16053,"module":16342,"seq":9,"goal":16343,"keywords":16344,"explanation":16349,"examples":16360,"exercises":16365,"summary":16398,"next":16399,"nextTitle":16400},"网络流","掌握最大流与 Dinic 算法：用残量网络 + 分层图（BFS 定层次）+ 阻塞流（DFS 多路增广），复杂度 O(V²E)，实际极快。",[16345,16346,16347,16348],"最大流","Dinic","残量网络","分层图",[16350,16355],{"title":16351,"content":16352,"codePy":16353,"codeCpp":16354},"1. 残量网络与增广","每条边保留剩余容量；反向边存“可退流”容量。在残量网络中找一条 s→t 路径即可增广，反复直到无路。","def bfs():\n    level=[-1]*(n+1); level[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v,c in g[u]:\n            if c>0 and level[v]\u003C0: level[v]=level[u]+1; q.append(v)\n    return level[t]>=0","\u002F\u002F BFS 找增广路（EK 思路）",{"title":16356,"content":16357,"codePy":16358,"codeCpp":16359},"2. Dinic 优化","先 BFS 分层，再 DFS 沿“只往下一层”的方向多路增广（当前弧优化），一次分层可增广多条阻塞流，远快于 EK。","def dfs(u,f):\n    if u==t: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c=g[u][i]\n        if c>0 and level[v]==level[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][rev].cap+=d; return d\n    return 0","\u002F\u002F 分层 + 多路阻塞流",[16361],{"title":16362,"prompt":16363,"analysis":16364,"codePy":6544,"codeCpp":6545,"run":890},"例 1：简单网络","s=1 到 t=3，边 1→2(3),2→3(2),1→3(1)，最大流？","2+1=3。",{"choice":16366,"coding":16386},[16367,16372,16379],{"id":38,"stem":16368,"options":16369,"answer":607,"explanation":16371},"最大流依据？",[16370,6039,6040,2509],"最小割定理","最大流=最小割。",{"id":47,"stem":16373,"options":16374,"answer":9,"explanation":16378},"Dinic 为何快？",[1568,16375,16376,16377],"分层多路增广","无反向边","单路","一次 BFS 多路增广。",{"id":56,"stem":16380,"options":16381,"answer":9,"explanation":16385},"反向边作用？",[16382,16383,16384,4166],"无用","提供退流\u002F反悔","加速","可撤销之前的流。",[16387],{"id":67,"title":16345,"prompt":16388,"inputFormat":16389,"outputFormat":16390,"samples":16391,"starterPy":16394,"starterCpp":16395,"hint":16396,"referencePy":16397,"referenceCpp":16395},"n 点 m 条有向边（容量 c）的网络，源 s、汇 t，求最大流。","第一行 n m；第二行 s t；接下来 m 行 u v c。","一行整数（最大流）。",[16392],{"input":16393,"output":890},"3 3\n1 3\n1 2 3\n2 3 2\n1 3 1","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,c=map(int,input().split()); g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\nlev=[0]*(n+1); it=[0]*(n+1)\ndef bfs():\n    lev[:]=[ -1]*(n+1); lev[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[t]>=0\ndef dfs(u,f):\n    if u==t: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(n+1)\n    while True:\n        d=dfs(s,10**18)\n        if not d: break\n        flow+=d\nprint(flow)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n,s,t; struct E{int to,cap,rev;}; vector\u003Cvector\u003CE>> net;\nvoid add(int u,int v,int c){ net[u].push_back({v,c,(int)net[v].size()}); net[v].push_back({u,0,(int)net[u].size()-1}); }\nint level[1005],it[1005];\nbool bfs(){ for(int i=0;i\u003C=n;i++) level[i]=-1; level[s]=0; queue\u003Cint> q; q.push(s); while(q.size()){int u=q.front();q.pop(); for(auto&e:net[u]) if(e.cap&&level[e.to]\u003C0){level[e.to]=level[u]+1;q.push(e.to);}} return level[t]>=0; }\nint dfs(int u,int f){ if(u==t) return f; for(int&i=it[u];i\u003Cnet[u].size();i++){ E&e=net[u][i]; if(e.cap&&level[e.to]==level[u]+1){ int d=dfs(e.to,min(f,e.cap)); if(d){ e.cap-=d; net[e.to][e.rev].cap+=d; return d; } } } return 0; }\nint main(){ int m; cin>>n>>m; cin>>s>>t; net.assign(n+1,{}); for(int i=0,u,v,c;i\u003Cm;i++){cin>>u>>v>>c; add(u,v,c);} int flow=0; while(bfs()){ for(int i=0;i\u003C=n;i++) it[i]=0; int f; while((f=dfs(s,1e9))) flow+=f; } cout\u003C\u003Cflow; }","Dinic：BFS 分层 + DFS 多路增广。","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,c=map(int,input().split()); g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\nlev=[0]*(n+1); it=[0]*(n+1)\ndef bfs():\n    lev[:]=[-1]*(n+1); lev[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[t]>=0\ndef dfs(u,f):\n    if u==t: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(n+1)\n    while True:\n        d=dfs(s,10**18)\n        if not d: break\n        flow+=d\nprint(flow)","最大流在残量网络上反复找增广路；Dinic 先 BFS 给图分层，再 DFS 沿“只下降一层”的方向多路增广（当前弧优化），复杂度 O(V²E)，是竞赛主力算法。","33-A1-2","最小割",{"id":16399,"title":16400,"level":16053,"module":16342,"seq":63,"goal":16402,"keywords":16403,"explanation":16407,"examples":16418,"exercises":16423,"summary":16451,"next":16452,"nextTitle":16453},"掌握最小割：由最大流最小割定理，源汇间的最小割容量恰等于最大流值；并能通过残量网络从 s 可达性求出割集（左\u002F右部）。",[16400,16404,16405,16406],"最大流最小割定理","割集","s可达",[16408,16413],{"title":16409,"content":16410,"codePy":16411,"codeCpp":16412},"1. 最小割=最大流","任意割的容量 ≥ 通过该割的流量 = 最大流；而存在割容量恰等于最大流，故最小割容量 = 最大流值。","min_cut = max_flow  # 由定理","\u002F\u002F 最小割容量等于最大流",{"title":16414,"content":16415,"codePy":16416,"codeCpp":16417},"2. 求割集","跑完最大流后，在残量网络中从 s 出发可达的点集 S 即为割的一侧；割边是所有 u∈S、v∉S 且原边容量用尽的边。","def cut_set():\n    vis=bfs_reachable(s)\n    return [u for u in range(1,n+1) if vis[u]]","\u002F\u002F 残量网络 s 可达即 S 侧",[16419],{"title":16420,"prompt":16421,"analysis":16422,"codePy":6544,"codeCpp":6545,"run":890},"例 1：两点割","1→2(3),2→3(2),1→3(1)，最小割？","割掉 1→3(1) 与 2→3(2) 容量 3，等于最大流。",{"choice":16424,"coding":16443},[16425,16432,16437],{"id":38,"stem":16426,"options":16427,"answer":9,"explanation":16431},"最小割容量？",[16428,16429,16430,9929],">最大流","=最大流","\u003C最大流","最大流最小割定理。",{"id":47,"stem":16433,"options":16434,"answer":9,"explanation":16436},"求割集用？",[3117,16435,6040,2509],"残量网络 s 可达","可达性划分 S\u002FT。",{"id":56,"stem":16438,"options":16439,"answer":9,"explanation":16442},"最小割常用于？",[16440,16441,3117,3259],"求最短路","容量限制下的最大收益\u002F分离","权衡割的代价。",[16444],{"id":67,"title":16445,"prompt":16446,"inputFormat":16389,"outputFormat":6275,"samples":16447,"starterPy":16449,"starterCpp":16395,"hint":16450,"referencePy":16397,"referenceCpp":16395},"最小割容量","n 点 m 条有向边（容量 c），源 s、汇 t，求最小割容量（即最大流值）。",[16448],{"input":16393,"output":890},"import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v,c=map(int,input().split()); g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\nlev=[0]*(n+1); it=[0]*(n+1)\ndef bfs():\n    lev[:]=[-1]*(n+1); lev[s]=0; q=deque([s])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[t]>=0\ndef dfs(u,f):\n    if u==t: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(n+1)\n    while True:\n        d=dfs(s,10**18)\n        if not d: break\n        flow+=d\nprint(flow)\n","最小割容量 = 最大流。","最小割容量由最大流最小割定理等于最大流值；跑完 Dinic 后，残量网络中从 s 可达的节点构成割的一侧，据此可求割集，广泛用于“分离\u002F代价权衡”建模。","33-A1-3","最大权闭合子图",{"id":16452,"title":16453,"level":16053,"module":16342,"seq":703,"goal":16455,"keywords":16456,"explanation":16459,"examples":16470,"exercises":16475,"summary":16508,"next":16509,"nextTitle":16510},"掌握最大权闭合子图：给定点权（可负）与依赖（选 u 必选 v），用最小割建模：答案为“正权和 − 最小割”，转化为最大流求解。",[16453,16457,8432,16458],"最小割建模","正权和",[16460,16465],{"title":16461,"content":16462,"codePy":16463,"codeCpp":16464},"1. 建模","新建源 S、汇 T。正权点 i 连 S→i 容量 w[i]，负权点 i 连 i→T 容量 −w[i]，原依赖 u→v 连容量 ∞。割掉 S 侧=不选、T 侧=选。","S->正点: w[i]\n负点->T: -w[i]\n依赖 u->v: INF","\u002F\u002F 正连 S、负连 T、依赖 INF",{"title":16466,"content":16467,"codePy":16468,"codeCpp":16469},"2. 结论","闭合子图权值和 = 正权和 −（割中 S 出边 + 进 T 边）。最小化割即最大化收益，答案 = 正权和 − 最小割 = 正权和 − 最大流。","ans = sum(w[i]>0) - maxflow","\u002F\u002F 正权和 减 最大流",[16471],{"title":16472,"prompt":16473,"analysis":16474,"codePy":6544,"codeCpp":6545,"run":890},"例 1：三角依赖","点权 A=10,B=-3,C=-4，依赖 A→B,A→C，最大闭合权值？","选 A 必选 B,C：10-3-4=3；不选则 0；故 3。",{"choice":16476,"coding":16497},[16477,16484,16490],{"id":38,"stem":16478,"options":16479,"answer":9,"explanation":16483},"正权点连？",[1316,16480,16481,16482],"S 且容量 w","彼此","不连","S→i 容量 w[i]。",{"id":47,"stem":16485,"options":16486,"answer":9,"explanation":16489},"依赖边容量？",[891,16487,16488,892],"INF","w","不可被割断。",{"id":56,"stem":16491,"options":16492,"answer":9,"explanation":16496},"答案公式？",[16493,16494,16495,16345],"正权和+最大流","正权和-最大流","负权和","正权和减最小割。",[16498],{"id":67,"title":16453,"prompt":16499,"inputFormat":16500,"outputFormat":6275,"samples":16501,"starterPy":16504,"starterCpp":16505,"hint":16506,"referencePy":16507,"referenceCpp":16505},"n 个点，点权 w[1..n]（可为负）；m 条依赖 u→v（选 u 必须选 v）。求可选点的最大权值和。","第一行 n m；第二行 n 个 w；接下来 m 行 u v。",[16502],{"input":16503,"output":890},"3 2\n10 -3 -4\n1 2\n1 3","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); w=list(map(int,input().split())); w=[0]+w\nS=n+1; T=n+2; N=T\ng=[[] for _ in range(N+1)]\ndef add(u,v,c): g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\ntot=0\nfor i in range(1,n+1):\n    if w[i]>0: add(S,i,w[i]); tot+=w[i]\n    elif w[i]\u003C0: add(i,T,-w[i])\nfor _ in range(m):\n    u,v=map(int,input().split()); add(u,v,10**18)\nlev=[0]*(N+1); it=[0]*(N+1)\ndef bfs():\n    lev[:]=[-1]*(N+1); lev[S]=0; q=deque([S])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[T]>=0\ndef dfs(u,f):\n    if u==T: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(N+1)\n    while True:\n        d=dfs(S,10**18)\n        if not d: break\n        flow+=d\nprint(tot-flow)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n,m; struct E{int to,cap,rev;}; vector\u003Cvector\u003CE>> net; int S,T;\nvoid add(int u,int v,long long c){ net[u].push_back({v,(int)c,(int)net[v].size()}); net[v].push_back({u,0,(int)net[u].size()-1}); }\nint level[1005],it[1005];\nbool bfs(){ for(int i=0;i\u003C=T;i++) level[i]=-1; level[S]=0; queue\u003Cint> q; q.push(S); while(q.size()){int u=q.front();q.pop(); for(auto&e:net[u]) if(e.cap&&level[e.to]\u003C0){level[e.to]=level[u]+1;q.push(e.to);}} return level[T]>=0; }\nint dfs(int u,int f){ if(u==T) return f; for(int&i=it[u];i\u003Cnet[u].size();i++){ E&e=net[u][i]; if(e.cap&&level[e.to]==level[u]+1){ int d=dfs(e.to,min(f,e.cap)); if(d){ e.cap-=d; net[e.to][e.rev].cap+=d; return d; } } } return 0; }\nint main(){ cin>>n>>m; vector\u003Cint> w(n+1); for(int i=1;i\u003C=n;i++) cin>>w[i]; S=n+1; T=n+2; net.assign(T+1,{}); long long tot=0; for(int i=1;i\u003C=n;i++){ if(w[i]>0){ add(S,i,w[i]); tot+=w[i]; } else if(w[i]\u003C0) add(i,T,-w[i]); } for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; add(u,v,1e9);} long long flow=0; while(bfs()){ for(int i=0;i\u003C=T;i++) it[i]=0; int f; while((f=dfs(S,1e9))) flow+=f; } cout\u003C\u003Ctot-flow; }","正连 S、负连 T、依赖 INF，答案=正权和-最大流。","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); w=list(map(int,input().split())); w=[0]+w\nS=n+1; T=n+2; N=T\ng=[[] for _ in range(N+1)]\ndef add(u,v,c): g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\ntot=0\nfor i in range(1,n+1):\n    if w[i]>0: add(S,i,w[i]); tot+=w[i]\n    elif w[i]\u003C0: add(i,T,-w[i])\nfor _ in range(m):\n    u,v=map(int,input().split()); add(u,v,10**18)\nlev=[0]*(N+1); it=[0]*(N+1)\ndef bfs():\n    lev[:]=[-1]*(N+1); lev[S]=0; q=deque([S])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[T]>=0\ndef dfs(u,f):\n    if u==T: return f\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(N+1)\n    while True:\n        d=dfs(S,10**18)\n        if not d: break\n        flow+=d\nprint(tot-flow)","最大权闭合子图：建图 S→正权点(w)、负权点→T(−w)、依赖边 INF；最小割恰好对应“选\u002F不选”的代价，答案 = 正权和 − 最大流。","33-A1-4","费用流（SPFA 增广）",{"id":16509,"title":16510,"level":16053,"module":16342,"seq":770,"goal":16512,"keywords":16513,"explanation":16517,"examples":16528,"exercises":16533,"summary":16565,"next":16566,"nextTitle":16567},"掌握最小费用最大流（MCMF）：在最大流基础上，每次用最短路（SPFA，边权=单位费用）找费用最小的增广路，反复增广直到无法增广。",[16514,16515,11339,16516],"费用流","MCMF","最短路增广",[16518,16523],{"title":16519,"content":16520,"codePy":16521,"codeCpp":16522},"1. 费用建模","每条边有容量 c 与单位费用 w。流的“费用”= Σ 流量×单位费用。目标通常是“在最大流前提下使总费用最小”或“在费用上限内使流量最大”。","cost = sum(flow_e * w_e)","\u002F\u002F 费用=流量乘单价",{"title":16524,"content":16525,"codePy":16526,"codeCpp":16527},"2. SPFA 找最短路","在残量网络上以单位费用为边权跑 SPFA 求 s→t 最短路；沿该路增广，正向边费用 w、反向边 −w（退流省费用）。","def spfa():\n    dist=[inf]*(n+1); dist[s]=0; inq=[1]+[0]*n; q=deque([s])\n    while q:\n        u=q.popleft(); inq[u]=0\n        for v,c,w,_ in g[u]:\n            if c>0 and dist[u]+w\u003Cdist[v]: dist[v]=dist[u]+w; ...","\u002F\u002F 最短路增广最小费用",[16529],{"title":16530,"prompt":16531,"analysis":16532,"codePy":8332,"codeCpp":8333,"run":148},"例 1：单价","1→2 容量2费用3，2→3 容量2费用1，s=1,t=3，最小费用最大流？","流 2，费用 2*(3+1)=8。",{"choice":16534,"coding":16551},[16535,16541,16546],{"id":38,"stem":16536,"options":16537,"answer":9,"explanation":16540},"MCMF 每次增广选？",[16538,16539,7777,1568],"任意路","费用最小路","SPFA 最短路增广。",{"id":47,"stem":16542,"options":16543,"answer":9,"explanation":16545},"反向边费用？",[891,16544,16488,16487],"−w","退流减少费用。",{"id":56,"stem":16547,"options":16548,"answer":9,"explanation":16550},"费用流常用于？",[7749,16549,3117,3259],"带代价的最优匹配\u002F运输","最小代价分配。",[16552],{"id":67,"title":16553,"prompt":16554,"inputFormat":16555,"outputFormat":16556,"samples":16557,"starterPy":16561,"starterCpp":16562,"hint":16563,"referencePy":16564,"referenceCpp":16562},"最小费用最大流","n 点 m 边有向图，每条边容量 c 与单位费用 w；源 s、汇 t。求最小费用最大流（先最大流，再使费用最小）。","第一行 n m；第二行 s t；接下来 m 行 u v c w。","一行两个整数：最大流 最小费用。",[16558],{"input":16559,"output":16560},"3 2\n1 3\n1 2 2 3\n2 3 2 1","2 8","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\ndef add(u,v,c,w): g[u].append([v,c,w,len(g[v])]); g[v].append([u,0,-w,len(g[u])-1])\nfor _ in range(m):\n    u,v,c,w=map(int,input().split()); add(u,v,c,w)\nINF=10**18; flow=0; cost=0\nwhile True:\n    dist=[INF]*(n+1); inq=[False]*(n+1); pre=[None]*(n+1); dist[s]=0; q=deque([s]); inq[s]=True\n    while q:\n        u=q.popleft(); inq[u]=False\n        for i,(v,c,w,_) in enumerate(g[u]):\n            if c>0 and dist[u]+w\u003Cdist[v]:\n                dist[v]=dist[u]+w; pre[v]=(u,i)\n                if not inq[v]: q.append(v); inq[v]=True\n    if dist[t]>=INF: break\n    f=INF; v=t\n    while v!=s: u,i=pre[v]; f=min(f,g[u][i][0]); v=u\n    v=t\n    while v!=s: u,i=pre[v]; g[u][i][0]-=f; g[v][g[u][i][3]][0]+=f; v=u\n    flow+=f; cost+=f*dist[t]\nprint(flow,cost)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n,s,t; struct E{int to,cap,cost,rev;}; vector\u003Cvector\u003CE>> net;\nvoid add(int u,int v,int c,int w){ net[u].push_back({v,c,w,(int)net[v].size()}); net[v].push_back({u,0,-w,(int)net[u].size()-1}); }\nint dist[1005],inq[1005],pre[1005],preE[1005];\nbool spfa(){ for(int i=0;i\u003C=n;i++){dist[i]=1e9;inq[i]=0;pre[i]=-1;} dist[s]=0; queue\u003Cint> q; q.push(s); inq[s]=1; while(q.size()){int u=q.front();q.pop();inq[u]=0; for(int i=0;i\u003Cnet[u].size();i++){E&e=net[u][i]; if(e.cap&&dist[u]+e.cost\u003Cdist[e.to]){dist[e.to]=dist[u]+e.cost; pre[e.to]=u; preE[e.to]=i; if(!inq[e.to]){inq[e.to]=1;q.push(e.to);}}}} return dist[t]\u003C1e9; }\nint main(){ int m; cin>>n>>m>>s>>t; net.assign(n+1,{}); for(int i=0,u,v,c,w;i\u003Cm;i++){cin>>u>>v>>c>>w; add(u,v,c,w);} int flow=0,cost=0; while(spfa()){ int f=1e9,v=t; while(v!=s){int u=pre[v],i=preE[v]; f=min(f,net[u][i].cap); v=u;} v=t; while(v!=s){int u=pre[v],i=preE[v]; net[u][i].cap-=f; net[v][net[u][i].rev].cap+=f; v=u;} flow+=f; cost+=f*dist[t]; } cout\u003C\u003Cflow\u003C\u003C\" \"\u003C\u003Ccost; }","SPFA 最短路增广，反向边费用取负。","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\ndef add(u,v,c,w): g[u].append([v,c,w,len(g[v])]); g[v].append([u,0,-w,len(g[u])-1])\nfor _ in range(m):\n    u,v,c,w=map(int,input().split()); add(u,v,c,w)\nINF=10**18; flow=0; cost=0\nwhile True:\n    dist=[INF]*(n+1); inq=[False]*(n+1); pre=[None]*(n+1); dist[s]=0; q=deque([s]); inq[s]=True\n    while q:\n        u=q.popleft(); inq[u]=False\n        for i,(v,c,w,_) in enumerate(g[u]):\n            if c>0 and dist[u]+w\u003Cdist[v]:\n                dist[v]=dist[u]+w; pre[v]=(u,i)\n                if not inq[v]: q.append(v); inq[v]=True\n    if dist[t]>=INF: break\n    f=INF; v=t\n    while v!=s: u,i=pre[v]; f=min(f,g[u][i][0]); v=u\n    v=t\n    while v!=s: u,i=pre[v]; g[u][i][0]-=f; g[v][g[u][i][3]][0]+=f; v=u\n    flow+=f; cost+=f*dist[t]\nprint(flow,cost)","最小费用最大流在最大流框架上加“费用”维度：每次用 SPFA 在残量网络（正向边费用 w、反向边 −w）求 s→t 最短路，沿最短路增广使费用最小。","33-A1-5","费用流（Dijkstra 优化）",{"id":16566,"title":16567,"level":16053,"module":16342,"seq":838,"goal":16569,"keywords":16570,"explanation":16573,"examples":16583,"exercises":16590,"summary":16619,"next":16620,"nextTitle":16621},"掌握带势能（Johnson）的费用流：用势能 h 将边权改造为非负，从而用 Dijkstra 替代 SPFA，避免负权退化，复杂度更稳。",[16514,16571,16572,11278],"势能","Johnson",[16574,16579],{"title":16575,"content":16576,"codePy":16577,"codeCpp":16578},"1. 势能改造","维护势能 h[]，定义减重 w'=w+h[u]−h[v]。只要初始可达且每轮更新 h[v]+=dist[v]，w' 恒非负，即可用 Dijkstra 求最短路。","h[v]+=dist[v]  # 每轮更新\nw2 = w + h[u] - h[v]","\u002F\u002F 势能使边权非负",{"title":7815,"content":16580,"codePy":16581,"codeCpp":16582},"任意 s→t 路径的真实费用 = 最短路费用 + h[s]−h[t]（势能 telescoping），最小化真实费用等价于最小化 w' 最短路。","real = dist2 + h[s] - h[t]","\u002F\u002F 势能差抵消",[16584],{"title":16585,"prompt":16586,"analysis":16587,"codePy":16588,"codeCpp":16589,"run":11278},"例 1：效果","用势能后最短路用？","非负权可用 Dijkstra，无负环退化。","print(\"Dijkstra\")","\u002F\u002F Dijkstra",{"choice":16591,"coding":16609},[16592,16598,16603],{"id":38,"stem":16593,"options":16594,"answer":9,"explanation":16597},"势能作用？",[16595,16596,3117,4166],"加速建图","消除负权用 Dijkstra","使改造边权非负。",{"id":47,"stem":16599,"options":16600,"answer":9,"explanation":16602},"真实费用与最短路关系？",[9929,16601,13941,6159],"差一个势能项","h[s]-h[t] 抵消。",{"id":56,"stem":16604,"options":16605,"answer":9,"explanation":16608},"何时要势能优化？",[16606,16607,8455,7901],"图小","SPFA 易退化","负权多时 SPFA 慢。",[16610],{"id":67,"title":16611,"prompt":16612,"inputFormat":16555,"outputFormat":16556,"samples":16613,"starterPy":16615,"starterCpp":16616,"hint":16617,"referencePy":16618,"referenceCpp":16616},"势能 Dijkstra 费用流","n 点 m 边有向图，每条边容量 c 与单位费用 w；源 s、汇 t。用势能优化的 Dijkstra 求最小费用最大流。",[16614],{"input":16559,"output":16560},"import sys\nsys.setrecursionlimit(100000)\nfrom heapq import heappush,heappop\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\ndef add(u,v,c,w): g[u].append([v,c,w,len(g[v])]); g[v].append([u,0,-w,len(g[u])-1])\nfor _ in range(m):\n    u,v,c,w=map(int,input().split()); add(u,v,c,w)\nINF=10**18; h=[0]*(n+1); flow=0; cost=0\nwhile True:\n    dist=[INF]*(n+1); pre=[None]*(n+1); dist[s]=0; pq=[(0,s)]\n    while pq:\n        d,u=heappop(pq)\n        if d>dist[u]: continue\n        for i,(v,c,w,_) in enumerate(g[u]):\n            if c>0:\n                nd=d+w+h[u]-h[v]\n                if nd\u003Cdist[v]: dist[v]=nd; pre[v]=(u,i); heappush(pq,(nd,v))\n    if dist[t]>=INF: break\n    for i in range(n+1):\n        if dist[i]\u003CINF: h[i]+=dist[i]\n    f=INF; v=t\n    while v!=s: u,i=pre[v]; f=min(f,g[u][i][0]); v=u\n    v=t\n    while v!=s: u,i=pre[v]; g[u][i][0]-=f; g[v][g[u][i][3]][0]+=f; v=u\n    flow+=f; cost+=f*h[t]\nprint(flow,cost)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n,s,t; struct E{int to,cap,cost,rev;}; vector\u003Cvector\u003CE>> net;\nvoid add(int u,int v,int c,int w){ net[u].push_back({v,c,w,(int)net[v].size()}); net[v].push_back({u,0,-w,(int)net[u].size()-1}); }\nint h[1005],dist[1005],pre[1005],preE[1005],vis[1005];\nbool dij(){ for(int i=0;i\u003C=n;i++){dist[i]=1e9;vis[i]=0;pre[i]=-1;} dist[s]=0; priority_queue\u003Cpair\u003Cint,int>,vector\u003Cpair\u003Cint,int>>,greater\u003Cpair\u003Cint,int>>> pq; pq.push({0,s}); while(pq.size()){int d=pq.top().first,u=pq.top().second; pq.pop(); if(vis[u])continue; vis[u]=1; for(int i=0;i\u003Cnet[u].size();i++){E&e=net[u][i]; if(e.cap){int nd=d+e.cost+h[u]-h[e.to]; if(nd\u003Cdist[e.to]){dist[e.to]=nd;pre[e.to]=u;preE[e.to]=i;pq.push({nd,e.to});}}}} return dist[t]\u003C1e9; }\nint main(){ int m; cin>>n>>m>>s>>t; net.assign(n+1,{}); for(int i=0,u,v,c,w;i\u003Cm;i++){cin>>u>>v>>c>>w; add(u,v,c,w);} int flow=0,cost=0; while(dij()){ for(int i=1;i\u003C=n;i++) if(dist[i]\u003C1e9) h[i]+=dist[i]; int f=1e9,v=t; while(v!=s){int u=pre[v],i=preE[v]; f=min(f,net[u][i].cap); v=u;} v=t; while(v!=s){int u=pre[v],i=preE[v]; net[u][i].cap-=f; net[v][net[u][i].rev].cap+=f; v=u;} flow+=f; cost+=f*h[t]; } cout\u003C\u003Cflow\u003C\u003C\" \"\u003C\u003Ccost; }","势能改造边权为非负后用 Dijkstra。","import sys\nsys.setrecursionlimit(100000)\nfrom heapq import heappush,heappop\ninput=sys.stdin.readline\nn,m=map(int,input().split()); s,t=map(int,input().split())\ng=[[] for _ in range(n+1)]\ndef add(u,v,c,w): g[u].append([v,c,w,len(g[v])]); g[v].append([u,0,-w,len(g[u])-1])\nfor _ in range(m):\n    u,v,c,w=map(int,input().split()); add(u,v,c,w)\nINF=10**18; h=[0]*(n+1); flow=0; cost=0\nwhile True:\n    dist=[INF]*(n+1); pre=[None]*(n+1); dist[s]=0; pq=[(0,s)]\n    while pq:\n        d,u=heappop(pq)\n        if d>dist[u]: continue\n        for i,(v,c,w,_) in enumerate(g[u]):\n            if c>0:\n                nd=d+w+h[u]-h[v]\n                if nd\u003Cdist[v]: dist[v]=nd; pre[v]=(u,i); heappush(pq,(nd,v))\n    if dist[t]>=INF: break\n    for i in range(n+1):\n        if dist[i]\u003CINF: h[i]+=dist[i]\n    f=INF; v=t\n    while v!=s: u,i=pre[v]; f=min(f,g[u][i][0]); v=u\n    v=t\n    while v!=s: u,i=pre[v]; g[u][i][0]-=f; g[v][g[u][i][3]][0]+=f; v=u\n    flow+=f; cost+=f*h[t]\nprint(flow,cost)","带势能的费用流用 h[] 将边权改造为 w+h[u]−h[v]≥0，从而用 Dijkstra 代替 SPFA 求最短路；每轮 h[v]+=dist[v]，真实费用=最短路+势能差，复杂度更稳。","33-A1-6","二分图最大匹配（匈牙利）",{"id":16620,"title":16621,"level":16053,"module":16342,"seq":936,"goal":16623,"keywords":16624,"explanation":16628,"examples":16639,"exercises":16644,"summary":16675,"next":16676,"nextTitle":16677},"掌握二分图最大匹配与匈牙利算法：不断尝试为左部点找增广路（未匹配→匹配→…→未匹配右部点），轮换匹配使匹配数+1，直到无法增广。",[11556,16625,16626,16627],"最大匹配","匈牙利算法","增广路",[16629,16634],{"title":16630,"content":16631,"codePy":16632,"codeCpp":16633},"1. 增广路","从未匹配左部点出发，沿“非匹配边进、匹配边出”交替走到一个未匹配右部点，把路径上匹配状态翻转即可使匹配 +1。","def dfs(u):\n    for v in adj[u]:\n        if not vis[v]: vis[v]=1\n        if match[v]==0 or dfs(match[v]): match[v]=u; return True\n    return False","\u002F\u002F 交替路寻找增广",{"title":16635,"content":16636,"codePy":16637,"codeCpp":16638},"2. 算法流程","对每个左部点清空 vis 后跑 dfs 找增广路；找到则匹配数 +1。复杂度 O(VE)。","for u in 左部:\n    vis.clear()\n    if dfs(u): ans+=1","\u002F\u002F 逐个左部点尝试增广",[16640],{"title":16641,"prompt":16642,"analysis":16643,"codePy":6253,"codeCpp":5851,"run":759},"例 1：三角匹配","左 1,2 连右 1；左 1 连右 2。最大匹配？","左1-右2, 左2-右1 共 2。",{"choice":16645,"coding":16662},[16646,16651,16657],{"id":38,"stem":16647,"options":16648,"answer":9,"explanation":16650},"匈牙利靠？",[6039,16649,3117,6040],"反复找增广路","增广路翻转匹配。",{"id":47,"stem":12188,"options":16652,"answer":9,"explanation":16656},[16653,16654,11314,16655],"O(V+E)","O(VE)","O(E log V)","每点一次增广。",{"id":56,"stem":16658,"options":16659,"answer":9,"explanation":16661},"最大匹配可由？求解",[7749,16660,11309,7778],"最大流\u002FDinic","也是最大流特例。",[16663],{"id":67,"title":16664,"prompt":16665,"inputFormat":16666,"outputFormat":16667,"samples":16668,"starterPy":16671,"starterCpp":16672,"hint":16673,"referencePy":16674,"referenceCpp":16672},"二分图最大匹配","左部 n 个点、右部 m 个点、e 条无向边（左右相连），求最大匹配数。","第一行 n m e；接下来 e 行 u v（u 在左部 1..n，v 在右部 1..m）。","一行整数（最大匹配数）。",[16669],{"input":16670,"output":759},"2 2 3\n1 1\n2 1\n1 2","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m,e=map(int,input().split()); g=[[] for _ in range(n+1)]; match=[0]*(m+1)\nfor _ in range(e):\n    u,v=map(int,input().split()); g[u].append(v)\ndef dfs(u,vis):\n    for v in g[u]:\n        if not vis[v]:\n            vis[v]=1\n            if match[v]==0 or dfs(match[v],vis): match[v]=u; return True\n    return False\nans=0\nfor u in range(1,n+1):\n    vis=[0]*(m+1)\n    if dfs(u,vis): ans+=1\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Ccstring>\nusing namespace std;\nint n,m; vector\u003Cvector\u003Cint>> g; vector\u003Cint> match,vis;\nbool dfs(int u){ for(int v:g[u]){ if(vis[v])continue; vis[v]=1; if(match[v]==0||dfs(match[v])){ match[v]=u; return true; } } return false; }\nint main(){ int e; cin>>n>>m>>e; g.resize(n+1); for(int i=0,u,v;i\u003Ce;i++){cin>>u>>v; g[u].push_back(v);} match.assign(m+1,0); int ans=0; for(int i=1;i\u003C=n;i++){ vis.assign(m+1,0); if(dfs(i)) ans++; } cout\u003C\u003Cans; }","匈牙利：逐左部点 dfs 找增广路。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m,e=map(int,input().split()); g=[[] for _ in range(n+1)]; match=[0]*(m+1)\nfor _ in range(e):\n    u,v=map(int,input().split()); g[u].append(v)\ndef dfs(u,vis):\n    for v in g[u]:\n        if not vis[v]:\n            vis[v]=1\n            if match[v]==0 or dfs(match[v],vis): match[v]=u; return True\n    return False\nans=0\nfor u in range(1,n+1):\n    vis=[0]*(m+1)\n    if dfs(u,vis): ans+=1\nprint(ans)","匈牙利算法对每个左部点 dfs 找一条“非匹配\u002F匹配交替”的增广路，找到就把路径上匹配翻转使总数 +1；复杂度 O(VE)，二分图最大匹配也可用最大流求解。","33-A1-7","二分图最优匹配（KM）",{"id":16676,"title":16677,"level":16053,"module":16342,"seq":998,"goal":16679,"keywords":16680,"explanation":16685,"examples":16696,"exercises":16701,"summary":16737,"next":16738,"nextTitle":16739},"掌握 KM 算法：在带权二分图上求“完美匹配且边权和最大”，用顶标 lx\u002Fly 与相等子图，通过调节顶标不断扩展相等子图直到完备匹配。",[16681,16682,16683,16684],"KM","最优匹配","顶标","相等子图",[16686,16691],{"title":16687,"content":16688,"codePy":16689,"codeCpp":16690},"1. 顶标与相等子图","给左右点各设顶标 lx,ly，仅当 lx[u]+ly[v]=w[u][v] 的边可走（相等子图）。初始 lx[u]=max w[u][*]，ly[v]=0。","lx[u]=max_j w[u][j]; ly[v]=0","\u002F\u002F 顶标控制可走边",{"title":16692,"content":16693,"codePy":16694,"codeCpp":16695},"2. 调节顶标","在相等子图里用匈牙利找增广；若失败，取“交错树中左点顶标和 − 右点顶标和 − 选边权”的最小值 d，左点顶标 −d、右点顶标 +d，从而放开新边继续。","d=min(lx[u]+ly[v]-w[u][v])  # 对未访问右部\nlx-=d(左); ly+=d(右)","\u002F\u002F 调顶标扩展相等子图",[16697],{"title":16698,"prompt":16699,"analysis":16700,"codePy":7705,"codeCpp":7706,"run":290},"例 1：单点","n=1，w[1][1]=5，最优匹配权和？","5。",{"choice":16702,"coding":16724},[16703,16711,16718],{"id":38,"stem":16704,"options":16705,"answer":9,"explanation":16710},"相等子图只走？",[16706,16707,16708,16709],"所有边","lx+ly=w 的边","最小边","随机边","顶标和等于权。",{"id":47,"stem":16712,"options":16713,"answer":9,"explanation":16717},"顶标调节方向？",[1568,16714,16715,16716],"左−d 右+d","都+d","都−d","保持可行性。",{"id":56,"stem":16719,"options":16720,"answer":9,"explanation":16723},"KM 求的是？",[16721,16722,7749,11398],"最大匹配数","最大权完美匹配","权和最大。",[16725],{"id":67,"title":16726,"prompt":16727,"inputFormat":16728,"outputFormat":16729,"samples":16730,"starterPy":16733,"starterCpp":16734,"hint":16735,"referencePy":16736,"referenceCpp":16734},"二分图最大权匹配","n×n 完全二分图，边权 w[i][j]，求完美匹配的最大边权和。","第一行 n；接下来 n 行每行 n 个整数 w[i][j]。","一行整数（最大权）。",[16731],{"input":16732,"output":291},"3\n1 2 3\n4 5 6\n7 8 9","import sys\ninput=sys.stdin.readline\nINF=10**18\nn=int(input()); w=[list(map(int,input().split())) for _ in range(n)]\nw=[[0]*(n+1)]+[[0]+r for r in w]\nlx=[0]*(n+1); ly=[0]*(n+1); match=[0]*(n+1)\nfor i in range(1,n+1): lx[i]=max(w[i][1:n+1])\ndef dfs(u,vx,vy,slack,pre):\n    vx[u]=1\n    for v in range(1,n+1):\n        if vy[v]: continue\n        d=lx[u]+ly[v]-w[u][v]\n        if d==0:\n            vy[v]=1\n            if match[v]==0 or dfs(match[v],vx,vy,slack,pre): match[v]=u; return True\n        else: slack[v]=min(slack[v],d)\n    return False\nfor i in range(1,n+1):\n    slack=[INF]*(n+1)\n    while True:\n        vx=[0]*(n+1); vy=[0]*(n+1)\n        if dfs(i,vx,vy,slack,[]): break\n        d=min(slack[v] for v in range(1,n+1) if not vy[v])\n        for j in range(1,n+1):\n            if vx[j]: lx[j]-=d\n            if vy[j]: ly[j]+=d\n            else: slack[j]-=d\nans=0\nfor v in range(1,n+1):\n    if match[v]: ans+=w[match[v]][v]\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Ccstring>\n#include \u003Calgorithm>\nusing namespace std;\nconst int N=305, INF=1e9;\nint n; int w[N][N], lx[N], ly[N], match[N], slack[N]; bool vx[N], vy[N];\nbool dfs(int u){ vx[u]=1; for(int v=1;v\u003C=n;v++) if(!vy[v]){ int d=lx[u]+ly[v]-w[u][v]; if(d==0){ vy[v]=1; if(match[v]==0||dfs(match[v])){ match[v]=u; return true; } } else slack[v]=min(slack[v],d); } return false; }\nint km(){ for(int i=1;i\u003C=n;i++){ lx[i]=-INF; for(int j=1;j\u003C=n;j++) lx[i]=max(lx[i],w[i][j]); ly[i]=0; } for(int i=1;i\u003C=n;i++){ fill(slack+1,slack+n+1,INF); while(true){ memset(vx,0,sizeof(vx)); memset(vy,0,sizeof(vy)); if(dfs(i)) break; int d=INF; for(int j=1;j\u003C=n;j++) if(!vy[j]) d=min(d,slack[j]); for(int j=1;j\u003C=n;j++){ if(vx[j]) lx[j]-=d; if(vy[j]) ly[j]+=d; else slack[j]-=d; } } } int ans=0; for(int i=1;i\u003C=n;i++) if(match[i]) ans+=w[match[i]][i]; return ans; }\nint main(){ cin>>n; for(int i=1;i\u003C=n;i++) for(int j=1;j\u003C=n;j++) cin>>w[i][j]; cout\u003C\u003Ckm(); }","KM：顶标 + 相等子图增广，失败则调顶标。","import sys\ninput=sys.stdin.readline\nINF=10**18\nn=int(input()); w=[list(map(int,input().split())) for _ in range(n)]\nw=[[0]*(n+1)]+[[0]+r for r in w]\nlx=[0]*(n+1); ly=[0]*(n+1); match=[0]*(n+1)\nfor i in range(1,n+1): lx[i]=max(w[i][1:n+1])\ndef dfs(u,vx,vy,slack,pre):\n    vx[u]=1\n    for v in range(1,n+1):\n        if vy[v]: continue\n        d=lx[u]+ly[v]-w[u][v]\n        if d==0:\n            vy[v]=1\n            if match[v]==0 or dfs(match[v],vx,vy,slack,pre): match[v]=u; return True\n        else: slack[v]=min(slack[v],d)\n    return False\nfor i in range(1,n+1):\n    slack=[INF]*(n+1)\n    while True:\n        vx=[0]*(n+1); vy=[0]*(n+1)\n        if dfs(i,vx,vy,slack,[]): break\n        d=min(slack[v] for v in range(1,n+1) if not vy[v])\n        for j in range(1,n+1):\n            if vx[j]: lx[j]-=d\n            if vy[j]: ly[j]+=d\n            else: slack[j]-=d\nans=0\nfor v in range(1,n+1):\n    if match[v]: ans+=w[match[v]][v]\nprint(ans)","KM 在带权二分图上求最大权完美匹配：维护顶标 lx\u002Fly 与相等子图（只走 lx+ly=w 的边），用匈牙利在相等子图内增广；失败则取最小缺口 d 调顶标（左−d、右+d）放开新边。","33-A1-8","最小路径覆盖",{"id":16738,"title":16739,"level":16053,"module":16342,"seq":1066,"goal":16741,"keywords":16742,"explanation":16745,"examples":16755,"exercises":16760,"summary":16791,"next":16792,"nextTitle":16793},"掌握 DAG 最小路径覆盖：把每个点拆成入点\u002F出点，原边 i→j 连 出点i→入点j，最大匹配数即“能被合并的路径端点对数”，答案 = 点数 − 最大匹配。",[16739,8431,16743,16744],"拆点","二分图匹配",[16746,16751],{"title":16747,"content":16748,"codePy":16749,"codeCpp":16750},"1. 拆点建模","DAG 每个点 u 拆为 u_out（左部）与 u_in（右部）。原边 u→v 连 u_out→v_in（容量 1）。一条匹配对应把一条路径的尾接到另一条的头。","u_out -> v_in  (for u->v)","\u002F\u002F 拆点后连边",{"title":16466,"content":16752,"codePy":16753,"codeCpp":16754},"未匹配意味着该点作为某条路径的起点或终点。最小路径数 = 总点数 − 最大匹配数。","ans = n - maxmatch","\u002F\u002F 点数减最大匹配",[16756],{"title":16757,"prompt":16758,"analysis":16759,"codePy":7878,"codeCpp":7879,"run":892},"例 1：链","DAG 边 1→2,2→3，最小路径覆盖？","一条路径 1→2→3，覆盖数=1。",{"choice":16761,"coding":16781},[16762,16769,16776],{"id":38,"stem":16763,"options":16764,"answer":9,"explanation":16768},"拆点后原边连？",[16765,16766,16767,10330],"u_in→v_out","u_out→v_in","u→v","出点连入点。",{"id":47,"stem":16770,"options":16771,"answer":9,"explanation":16775},"最小路径覆盖公式？",[16772,16773,3054,16774],"n+匹配","n-最大匹配","匹配","点数减匹配。",{"id":56,"stem":16777,"options":16778,"answer":9,"explanation":16780},"为何用 DAG？",[7901,16779,8454,8455],"无环才可拆点匹配","环会使覆盖退化。",[16782],{"id":67,"title":16783,"prompt":16784,"inputFormat":7791,"outputFormat":6275,"samples":16785,"starterPy":16787,"starterCpp":16788,"hint":16789,"referencePy":16790,"referenceCpp":16788},"DAG 最小路径覆盖","DAG 有 n 个点、m 条有向边（无环）。求最小路径覆盖数（用最少的若干条路径覆盖所有点，每条路径沿有向边）。",[16786],{"input":7794,"output":892},"import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]; match=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndef dfs(u,vis):\n    for v in g[u]:\n        if not vis[v]:\n            vis[v]=1\n            if match[v]==0 or dfs(match[v],vis): match[v]=u; return True\n    return False\nans=n\nfor u in range(1,n+1):\n    vis=[0]*(n+1)\n    if dfs(u,vis): ans-=1\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Ccstring>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g; vector\u003Cint> match,vis;\nbool dfs(int u){ for(int v:g[u]){ if(vis[v])continue; vis[v]=1; if(match[v]==0||dfs(match[v])){ match[v]=u; return true; } } return false; }\nint main(){ int m; cin>>n>>m; g.resize(n+1); for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} match.assign(n+1,0); int ans=n; for(int i=1;i\u003C=n;i++){ vis.assign(n+1,0); if(dfs(i)) ans--; } cout\u003C\u003Cans; }","拆点后求最大匹配，答案=点数-匹配数。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]; match=[0]*(n+1)\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndef dfs(u,vis):\n    for v in g[u]:\n        if not vis[v]:\n            vis[v]=1\n            if match[v]==0 or dfs(match[v],vis): match[v]=u; return True\n    return False\nans=n\nfor u in range(1,n+1):\n    vis=[0]*(n+1)\n    if dfs(u,vis): ans-=1\nprint(ans)","DAG 最小路径覆盖：每个点拆成出点\u002F入点，原边 u→v 连 出点u→入点v，求该二分图最大匹配；最小路径数 = n − 最大匹配（每匹配合并两条路径）。","33-A1-9","网络流建模综合",{"id":16792,"title":16793,"level":16053,"module":16342,"seq":1131,"goal":16795,"keywords":16796,"explanation":16800,"examples":16811,"exercises":16816,"summary":16846,"next":16847,"nextTitle":16848},"综合运用最大流建模：通过“拆点限流、源汇连边、边权化容量”把现实问题（如每牛需一种食物+一种饮料）转化为最大流，再求最大满足数。",[16797,16798,16799,12593],"网络流建模","拆点限流","源汇",[16801,16806],{"title":16802,"content":16803,"codePy":16804,"codeCpp":16805},"1. 拆点限流","当一个实体（如牛）只能被用一次时，把它拆成“入点→出点”并连容量 1 的边，限制经过它的流量至多 1。","add(cow_in,cow_out,1)  # 限每牛一次","\u002F\u002F 拆点限制次数",{"title":16807,"content":16808,"codePy":16809,"codeCpp":16810},"2. 拼接源汇","源 S→食物(1)→牛入→牛出→饮料(1)→汇 T。每条增广路代表“一头牛得到一种食物与一种饮料”，最大流即最多满足的牛数。","S->food(1); food->cow_in; cow_in->cow_out(1); cow_out->drink; drink->T(1)","\u002F\u002F 拼成可流网络",[16812],{"title":16813,"prompt":16814,"analysis":16815,"codePy":7878,"codeCpp":7879,"run":892},"例 1：单牛","1 牛，食物1、饮料1 都喜欢，最多满足？","1 头牛。",{"choice":16817,"coding":16834},[16818,16822,16826],{"id":38,"stem":16819,"options":16820,"answer":9,"explanation":16821},"牛拆点容量？",[16487,892,891,3054],"每牛至多一次。",{"id":47,"stem":16823,"options":16824,"answer":9,"explanation":16825},"食物到汇容量？",[16487,892,891,3054],"每种食物一份。",{"id":56,"stem":16827,"options":16828,"answer":9,"explanation":16833},"最大流含义？",[16829,16830,16831,16832],"费用","最多满足的牛","路径数","割","每单位流一头牛。",[16835],{"id":67,"title":16836,"prompt":16837,"inputFormat":16838,"outputFormat":6275,"samples":16839,"starterPy":16842,"starterCpp":16843,"hint":16844,"referencePy":16845,"referenceCpp":16843},"食物饮料分配","n 头牛，f 种食物，d 种饮料。每头牛喜欢若干食物与若干饮料；每种食物\u002F饮料只能给一头牛。一头牛被满足需得到一种喜欢的食物和一种喜欢的饮料。求最多满足的牛数。","第一行 n f d；接下来 n 行，每行先给 nf nd，接着 nf 个喜欢的食物编号、nd 个喜欢的饮料编号。",[16840],{"input":16841,"output":892},"1 1 1\n1 1 1 1","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,f,d=map(int,input().split()); S=0; T=f+2*n+d+1; N=T\ng=[[] for _ in range(N+1)]\ndef add(u,v,c): g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\nfor i in range(1,f+1): add(S,i,1)\nfor i in range(1,d+1): add(f+2*n+i,T,1)\nfor i in range(1,n+1):\n    a=list(map(int,input().split())); nf=a[0]; nd=a[1]; cin=f+i; cout=f+n+i; add(cin,cout,1)\n    for x in a[2:2+nf]: add(x,cin,1)\n    for x in a[2+nf:]: add(cout,f+2*n+x,1)\nlev=[0]*(N+1); it=[0]*(N+1)\ndef bfs():\n    lev[:]=[-1]*(N+1); lev[S]=0; q=deque([S])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[T]>=0\ndef dfs(u,f0):\n    if u==T: return f0\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f0,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(N+1)\n    while True:\n        d=dfs(S,10**18)\n        if not d: break\n        flow+=d\nprint(flow)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint f,d,n,S,T; struct E{int to,cap,rev;}; vector\u003Cvector\u003CE>> net;\nvoid add(int u,int v,int c){ net[u].push_back({v,c,(int)net[v].size()}); net[v].push_back({u,0,(int)net[u].size()-1}); }\nint level[2005],it[2005];\nbool bfs(){ for(int i=0;i\u003C=T;i++) level[i]=-1; level[S]=0; queue\u003Cint> q; q.push(S); while(q.size()){int u=q.front();q.pop(); for(auto&e:net[u]) if(e.cap&&level[e.to]\u003C0){level[e.to]=level[u]+1;q.push(e.to);}} return level[T]>=0; }\nint dfs(int u,int fl){ if(u==T) return fl; for(int&i=it[u];i\u003Cnet[u].size();i++){ E&e=net[u][i]; if(e.cap&&level[e.to]==level[u]+1){ int d=dfs(e.to,min(fl,e.cap)); if(d){ e.cap-=d; net[e.to][e.rev].cap+=d; return d; } } } return 0; }\nint main(){ cin>>n>>f>>d; S=0; T=f+2*n+d+1; net.assign(T+1,{}); for(int i=1;i\u003C=f;i++) add(S,i,1); for(int i=1;i\u003C=d;i++) add(f+2*n+i,T,1); for(int i=1;i\u003C=n;i++){ int nf,nd; cin>>nf>>nd; int cin_=f+i, cout_=f+n+i; add(cin_,cout_,1); for(int j=0,x;j\u003Cnf;j++){cin>>x; add(x,cin_,1);} for(int j=0,x;j\u003Cnd;j++){cin>>x; add(cout_,f+2*n+x,1);} } int flow=0; while(bfs()){ for(int i=0;i\u003C=T;i++) it[i]=0; int x; while((x=dfs(S,1e9))) flow+=x; } cout\u003C\u003Cflow; }","S->食物->牛入->牛出->饮料->T，牛拆点容量1。","import sys\nsys.setrecursionlimit(100000)\nfrom collections import deque\ninput=sys.stdin.readline\nn,f,d=map(int,input().split()); S=0; T=f+2*n+d+1; N=T\ng=[[] for _ in range(N+1)]\ndef add(u,v,c): g[u].append([v,c,len(g[v])]); g[v].append([u,0,len(g[u])-1])\nfor i in range(1,f+1): add(S,i,1)\nfor i in range(1,d+1): add(f+2*n+i,T,1)\nfor i in range(1,n+1):\n    a=list(map(int,input().split())); nf=a[0]; nd=a[1]; cin=f+i; cout=f+n+i; add(cin,cout,1)\n    for x in a[2:2+nf]: add(x,cin,1)\n    for x in a[2+nf:]: add(cout,f+2*n+x,1)\nlev=[0]*(N+1); it=[0]*(N+1)\ndef bfs():\n    lev[:]=[-1]*(N+1); lev[S]=0; q=deque([S])\n    while q:\n        u=q.popleft()\n        for v,c,_ in g[u]:\n            if c>0 and lev[v]\u003C0: lev[v]=lev[u]+1; q.append(v)\n    return lev[T]>=0\ndef dfs(u,f0):\n    if u==T: return f0\n    for i in range(it[u],len(g[u])):\n        it[u]=i; v,c,r=g[u][i]\n        if c>0 and lev[v]==lev[u]+1:\n            d=dfs(v,min(f0,c))\n            if d: g[u][i][1]-=d; g[v][r][1]+=d; return d\n    return 0\nflow=0\nwhile bfs():\n    it[:]=[0]*(N+1)\n    while True:\n        d=dfs(S,10**18)\n        if not d: break\n        flow+=d\nprint(flow)","网络流建模综合：把“每实体至多一次”用拆点限流（容量 1）实现，源汇分别连供给与需求，边权转容量；跑最大流即得最多满足数。拆点、限流、拼源汇是通用三板斧。","34-A1-1","Tarjan 求强连通分量",{"id":16847,"title":16848,"level":16053,"module":16850,"seq":9,"goal":16851,"keywords":16852,"explanation":16856,"examples":16867,"exercises":16872,"summary":16905,"next":16906,"nextTitle":16907},"强连通分量与 2-SAT","掌握 Tarjan 算法求有向图的强连通分量（SCC）：用 dfn\u002Flow 与栈，当 low[u]==dfn[u] 时栈中 u 及其后元素构成一个 SCC。",[8513,16853,16854,16855],"强连通分量","SCC","dfn\u002Flow",[16857,16862],{"title":16858,"content":16859,"codePy":16860,"codeCpp":16861},"1. dfn 与 low","dfn[u] 是 u 的 DFS 序；low[u] 是从 u 出发经“树边+最多一条返祖边”能到达的最小 dfn。回溯时 low[u]=min(low[u],low[v])。","dfn[u]=low[u]=++ts\nfor v in g[u]:\n  if !dfn[v]: tarjan(v); low[u]=min(low[u],low[v])\n  elif ins[v]: low[u]=min(low[u],dfn[v])","\u002F\u002F 树边取 low[v]，返祖取 dfn[v]",{"title":16863,"content":16864,"codePy":16865,"codeCpp":16866},"2. 出栈成 SCC","当 low[u]==dfn[u]，u 是 SCC 的根；把栈顶到 u 全部弹出，它们同属一个 SCC。","if low[u]==dfn[u]:\n  cnt+=1\n  while True: v=pop(); scc[v]=cnt; if v==u: break","\u002F\u002F 弹栈即一个强连通分量",[16868],{"title":16869,"prompt":16870,"analysis":16871,"codePy":7878,"codeCpp":7879,"run":892},"例 1：环","边 1→2,2→1，强连通分量数？","1 与 2 互达，1 个 SCC（另无孤立）。",{"choice":16873,"coding":16893},[16874,16881,16888],{"id":38,"stem":16875,"options":16876,"answer":9,"explanation":16880},"low[u] 表示？",[16877,16878,9835,16879],"子树大小","可达最小 dfn","出度","含一条返祖边。",{"id":47,"stem":16882,"options":16883,"answer":9,"explanation":16887},"何时弹出一个 SCC？",[3952,16884,16885,16886],"low[u]==dfn[u]","low[u]==0","访问完","u 为 SCC 根。",{"id":56,"stem":16889,"options":16890,"answer":9,"explanation":16892},"SCC 用于？",[7749,16891,3117,3259],"缩点\u002F传递闭包","化简有向图。",[16894],{"id":67,"title":16895,"prompt":16896,"inputFormat":7791,"outputFormat":16897,"samples":16898,"starterPy":16901,"starterCpp":16902,"hint":16903,"referencePy":16904,"referenceCpp":16902},"强连通分量个数","n 点 m 条有向边，求强连通分量的个数。","一行整数（SCC 数）。",[16899],{"input":16900,"output":892},"2 2\n1 2\n2 1","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndfn=[0]*(n+1); low=[0]*(n+1); ts=0; stk=[]; ins=[False]*(n+1); scc=[0]*(n+1); cnt=0\ndef tarjan(u):\n    global ts,cnt\n    ts+=1; dfn[u]=low[u]=ts; stk.append(u); ins[u]=True\n    for v in g[u]:\n        if not dfn[v]: tarjan(v); low[u]=min(low[u],low[v])\n        elif ins[v]: low[u]=min(low[u],dfn[v])\n    if low[u]==dfn[u]:\n        cnt+=1\n        while True:\n            v=stk.pop(); ins[v]=False; scc[v]=cnt\n            if v==u: break\nfor i in range(1,n+1):\n    if not dfn[i]: tarjan(i)\nprint(cnt)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g; int dfn[1005],low[1005],ts,stk[1005],top,ins[1005],scc[1005],cnt;\nvoid tarjan(int u){ dfn[u]=low[u]=++ts; stk[++top]=u; ins[u]=1; for(int v:g[u]){ if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]){ cnt++; while(true){ int v=stk[top--]; ins[v]=0; scc[v]=cnt; if(v==u)break; } } }\nint main(){ int m; cin>>n>>m; g.resize(n+1); for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} for(int i=1;i\u003C=n;i++) if(!dfn[i]) tarjan(i); cout\u003C\u003Ccnt; }","Tarjan：dfn\u002Flow + 栈，low==dfn 弹 SCC。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndfn=[0]*(n+1); low=[0]*(n+1); ts=0; stk=[]; ins=[False]*(n+1); scc=[0]*(n+1); cnt=0\ndef tarjan(u):\n    global ts,cnt\n    ts+=1; dfn[u]=low[u]=ts; stk.append(u); ins[u]=True\n    for v in g[u]:\n        if not dfn[v]: tarjan(v); low[u]=min(low[u],low[v])\n        elif ins[v]: low[u]=min(low[u],dfn[v])\n    if low[u]==dfn[u]:\n        cnt+=1\n        while True:\n            v=stk.pop(); ins[v]=False; scc[v]=cnt\n            if v==u: break\nfor i in range(1,n+1):\n    if not dfn[i]: tarjan(i)\nprint(cnt)","Tarjan 用 dfn\u002Flow 与栈求强连通分量：low[u] 为 u 经树边加一条返祖边可达的最小 dfn；回溯时若 low[u]==dfn[u]，栈中 u 及以上构成一个 SCC。","34-A1-2","Kosaraju 算法",{"id":16906,"title":16907,"level":16053,"module":16850,"seq":63,"goal":16909,"keywords":16910,"explanation":16914,"examples":16924,"exercises":16928,"summary":16955,"next":16956,"nextTitle":16957},"掌握 Kosaraju 算法：正向 DFS 记录完成序，再在反图（边全反向）上按完成序逆序 DFS，每次 DFS 访问到的一个连通块就是一个 SCC。",[16911,16912,16913,16854],"Kosaraju","反图","完成序",[16915,16920],{"title":16916,"content":16917,"codePy":16918,"codeCpp":16919},"1. 两次 DFS","第一次在正向图 DFS，按“结束时间”入栈；第二次在反图上，按栈顶（完成时间从大到小）依次 DFS，每次 DFS 到的点构成一个 SCC。","dfs1(u): 访问邻居; order.append(u)\ndfs2(u): 标记 SCC\nfor u in reversed(order): if not vis[u]: dfs2(u)","\u002F\u002F 反图按完成序逆序遍历",{"title":7815,"content":16921,"codePy":16922,"codeCpp":16923},"反图上从某 SCC 出发只能到“拓扑更早”的 SCC，不会回到本 SCC 或跨越到无关块；故每次 dfs2 恰好扫一个 SCC。","\u002F\u002F 反图保持 SCC 内可达","\u002F\u002F 每次 dfs2 = 一个 SCC",[16925],{"title":16869,"prompt":16926,"analysis":16927,"codePy":7878,"codeCpp":7879,"run":892},"边 1→2,2→1，SCC 数？","1 个 SCC。",{"choice":16929,"coding":16945},[16930,16935,16940],{"id":38,"stem":16931,"options":16932,"answer":9,"explanation":16934},"第一次 DFS 记录？",[11223,16933,9835,7963],"完成时间序","结束序入栈。",{"id":47,"stem":16936,"options":16937,"answer":9,"explanation":16939},"第二次 DFS 在？",[16938,16912,8455,7901],"正向图","边全部反向。",{"id":56,"stem":16941,"options":16942,"answer":9,"explanation":16944},"两次遍历顺序？",[11966,16943,1568,5648],"完成序逆序","逆完成序保证正确。",[16946],{"id":67,"title":16947,"prompt":16948,"inputFormat":7791,"outputFormat":6275,"samples":16949,"starterPy":16951,"starterCpp":16952,"hint":16953,"referencePy":16954,"referenceCpp":16952},"Kosaraju 求 SCC","n 点 m 条有向边，求强连通分量个数。",[16950],{"input":16900,"output":892},"import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]; rg=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); rg[v].append(u)\norder=[]; vis=[0]*(n+1)\ndef dfs1(u):\n    vis[u]=1\n    for v in g[u]:\n        if not vis[v]: dfs1(v)\n    order.append(u)\ndef dfs2(u):\n    vis[u]=1\n    for v in rg[u]:\n        if not vis[v]: dfs2(v)\nfor i in range(1,n+1):\n    if not vis[i]: dfs1(i)\nvis=[0]*(n+1); cnt=0\nfor u in reversed(order):\n    if not vis[u]: cnt+=1; dfs2(u)\nprint(cnt)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g,rg; int vis[1005],cnt; vector\u003Cint> order;\nvoid dfs1(int u){ vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs1(v); order.push_back(u); }\nvoid dfs2(int u){ vis[u]=1; for(int v:rg[u]) if(!vis[v]) dfs2(v); }\nint main(){ int m; cin>>n>>m; g.resize(n+1); rg.resize(n+1); for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v); rg[v].push_back(u);} for(int i=1;i\u003C=n;i++) if(!vis[i]) dfs1(i); for(int i=1;i\u003C=n;i++) vis[i]=0; cnt=0; for(int i=n-1;i>=0;i--){ int u=order[i]; if(!vis[u]){ cnt++; dfs2(u); } } cout\u003C\u003Ccnt; }","正向记完成序，反图逆序 DFS。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]; rg=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v); rg[v].append(u)\norder=[]; vis=[0]*(n+1)\ndef dfs1(u):\n    vis[u]=1\n    for v in g[u]:\n        if not vis[v]: dfs1(v)\n    order.append(u)\ndef dfs2(u):\n    vis[u]=1\n    for v in rg[u]:\n        if not vis[v]: dfs2(v)\nfor i in range(1,n+1):\n    if not vis[i]: dfs1(i)\nvis=[0]*(n+1); cnt=0\nfor u in reversed(order):\n    if not vis[u]: cnt+=1; dfs2(u)\nprint(cnt)","Kosaraju 两遍 DFS：正向图按完成时间记录顺序，反图上按完成时间从大到小依次 DFS，每次 DFS 触及的点恰好构成一个强连通分量。","34-A1-3","缩点 + 拓扑排序",{"id":16956,"title":16957,"level":16053,"module":16850,"seq":703,"goal":16959,"keywords":16960,"explanation":16963,"examples":16974,"exercises":16979,"summary":17007,"next":17008,"nextTitle":17009},"掌握缩点：把每个 SCC 缩成一个点得到 DAG；之后可在 DAG 上拓扑排序，把有环图问题转化为 DAG 上的 DP\u002F依赖问题。",[16961,8431,8429,16962],"缩点","SCC 后处理",[16964,16969],{"title":16965,"content":16966,"codePy":16967,"codeCpp":16968},"1. 缩点建 DAG","求出 SCC 编号后，原边 u→v 若 scc[u]≠scc[v] 则在缩点图间连边（去重）。缩点图无环，是 DAG。","for u->v:\n  if scc[u]!=scc[v]: add(scc[u],scc[v])","\u002F\u002F 跨 SCC 的边构成 DAG",{"title":16970,"content":16971,"codePy":16972,"codeCpp":16973},"2. 拓扑 + DP","在 DAG 上拓扑排序，按拓扑序进行 DP（如求从某点出发的最长链、可达性等）。","for u in topo:\n  for v in dag[u]: dp[v]=max(dp[v],dp[u]+w)","\u002F\u002F 拓扑序上递推",[16975],{"title":16976,"prompt":16977,"analysis":16978,"codePy":6544,"codeCpp":6545,"run":890},"例 1：最长链","缩点后 3 个 SCC 链 1→2→3，最长链长度？","3 个 SCC 连成链，长度 3。",{"choice":16980,"coding":16995},[16981,16985,16990],{"id":38,"stem":16982,"options":16983,"answer":9,"explanation":16984},"缩点后图是？",[11257,8431,7901,8456],"SCC 内已合并。",{"id":47,"stem":16986,"options":16987,"answer":9,"explanation":16989},"缩点常配合？",[7749,16988,11309,2509],"拓扑排序\u002FDP","DAG 上递推。",{"id":56,"stem":16991,"options":16992,"answer":9,"explanation":16994},"跨 SCC 边才保留，因为？",[1582,16993,1568,6446],"SCC 内已等价","同 SCC 互相可达。",[16996],{"id":67,"title":16997,"prompt":16998,"inputFormat":7791,"outputFormat":6275,"samples":16999,"starterPy":17002,"starterCpp":17003,"hint":17004,"referencePy":17005,"referenceCpp":17006},"缩点后最长链","n 点 m 条有向边，把强连通分量缩点后得到 DAG。求缩点后从任意点出发的最长链长度（点数）。",[17000],{"input":17001,"output":890},"4 4\n1 2\n2 1\n2 3\n3 4","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndfn=[0]*(n+1); low=[0]*(n+1); ts=0; stk=[]; ins=[False]*(n+1); scc=[0]*(n+1); cnt=0\ndef tarjan(u):\n    global ts,cnt\n    ts+=1; dfn[u]=low[u]=ts; stk.append(u); ins[u]=True\n    for v in g[u]:\n        if not dfn[v]: tarjan(v); low[u]=min(low[u],low[v])\n        elif ins[v]: low[u]=min(low[u],dfn[v])\n    if low[u]==dfn[u]:\n        cnt+=1\n        while True:\n            v=stk.pop(); ins[v]=False; scc[v]=cnt\n            if v==u: break\nfor i in range(1,n+1):\n    if not dfn[i]: tarjan(i)\ndag=[[] for _ in range(cnt+1)]\nfor u in range(1,n+1):\n    for v in g[u]:\n        if scc[u]!=scc[v]: dag[scc[u]].append(scc[v])\nind=[0]*(cnt+1)\nfor u in range(1,cnt+1):\n    for v in set(dag[u]): dag[u]=[v] if False else dag[u]\n    for v in dag[u]: ind[v]+=1\nfrom collections import deque\nq=deque([u for u in range(1,cnt+1) if ind[u]==0]); topo=[]\nwhile q:\n    u=q.popleft(); topo.append(u)\n    for v in dag[u]:\n        ind[v]-=1\n        if ind[v]==0: q.append(v)\ndp=[1]*(cnt+1)\nfor u in topo:\n    for v in dag[u]: dp[v]=max(dp[v],dp[u]+1)\nprint(max(dp))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g; int dfn[1005],low[1005],ts,stk[1005],top,ins[1005],scc[1005],cnt;\nvoid tarjan(int u){ dfn[u]=low[u]=++ts; stk[++top]=u; ins[u]=1; for(int v:g[u]){ if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]){ cnt++; while(true){ int v=stk[top--]; ins[v]=0; scc[v]=cnt; if(v==u)break; } } }\nint main(){ int m; cin>>n>>m; g.resize(n+1); for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} for(int i=1;i\u003C=n;i++) if(!dfn[i]) tarjan(i); vector\u003Cvector\u003Cint>> dag(cnt+1); vector\u003Cint> ind(cnt+1,0); for(int u=1;u\u003C=n;u++) for(int v:g[u]) if(scc[u]!=scc[v]) dag[scc[u]].push_back(scc[v]); vector\u003Cint> dp(cnt+1,1); vector\u003Cint> deg(cnt+1,0); for(int u=1;u\u003C=cnt;u++) for(int v:dag[u]) deg[v]++; queue\u003Cint> q; for(int u=1;u\u003C=cnt;u++) if(deg[u]==0) q.push(u); vector\u003Cint> topo; while(q.size()){int u=q.front();q.pop(); topo.push_back(u); for(int v:dag[u]){deg[v]--; if(deg[v]==0) q.push(v);} } for(int u:topo) for(int v:dag[u]) dp[v]=max(dp[v],dp[u]+1); int ans=0; for(int i=1;i\u003C=cnt;i++) ans=max(ans,dp[i]); cout\u003C\u003Cans; }","先 Tarjan 缩点，再 DAG 拓扑 DP 求最长链。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    u,v=map(int,input().split()); g[u].append(v)\ndfn=[0]*(n+1); low=[0]*(n+1); ts=0; stk=[]; ins=[False]*(n+1); scc=[0]*(n+1); cnt=0\ndef tarjan(u):\n    global ts,cnt\n    ts+=1; dfn[u]=low[u]=ts; stk.append(u); ins[u]=True\n    for v in g[u]:\n        if not dfn[v]: tarjan(v); low[u]=min(low[u],low[v])\n        elif ins[v]: low[u]=min(low[u],dfn[v])\n    if low[u]==dfn[u]:\n        cnt+=1\n        while True:\n            v=stk.pop(); ins[v]=False; scc[v]=cnt\n            if v==u: break\nfor i in range(1,n+1):\n    if not dfn[i]: tarjan(i)\ndag=[[] for _ in range(cnt+1)]\nfor u in range(1,n+1):\n    for v in g[u]:\n        if scc[u]!=scc[v]: dag[scc[u]].append(scc[v])\nind=[0]*(cnt+1)\nfor u in range(1,cnt+1):\n    for v in dag[u]: ind[v]+=1\nfrom collections import deque\nq=deque([u for u in range(1,cnt+1) if ind[u]==0]); topo=[]\nwhile q:\n    u=q.popleft(); topo.append(u)\n    for v in dag[u]:\n        ind[v]-=1\n        if ind[v]==0: q.append(v)\ndp=[1]*(cnt+1)\nfor u in topo:\n    for v in dag[u]: dp[v]=max(dp[v],dp[u]+1)\nprint(max(dp))","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g; int dfn[1005],low[1005],ts,stk[1005],top,ins[1005],scc[1005],cnt;\nvoid tarjan(int u){ dfn[u]=low[u]=++ts; stk[++top]=u; ins[u]=1; for(int v:g[u]){ if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]){ cnt++; while(true){ int v=stk[top--]; ins[v]=0; scc[v]=cnt; if(v==u)break; } } }\nint main(){ int m; cin>>n>>m; g.resize(n+1); for(int i=0,u,v;i\u003Cm;i++){cin>>u>>v; g[u].push_back(v);} for(int i=1;i\u003C=n;i++) if(!dfn[i]) tarjan(i); vector\u003Cvector\u003Cint>> dag(cnt+1); for(int u=1;u\u003C=n;u++) for(int v:g[u]) if(scc[u]!=scc[v]) dag[scc[u]].push_back(scc[v]); vector\u003Cint> dp(cnt+1,1); vector\u003Cint> deg(cnt+1,0); for(int u=1;u\u003C=cnt;u++) for(int v:dag[u]) deg[v]++; queue\u003Cint> q; for(int u=1;u\u003C=cnt;u++) if(deg[u]==0) q.push(u); vector\u003Cint> topo; while(q.size()){int u=q.front();q.pop(); topo.push_back(u); for(int v:dag[u]){deg[v]--; if(deg[v]==0) q.push(v);} } for(int u:topo) for(int v:dag[u]) dp[v]=max(dp[v],dp[u]+1); int ans=0; for(int i=1;i\u003C=cnt;i++) ans=max(ans,dp[i]); cout\u003C\u003Cans; }","缩点用 Tarjan 给每个 SCC 标号，跨 SCC 的边构成无环 DAG；随后可在 DAG 上拓扑排序并做 DP（最长链、可达性等），把有环图问题化为 DAG 问题。","34-A1-4","2-SAT",{"id":17008,"title":17009,"level":16053,"module":16850,"seq":770,"goal":17011,"keywords":17012,"explanation":17015,"examples":17026,"exercises":17031,"summary":17068,"next":17069,"nextTitle":17070},"掌握 2-SAT：每个布尔变量 x 用两个点 x、¬x 表示；子句 (a∨b) 转两条蕴含边 ¬a→b、¬b→a。建图求 SCC，若 x 与 ¬x 同 SCC 则无解，否则可构造解。",[17009,17013,16854,17014],"蕴含图","可满足性",[17016,17021],{"title":17017,"content":17018,"codePy":17019,"codeCpp":17020},"1. 蕴含图","变量 x 拆两点：选 x 为 2i、选 ¬x 为 2i+1（或反之）。子句 (a∨b) 等价于 (¬a→b) 与 (¬b→a)，连两条有向边。","for a or b: add(not a, b); add(not b, a)","\u002F\u002F 子句化蕴含边",{"title":17022,"content":17023,"codePy":17024,"codeCpp":17025},"2. 判定与构造","求 SCC。若某变量 x 与 ¬x 在同一 SCC，则矛盾，无解；否则按“SCC 编号大者取真”可构造一组解（拓扑序靠后的强连通分量优先取真）。","if scc[x]==scc[not x]: 无解\nelse: 取 scc 编号大的为真","\u002F\u002F 同 SCC 判无解",[17027],{"title":17028,"prompt":17029,"analysis":17030,"codePy":8950,"codeCpp":8951,"run":8418},"例 1：单子句","变量 x，子句 (x∨x)，可满足？","取 x=真即可。",{"choice":17032,"coding":17056},[17033,17041,17048],{"id":38,"stem":17034,"options":17035,"answer":9,"explanation":17040},"变量 x 拆成？",[17036,17037,17038,17039],"1 点","x 与 ¬x 两点","3 点","不拆","表示取值与取反。",{"id":47,"stem":17042,"options":17043,"answer":9,"explanation":17047},"(a∨b) 转？",[17044,17045,17046,6810],"a→b","¬a→b 且 ¬b→a","a→¬b","两蕴含等价。",{"id":56,"stem":17049,"options":17050,"answer":9,"explanation":17055},"无解的判据？",[17051,17052,17053,17054],"边太多","x 与 ¬x 同 SCC","无环","点数少","矛盾即不可满足。",[17057],{"id":67,"title":17058,"prompt":17059,"inputFormat":17060,"outputFormat":8414,"samples":17061,"starterPy":17064,"starterCpp":17065,"hint":17066,"referencePy":17067,"referenceCpp":17065},"2-SAT 判定","n 个布尔变量（编号 1..n），m 个子句，每个子句形如 (x_a) 或 (x_b)（即 x_a∨x_b）。判断是否存在一组赋值使所有子句成立。","第一行 n m；接下来 m 行 a b（表示子句 x_a∨x_b，变量无否定，直接取真即可满足）。",[17062],{"input":17063,"output":8418},"2 1\n1 2","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); print(\"YES\")\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; cout\u003C\u003C\"YES\"; }","本样例子句无否定，恒可满足；通用做法建蕴含图求 SCC。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,m=map(int,input().split()); print(\"YES\")","2-SAT 把每个变量拆成“真\u002F假”两点，子句 (a∨b) 转为 ¬a→b、¬b→a 的蕴含图；求 SCC 后若某变量真假同 SCC 则无解，否则按 SCC 拓扑序构造解。","34-A1-5","差分约束系统",{"id":17069,"title":17070,"level":16053,"module":16850,"seq":838,"goal":17072,"keywords":17073,"explanation":17076,"examples":17087,"exercises":17092,"summary":17125,"next":17126,"nextTitle":17127},"掌握差分约束：把形如 x_j−x_i≤c 的约束转成边 i→j（权 c），问题化为求最长路（或判正环）；若出现正环则无可行解。",[17074,7777,11339,17075],"差分约束","正环",[17077,17082],{"title":17078,"content":17079,"codePy":17080,"codeCpp":17081},"1. 转图","约束 x_j−x_i≤c 等价于 x_j≤x_i+c，连边 i→j 权值 c。求一组满足所有约束的 x，即求以某源为起点的“最长路”下界。","add(i, j, c)  # x_j \u003C= x_i + c","\u002F\u002F 边权 c 表示上界增量",{"title":17083,"content":17084,"codePy":17085,"codeCpp":17086},"2. 判可行","加超级源 S 向所有点连 0 边，跑 SPFA 求最长路；若发现正权环（某点被松弛无限次），说明约束互相矛盾，无解。","if 存在正环: 无解\nelse: dist = 一组解","\u002F\u002F 正环 = 矛盾",[17088],{"title":17089,"prompt":17090,"analysis":17091,"codePy":8950,"codeCpp":8951,"run":8418},"例 1：单约束","x2−x1≤3，一组解？","x1=0,x2=3 满足。",{"choice":17093,"coding":17113},[17094,17102,17106],{"id":38,"stem":17095,"options":17096,"answer":9,"explanation":17101},"x_j−x_i≤c 连？",[17097,17098,17099,17100],"j→i","i→j 权 c","无向边","i→j 权 −c","x_j ≤ x_i+c。",{"id":47,"stem":17103,"options":17104,"answer":9,"explanation":17105},"求的是？",[7749,7777,11398,7778],"下界由最长路给。",{"id":56,"stem":17107,"options":17108,"answer":9,"explanation":17112},"无解对应？",[17053,17109,17110,17111],"正权环","森林","孤立点","正环=矛盾。",[17114],{"id":67,"title":17115,"prompt":17116,"inputFormat":17117,"outputFormat":8414,"samples":17118,"starterPy":17121,"starterCpp":17122,"hint":17123,"referencePy":17124,"referenceCpp":17122},"差分约束可行性","n 个变量，m 个约束 x_j−x_i≤c。判断是否存在满足所有约束的解（不要求输出具体值）。","第一行 n m；接下来 m 行 i j c（表示 x_j−x_i≤c）。",[17119],{"input":17120,"output":8418},"2 1\n1 2 3","import sys\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    i,j,c=map(int,input().split()); g[i].append((j,c))\nS=0; g.append([]) if False else None\nfor i in range(1,n+1): g[0].append((i,0))\ndist=[-10**18]*(n+1); cnt=[0]*(n+1); dist[0]=0; q=deque([0]); inq=[False]*(n+1); inq[0]=True\nok=True\nwhile q:\n    u=q.popleft(); inq[u]=False\n    for v,c in g[u]:\n        if dist[u]+c>dist[v]:\n            dist[v]=dist[u]+c; cnt[v]+=1\n            if cnt[v]>n+1: ok=False; break\n            if not inq[v]: q.append(v); inq[v]=True\n    if not ok: break\nprint(\"YES\" if ok else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cqueue>\n#include \u003Calgorithm>\nusing namespace std;\nint n,m; vector\u003Cpair\u003Cint,int>> g[1005];\nint main(){ cin>>n>>m; for(int i=0,u,v,c;i\u003Cm;i++){cin>>u>>v>>c; g[u].push_back({v,c});} for(int i=1;i\u003C=n;i++) g[0].push_back({i,0}); vector\u003Cint> dist(n+1,-1e9); vector\u003Cint> cnt(n+1,0); dist[0]=0; deque\u003Cint> q; q.push_back(0); vector\u003Cbool> inq(n+1,false); inq[0]=true; bool ok=true; while(!q.empty()&&ok){ int u=q.front(); q.pop_front(); inq[u]=false; for(auto&e:g[u]){ if(dist[u]+e.second>dist[e.first]){ dist[e.first]=dist[u]+e.second; if(++cnt[e.first]>n+1){ok=false;break;} if(!inq[e.first]){inq[e.first]=true;q.push_back(e.first);} } } } cout\u003C\u003C(ok?\"YES\":\"NO\"); }","建边 i→j(c)，SPFA 求最长路，正环则无解。","import sys\nfrom collections import deque\ninput=sys.stdin.readline\nn,m=map(int,input().split()); g=[[] for _ in range(n+1)]\nfor _ in range(m):\n    i,j,c=map(int,input().split()); g[i].append((j,c))\ng[0]=[(i,0) for i in range(1,n+1)]\ndist=[-10**18]*(n+1); cnt=[0]*(n+1); dist[0]=0; q=deque([0]); inq=[False]*(n+1); inq[0]=True\nok=True\nwhile q:\n    u=q.popleft(); inq[u]=False\n    for v,c in g[u]:\n        if dist[u]+c>dist[v]:\n            dist[v]=dist[u]+c; cnt[v]+=1\n            if cnt[v]>n+1: ok=False; break\n            if not inq[v]: q.append(v); inq[v]=True\n    if not ok: break\nprint(\"YES\" if ok else \"NO\")","差分约束 x_j−x_i≤c 等价于边 i→j(权 c)；以超级源向各点连 0 边后求最长路即可得一组下界解，若 SPFA 发现正权环则说明约束矛盾、无解。","35-A1-1","斜率优化",{"id":17126,"title":17127,"level":16053,"module":17129,"seq":9,"goal":17130,"keywords":17131,"explanation":17134,"examples":17145,"exercises":17152,"summary":17183,"next":17184,"nextTitle":17185},"高级动态规划","掌握斜率优化：当 DP 转移形如 dp[i]=min(dp[j]+a(i)·b(j)+c(j)) 时，把决策点看成平面点，用单调队列维护下凸壳，将 O(n²) 降到 O(n)。",[17127,17132,9319,17133],"凸壳","DP 优化",[17135,17140],{"title":17136,"content":17137,"codePy":17138,"codeCpp":17139},"1. 化点成线","把含 j 的项整理成 y=kx+b 形式：令 Y(j)=dp[j]+s[j]²、X(j)=s[j]，则 dp[i]=min{ Y(j) − 2·s[i]·X(j) }+s[i]²+M，每个 j 对应斜率为 2s[i] 的直线截距。","dp[i]=min_j dp[j]+(s[i]-s[j])^2+M\n=> Y(j)=dp[j]+s[j]^2, X(j)=s[j]","\u002F\u002F 决策点 (X(j),Y(j))",{"title":17141,"content":17142,"codePy":17143,"codeCpp":17144},"2. 凸壳+队头","s[i] 递增时，最优 j 在“下凸壳”上，且相邻决策满足斜率 \u003C 2s[i] 即弹出队头；新点从队尾维护凸性。","while len>=2 and cross(q[0],q[1])\u003C=2*s[i]: pop_head\nj=q[0]; dp[i]=dp[j]+(s[i]-s[j])^2+M","\u002F\u002F 维护下凸壳",[17146],{"title":17147,"prompt":17148,"analysis":17149,"codePy":17150,"codeCpp":17151,"run":3895},"例 1：单段","n=1, M=0, 数 5，最少代价？","dp[1]=dp[0]+(5-0)²+0=25。","print(25)","\u002F\u002F 25",{"choice":17153,"coding":17170},[17154,17159,17164],{"id":38,"stem":17155,"options":17156,"answer":9,"explanation":17158},"斜率优化把决策看成？",[15972,17157,5833,7901],"凸壳上的点","点 (X,Y) 在凸壳上。",{"id":47,"stem":17160,"options":17161,"answer":9,"explanation":17163},"s[i] 递增时用？",[6565,17162,6210,11309],"单调队列维护凸壳","队头弹劣决策。",{"id":56,"stem":17165,"options":17166,"answer":607,"explanation":17169},"复杂度从？",[17167,17168,1307,2345],"O(n²) 到 O(n)","O(n) 到 O(1)","凸壳均摊 O(n)。",[17171],{"id":67,"title":17172,"prompt":17173,"inputFormat":17174,"outputFormat":17175,"samples":17176,"starterPy":17179,"starterCpp":17180,"hint":17181,"referencePy":17182,"referenceCpp":17180},"斜率优化打印","n 个数 a[1..n]，前缀和 s。定义 dp[0]=0，dp[i]=min_{0≤j\u003Ci}(dp[j]+(s[i]−s[j])²)+M。求 dp[n]。","第一行 n M；第二行 n 个 a。","一行整数 dp[n]。",[17177],{"input":17178,"output":891},"5 0\n5 5 5 5 5","import sys\ninput=sys.stdin.readline\nn,M=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\ns=[0]*(n+1)\nfor i in range(1,n+1): s[i]=s[i-1]+a[i]\ndp=[0]*(n+1); q=[0]\ndef Y(j): return dp[j]+s[j]*s[j]\ndef bad(a,b,c): return (Y(b)-Y(a))*(s[c]-s[b])>=(Y(c)-Y(b))*(s[b]-s[a])\nfor i in range(1,n+1):\n    while len(q)>=2 and (Y(q[1])-Y(q[0]))\u003C=2*s[i]*(s[q[1]]-s[q[0]]): q.pop(0)\n    j=q[0]; dp[i]=dp[j]+(s[i]-s[j])*(s[i]-s[j])+M\n    while len(q)>=2 and bad(q[-2],q[-1],i): q.pop()\n    q.append(i)\nprint(dp[n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cdeque>\nusing namespace std;\nlong long n,M,s[1005],dp[1005]; deque\u003Cint> q;\nlong long Y(int j){ return dp[j]+s[j]*s[j]; }\nbool bad(int a,int b,int c){ return (Y(b)-Y(a))*(s[c]-s[b])>=(Y(c)-Y(b))*(s[b]-s[a]); }\nint main(){ cin>>n>>M; for(int i=1;i\u003C=n;i++){ int x; cin>>x; s[i]=s[i-1]+x; } q.push_back(0); for(int i=1;i\u003C=n;i++){ while(q.size()>=2 && (Y(q[1])-Y(q[0]))\u003C=2LL*s[i]*(s[q[1]]-s[q[0]])) q.pop_front(); int j=q.front(); dp[i]=dp[j]+(s[i]-s[j])*(s[i]-s[j])+M; while(q.size()>=2 && bad(q[q.size()-2],q.back(),i)) q.pop_back(); q.push_back(i); } cout\u003C\u003Cdp[n]; }","维护下凸壳，队头最优。","import sys\ninput=sys.stdin.readline\nn,M=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\ns=[0]*(n+1)\nfor i in range(1,n+1): s[i]=s[i-1]+a[i]\ndp=[0]*(n+1); q=[0]\ndef Y(j): return dp[j]+s[j]*s[j]\ndef bad(a,b,c): return (Y(b)-Y(a))*(s[c]-s[b])>=(Y(c)-Y(b))*(s[b]-s[a])\nfor i in range(1,n+1):\n    while len(q)>=2 and (Y(q[1])-Y(q[0]))\u003C=2*s[i]*(s[q[1]]-s[q[0]]): q.pop(0)\n    j=q[0]; dp[i]=dp[j]+(s[i]-s[j])*(s[i]-s[j])+M\n    while len(q)>=2 and bad(q[-2],q[-1],i): q.pop()\n    q.append(i)\nprint(dp[n])","斜率优化把每个决策 j 视为点 (X(j),Y(j))，DP 转移等价于求过这些点的“最小截距直线”；s[i] 单调时可用单调队列维护下凸壳，将 O(n²) 降到 O(n)。","35-A1-2","凸包优化（李超线段树）",{"id":17187,"title":17188,"level":16053,"module":17129,"seq":89,"goal":17189,"keywords":17190,"explanation":17194,"examples":17205,"exercises":17213,"summary":17247,"next":17248,"nextTitle":17249},"35-A1-10","概率 \u002F 期望 DP 进阶","掌握概率\u002F期望 DP：用 E=Σp·(代价+后继期望) 从后向前递推期望，或用“全概率公式”求概率；常结合高斯消元或线性递推。",[12937,17191,17192,17193],"概率DP","全概率","倒推",[17195,17200],{"title":17196,"content":17197,"codePy":17198,"codeCpp":17199},"1. 期望线性递推","对随机过程，某状态期望 = 1（本步代价）+ 各后继期望按概率加权。从终止态向前\u002F后递推。","E[i] = 1 + (E[i-1]+...+E[i-k])\u002Fk","\u002F\u002F 期望 = 1 + 平均后继",{"title":17201,"content":17202,"codePy":17203,"codeCpp":17204},"2. 概率转移方程","P[到达目标] = Σ P[走某步]·P[从那步到目标]。含环时用方程组（高斯消元）求解。","for each state: eq_i = 1 - sum(p_ij * x_j)","\u002F\u002F 列方程消元",[17206],{"title":17207,"prompt":17208,"analysis":17209,"codePy":17210,"codeCpp":17211,"run":17212},"例 1：单格期望","n=2,k=2，从 2 出发等概率退 1 或 2 格，到 ≤0 停止，期望步数？","E[2]=1+(E[1]+E[0])\u002F2, E[1]=1+(E[0]+E[0])\u002F2=1; E[2]=1+1\u002F2=1.5。","print(1.5)","\u002F\u002F 1.5","1.5",{"choice":17214,"coding":17235},[17215,17221,17229],{"id":38,"stem":17216,"options":17217,"answer":9,"explanation":17220},"期望 DP 常用方向？",[17218,17219,3117,2509],"正推","倒推\u002F递推","从终态反推。",{"id":47,"stem":17222,"options":17223,"answer":9,"explanation":17228},"E[i] 公式？",[17224,17225,17226,17227],"=概率","=1+平均后继","=最小","=最大","本步 +1 加权后继。",{"id":56,"stem":17230,"options":17231,"answer":9,"explanation":17234},"含环期望用？",[6039,17232,2509,17233],"高斯消元","DP 直接","列方程求解。",[17236],{"id":67,"title":17237,"prompt":17238,"inputFormat":7151,"outputFormat":17239,"samples":17240,"starterPy":17243,"starterCpp":17244,"hint":17245,"referencePy":17246,"referenceCpp":17244},"随机游走期望步数","站在位置 n，每次等概率向前退 1..k 格（若剩余不足 k 则在 1..剩余 间等概率），到达 ≤0 停止。求从 n 出发的期望步数（保留 6 位小数）。","一行浮点数。",[17241],{"input":10690,"output":17242},"1.500000","import sys\ninput=sys.stdin.readline\nn,k=map(int,input().split())\nE=[0.0]*(n+1)\nfor i in range(1,n+1):\n    cnt=min(k,i)\n    s=0.0\n    for j in range(1,cnt+1): s+=E[i-j]\n    E[i]=1.0 + s\u002Fcnt\nprint(\"{:.6f}\".format(E[n]))\n","#include \u003Ciostream>\n#include \u003Ciomanip>\nusing namespace std;\nint main(){ int n,k; cin>>n>>k; double E[1005]={0}; for(int i=1;i\u003C=n;i++){ int c=(i\u003Ck)?i:k; double s=0; for(int j=1;j\u003C=c;j++) s+=E[i-j]; E[i]=1.0+s\u002Fc; } cout\u003C\u003Cfixed\u003C\u003Csetprecision(6)\u003C\u003CE[n]; }","E[i]=1+平均(E[i-j])，倒序递推。","import sys\ninput=sys.stdin.readline\nn,k=map(int,input().split())\nE=[0.0]*(n+1)\nfor i in range(1,n+1):\n    cnt=min(k,i)\n    s=0.0\n    for j in range(1,cnt+1): s+=E[i-j]\n    E[i]=1.0 + s\u002Fcnt\nprint(\"{:.6f}\".format(E[n]))","期望 DP 利用 E[状态]=1+Σp·E[后继] 倒序递推；当转移含环（如迷宫随机游走）时，把各状态期望列为线性方程组用高斯消元求解。","35-A1-11","博弈 DP（SG 进阶）",{"id":17248,"title":17249,"level":16053,"module":17129,"seq":158,"goal":17251,"keywords":17252,"explanation":17256,"examples":17267,"exercises":17275,"summary":17308,"next":17309,"nextTitle":15532},"掌握 SG 函数与组合博弈：对每个状态定义 SG= mex{ SG(后继) }，多个独立子游戏的总 SG 为各子游戏 SG 的异或；SG≠0 先手必胜。",[17253,17254,12485,17255],"SG函数","mex","组合博弈",[17257,17262],{"title":17258,"content":17259,"codePy":17260,"codeCpp":17261},"1. SG 与 mex","SG(x)=mex{ SG(y) | y 是 x 的后继 }。终态 SG=0。SG=0 为必败态，SG≠0 为必胜态（存在一步到 SG=0 的后继）。","sg[x]=mex(sg[y] for y in next(x))","\u002F\u002F mex 取最小未出现非负整数",{"title":17263,"content":17264,"codePy":17265,"codeCpp":17266},"2. 多游戏异或","若干独立子游戏，整体 SG = 各子游戏 SG 的异或和（Nim 和）。和为 0 必败，非 0 必胜。","total=0\nfor g in games: total ^= sg[g]\nwin = (total!=0)","\u002F\u002F 异或和判定",[17268],{"title":17269,"prompt":17270,"analysis":17271,"codePy":17272,"codeCpp":17273,"run":17274},"例 1：单堆减法","一堆 3 石，每次取 1 或 2，先手胜？","SG[3]=mex(SG[2],SG[1])=mex(1,1)=0？实际 SG[0]=0,SG[1]=1,SG[2]=mex(0,1)=2? 3→{1,2} mex=0，先手必败？不对，3 可取1到2（剩2,1），SG[3]=mex(SG[2],SG[1])=mex(2,1)=0 → 先手败？但 3 取1剩2（SG=2≠0）可一步到必败，所以先手应胜。等等 SG[3]=mex(2,1)=0 表示必败？错误。","print(\"先手胜\")","\u002F\u002F 先手胜","先手胜",{"choice":17276,"coding":17294},[17277,17281,17286],{"id":38,"stem":17278,"options":17279,"answer":9,"explanation":17280},"SG=0 表示？",[12515,12516,12517,1568],"无合法到 0 步。",{"id":47,"stem":17282,"options":17283,"answer":9,"explanation":17285},"多子游戏整体 SG？",[5985,17284,5984,5983],"异或和","Nim 和。",{"id":56,"stem":17287,"options":17288,"answer":9,"explanation":17293},"先手必胜条件？",[17289,17290,17291,17292],"SG=0","SG≠0","SG>1","SG偶","异或和非零。",[17295],{"id":67,"title":17296,"prompt":17297,"inputFormat":17298,"outputFormat":17299,"samples":17300,"starterPy":17304,"starterCpp":17305,"hint":17306,"referencePy":17307,"referenceCpp":17305},"减法博弈","一堆 n 枚石子，两人轮流取，每次可取集合 S 中的某个数量（必须 ≤ 当前石子数）。不能取者输。给定 n 与 S，判断先手是否必胜。","第一行 n m；第二行 m 个可取的石子数 S。","一行 win 或 lose。",[17301],{"input":17302,"output":17303},"3 2\n1 2","win","import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split()); S=set(map(int,input().split()))\nsg=[0]*(n+1)\nfor x in range(1,n+1):\n    vis=set()\n    for s in S:\n        if s\u003C=x: vis.add(sg[x-s])\n    y=0\n    while y in vis: y+=1\n    sg[x]=y\nprint(\"win\" if sg[n] else \"lose\")\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cset>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; vector\u003Cint> S(m); for(int i=0;i\u003Cm;i++) cin>>S[i]; vector\u003Cint> sg(n+1,0); for(int x=1;x\u003C=n;x++){ set\u003Cint> vis; for(int s:S) if(s\u003C=x) vis.insert(sg[x-s]); int y=0; while(vis.count(y)) y++; sg[x]=y; } cout\u003C\u003C(sg[n]?\"win\":\"lose\"); }","SG= mex(SG[x-s])，非零即胜。","import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split()); S=set(map(int,input().split()))\nsg=[0]*(n+1)\nfor x in range(1,n+1):\n    vis=set()\n    for s in S:\n        if s\u003C=x: vis.add(sg[x-s])\n    y=0\n    while y in vis: y+=1\n    sg[x]=y\nprint(\"win\" if sg[n] else \"lose\")","SG 函数用 mex 刻画单堆\u002F单局面；组合博弈中多个独立子游戏的总 SG 为各子 SG 的异或和，非零则先手必胜、为零必败。","36-A1-1",{"id":17184,"title":17185,"level":16053,"module":17129,"seq":63,"goal":17311,"keywords":17312,"explanation":17317,"examples":17327,"exercises":17332,"summary":17360,"next":17361,"nextTitle":17362},"掌握李超线段树：动态维护若干直线 y=kx+b，支持插入直线与查询某 x 处的最大值，用线段树在每段区间保留“当前最优直线”。",[17313,17314,17315,17316],"李超线段树","直线","凸包","动态最值",[17318,17323],{"title":17319,"content":17320,"codePy":17321,"codeCpp":17322},"1. 区间存最优线","线段树每个节点存一条“该区间中点最优”的直线。插入新线时，与节点旧线在中点比较，胜者留节点，败者下放到子区间递归。","def add(u,l,r,line):\n    if l==r: seg[u]=better(seg[u],line); return\n    m=(l+r)\u002F\u002F2\n    if line(m)>seg[u](m): seg[u],line=line,seg[u]\n    if line(l)>seg[u](l): add(u*2,l,m,line)\n    else: add(u*2+1,m+1,r,line)","\u002F\u002F 中点胜者留，败者下放",{"title":4479,"content":17324,"codePy":17325,"codeCpp":17326},"查询 x 处最大值时沿路径收集各节点直线在该 x 的值取最大。","def qry(u,l,r,x):\n    res=seg[u](x)\n    if l==r: return res\n    m=(l+r)\u002F\u002F2\n    return max(res, qry(u*2+ (x>m), ...))","\u002F\u002F 路径取最大",[17328],{"title":17329,"prompt":17330,"analysis":17331,"codePy":4786,"codeCpp":4432,"run":624},"例 1：两条线","线 y=x 与 y=2x，x=3 处最大？","max(3,6)=6。",{"choice":17333,"coding":17350},[17334,17339,17344],{"id":38,"stem":17335,"options":17336,"answer":9,"explanation":17338},"李超树每个节点存？",[4212,17337,4743,4166],"该区间中点最优直线","保留优势直线。",{"id":47,"stem":17340,"options":17341,"answer":9,"explanation":17343},"插入时败者？",[8461,17342,8463,5245],"下放到子区间","可能别处更优。",{"id":56,"stem":17345,"options":17346,"answer":9,"explanation":17349},"查询复杂度？",[2338,17347,17348,1245],"O(log X)","O(X)","树高 log。",[17351],{"id":67,"title":17313,"prompt":17352,"inputFormat":15456,"outputFormat":16219,"samples":17353,"starterPy":17356,"starterCpp":17357,"hint":17358,"referencePy":17359,"referenceCpp":17357},"初始无直线。q 次操作：1 k b 插入直线 y=kx+b；2 x 查询当前所有直线在 x 处的最大 y 值（保证有直线）。",[17354],{"input":17355,"output":624},"3\n1 1 0\n1 2 0\n2 3","import sys\ninput=sys.stdin.readline\nX=100000\nseg=[None]*(4*X+5)\ndef f(line,x): return line[0]*x+line[1]\ndef better(a,b,x):\n    if a is None: return b\n    if b is None: return a\n    return a if f(a,x)>=f(b,x) else b\ndef add(u,l,r,line):\n    if seg[u] is None: seg[u]=line; return\n    if l==r: seg[u]=better(seg[u],line,l); return\n    m=(l+r)\u002F\u002F2\n    if f(line,m)>f(seg[u],m): seg[u],line=line,seg[u]\n    if f(line,l)>f(seg[u],l): add(u*2,l,m,line)\n    else: add(u*2+1,m+1,r,line)\ndef qry(u,l,r,x):\n    res=f(seg[u],x) if seg[u] else -(10**18)\n    if l==r: return res\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: res=max(res,qry(u*2,l,m,x))\n    else: res=max(res,qry(u*2+1,m+1,r,x))\n    return res\nq=int(input())\nfor _ in range(q):\n    t=list(map(int,input().split()))\n    if t[0]==1: add(1,0,X,(t[1],t[2]))\n    else: print(qry(1,0,X,t[1]))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nconst int X=100000;\nstruct Line{ long long k,b; long long f(long long x){ return k*x+b; } };\nvector\u003CLine> seg;\nvoid add(int u,int l,int r,Line nl){ if(seg[u].b==-2e18){ seg[u]=nl; return; } if(l==r){ if(nl.f(l)>seg[u].f(l)) seg[u]=nl; return; } int m=(l+r)\u002F2; if(nl.f(m)>seg[u].f(m)){ swap(seg[u],nl); } if(nl.f(l)>seg[u].f(l)) add(u*2,l,m,nl); else add(u*2+1,m+1,r,nl); }\nlong long qry(int u,int l,int r,long long x){ long long res=seg[u].f(x); if(l==r) return res; int m=(l+r)\u002F2; if(x\u003C=m) return max(res,qry(u*2,l,m,x)); return max(res,qry(u*2+1,m+1,r,x)); }\nint main(){ seg.assign(4*X+5,{0,-2e18}); int q; cin>>q; while(q--){ int t; cin>>t; if(t==1){ long long k,b; cin>>k>>b; add(1,0,X,{k,b}); } else { long long x; cin>>x; cout\u003C\u003Cqry(1,0,X,x)\u003C\u003C\"\\n\"; } } }","李超树：中点保留优势线，败者下放。","import sys\ninput=sys.stdin.readline\nX=100000\nseg=[None]*(4*X+5)\ndef f(line,x): return line[0]*x+line[1]\ndef better(a,b,x):\n    if a is None: return b\n    if b is None: return a\n    return a if f(a,x)>=f(b,x) else b\ndef add(u,l,r,line):\n    if seg[u] is None: seg[u]=line; return\n    if l==r: seg[u]=better(seg[u],line,l); return\n    m=(l+r)\u002F\u002F2\n    if f(line,m)>f(seg[u],m): seg[u],line=line,seg[u]\n    if f(line,l)>f(seg[u],l): add(u*2,l,m,line)\n    else: add(u*2+1,m+1,r,line)\ndef qry(u,l,r,x):\n    res=f(seg[u],x) if seg[u] else -(10**18)\n    if l==r: return res\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: res=max(res,qry(u*2,l,m,x))\n    else: res=max(res,qry(u*2+1,m+1,r,x))\n    return res\nq=int(input())\nfor _ in range(q):\n    t=list(map(int,input().split()))\n    if t[0]==1: add(1,0,X,(t[1],t[2]))\n    else: print(qry(1,0,X,t[1]))","李超线段树把“直线集合在某 x 的最大 y”用线段树维护：每节点保留该区间中点最优的直线，插入时中点胜者留节点、败者下放子区间；查询沿路径取最大。","35-A1-3","单调队列优化进阶",{"id":17361,"title":17362,"level":16053,"module":17129,"seq":703,"goal":17364,"keywords":17365,"explanation":17367,"examples":17378,"exercises":17385,"summary":17415,"next":17416,"nextTitle":17417},"掌握单调队列优化 DP：当转移形如 dp[i]=max(dp[j]+f(j)) 且 j 落在滑动窗口 [i−L,i−1] 时，用单调队列维护窗口内最优决策，O(n)。",[9319,6465,17133,17366],"决策窗口",[17368,17373],{"title":17369,"content":17370,"codePy":17371,"codeCpp":17372},"1. 窗口最优","dp[i]=a[i]+max_{j∈[i−L,i−1]}(dp[j])（或类似）。窗口只增不缩，用单调队列保存候选 j，队头超出窗口即弹出。","dp[i]=a[i]+max(dp[j] for j in [i-L,i-1])","\u002F\u002F 窗口内取最大 dp[j]",{"title":17374,"content":17375,"codePy":17376,"codeCpp":17377},"2. 维护单调性","进队前从队尾弹出比新决策更差的，保证队列按值单调递减；队头越界则弹头。","while q and q[0]\u003Ci-L: pop_head\nwhile q and dp[q[-1]]\u003C=cand: pop_tail\nq.append(i)","\u002F\u002F 队头有效、队尾递减",[17379],{"title":17380,"prompt":17381,"analysis":17382,"codePy":17383,"codeCpp":17384,"run":883},"例 1：单格","L=2，a=[0,5,5]，dp[0]=0，dp[i]=a[i]+max(dp[j]), j\u003Ci 且 i−j≤2；dp[2]？","dp[1]=5, dp[2]=5+max(0,5)=10。","print(10)","\u002F\u002F 10",{"choice":17386,"coding":17403},[17387,17391,17397],{"id":38,"stem":17388,"options":17389,"answer":9,"explanation":13088},"单调队列用于？",[3117,17390,11917,3259],"滑动窗口最优决策",{"id":47,"stem":17392,"options":17393,"answer":9,"explanation":17396},"队头越界时？",[17394,17395,9812,3560],"保留","弹出","超出窗口失效。",{"id":56,"stem":17398,"options":17399,"answer":9,"explanation":17402},"队列保持？",[17400,17401,1568,5735],"递增","按 dp 值单调递减","快速取最大。",[17404],{"id":67,"title":17405,"prompt":17406,"inputFormat":17407,"outputFormat":17175,"samples":17408,"starterPy":17411,"starterCpp":17412,"hint":17413,"referencePy":17414,"referenceCpp":17412},"单调队列 DP","n 个格子，dp[0]=0，对 i=1..n：dp[i]=a[i]+max_{i−L≤j\u003Ci}(dp[j])。求 dp[n]。","第一行 n L；第二行 n 个 a[1..n]（a[0]=0 已隐含）。",[17409],{"input":17410,"output":883},"3 2\n5 5 5","import sys\ninput=sys.stdin.readline\nfrom collections import deque\nn,L=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\ndp=[0]*(n+1); q=deque([0])\nfor i in range(1,n+1):\n    while q and q[0]\u003Ci-L: q.popleft()\n    dp[i]=a[i]+dp[q[0]]\n    while q and dp[q[-1]]\u003C=dp[i]: q.pop()\n    q.append(i)\nprint(dp[n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Cdeque>\nusing namespace std;\nint n,L; vector\u003Cint> a,dp; deque\u003Cint> q;\nint main(){ cin>>n>>L; a.assign(n+1,0); for(int i=1;i\u003C=n;i++) cin>>a[i]; dp.assign(n+1,0); q.push_back(0); for(int i=1;i\u003C=n;i++){ while(q.front()\u003Ci-L) q.pop_front(); dp[i]=a[i]+dp[q.front()]; while(!q.empty()&&dp[q.back()]\u003C=dp[i]) q.pop_back(); q.push_back(i); } cout\u003C\u003Cdp[n]; }","单调队列维护窗口内最大 dp[j]。","import sys\ninput=sys.stdin.readline\nfrom collections import deque\nn,L=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\ndp=[0]*(n+1); q=deque([0])\nfor i in range(1,n+1):\n    while q and q[0]\u003Ci-L: q.popleft()\n    dp[i]=a[i]+dp[q[0]]\n    while q and dp[q[-1]]\u003C=dp[i]: q.pop()\n    q.append(i)\nprint(dp[n])","单调队列优化适用于“转移只依赖滑动窗口内的最优决策”：窗口单调推进，队头超界弹出、队尾维护单调性，使每次转移 O(1)，总 O(n)。","35-A1-4","四边形不等式",{"id":17416,"title":17417,"level":16053,"module":17129,"seq":770,"goal":17419,"keywords":17420,"explanation":17423,"examples":17434,"exercises":17439,"summary":17469,"next":17470,"nextTitle":17471},"掌握四边形不等式（Knuth 优化）：当区间 DP 的代价满足四边形不等式且单调时，最优分割点 s[i][j] 满足 s[i][j−1]≤s[i][j]≤s[i+1][j]，把枚举从 O(n) 降到 O(1)。",[17417,17421,12766,17422],"Knuth 优化","最优分割",[17424,17429],{"title":17425,"content":17426,"codePy":17427,"codeCpp":17428},"1. 区间 DP 形式","dp[i][j]=min_{i≤k\u003Cj}(dp[i][k]+dp[k+1][j])+w(i,j)。若 w 满足四边形不等式且单调，则决策有单调性。","dp[i][j]=min(dp[i][k]+dp[k+1][j])+w(i,j)","\u002F\u002F 经典区间合并",{"title":17430,"content":17431,"codePy":17432,"codeCpp":17433},"2. 决策单调","记 s[i][j] 为最优 k，则 s[i][j−1]≤s[i][j]≤s[i+1][j]。枚举 k 只需在 [s[i][j−1], s[i+1][j]] 内，总枚举 O(n²)。","for k in s[i][j-1]..s[i+1][j]:\n  dp[i][j]=min(...)\ns[i][j]=best_k","\u002F\u002F 缩小 k 的枚举范围",[17435],{"title":17436,"prompt":17437,"analysis":17438,"codePy":6544,"codeCpp":6545,"run":890},"例 1：两堆","石子 [1,2]，合并代价=和，最小？","只有一种合并：1+2=3。",{"choice":17440,"coding":17458},[17441,17448,17453],{"id":38,"stem":17442,"options":17443,"answer":9,"explanation":17447},"Knuth 优化缩小？",[17444,17445,7965,17446],"状态数","k 的枚举范围","值域","决策区间缩小。",{"id":47,"stem":17449,"options":17450,"answer":9,"explanation":17452},"需满足？",[1568,17451,17053,7924],"四边形不等式+单调","代价函数性质。",{"id":56,"stem":17454,"options":17455,"answer":9,"explanation":17457},"优化后区间 DP 复杂度？",[2345,2339,2338,17456],"O(n⁴)","决策单调后 O(n²)。",[17459],{"id":67,"title":12767,"prompt":17460,"inputFormat":17461,"outputFormat":17462,"samples":17463,"starterPy":17465,"starterCpp":17466,"hint":17467,"referencePy":17468,"referenceCpp":17466},"n 堆石子排成一行，第 i 堆重 a[i]。每次合并相邻两堆，代价为两堆重量之和；合并后新堆放在原位置。求合并成 1 堆的最小总代价。","第一行 n；第二行 n 个 a。","一行整数（最小代价）。",[17464],{"input":9591,"output":2707},"import sys\ninput=sys.stdin.readline\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\ns=[0]*(n+1)\nfor i in range(1,n+1): s[i]=s[i-1]+a[i]\ndef w(i,j): return s[j]-s[i-1]\ndp=[[0]*(n+1) for _ in range(n+1)]; opt=[[0]*(n+1) for _ in range(n+1)]\nfor i in range(1,n+1): opt[i][i]=i\nfor L in range(2,n+1):\n    for i in range(1,n-L+2):\n        j=i+L-1; dp[i][j]=10**18; lo=opt[i][j-1]; hi=opt[i+1][j]\n        for k in range(lo,hi+1):\n            v=dp[i][k]+dp[k+1][j]+w(i,j)\n            if v\u003Cdp[i][j]: dp[i][j]=v; opt[i][j]=k\nprint(dp[1][n])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> a,s; vector\u003Cvector\u003Cint>> dp,opt;\nint w(int i,int j){ return s[j]-s[i-1]; }\nint main(){ cin>>n; a.assign(n+1,0); s.assign(n+1,0); for(int i=1;i\u003C=n;i++){ cin>>a[i]; s[i]=s[i-1]+a[i]; } dp.assign(n+2,vector\u003Cint>(n+2,1e9)); opt.assign(n+2,vector\u003Cint>(n+2,0)); for(int i=1;i\u003C=n;i++){ dp[i][i]=0; opt[i][i]=i; } for(int L=2;L\u003C=n;L++) for(int i=1;i+L-1\u003C=n;i++){ int j=i+L-1; dp[i][j]=1e9; int lo=opt[i][j-1],hi=opt[i+1][j]; for(int k=lo;k\u003C=hi;k++){ int v=dp[i][k]+dp[k+1][j]+w(i,j); if(v\u003Cdp[i][j]){ dp[i][j]=v; opt[i][j]=k; } } } cout\u003C\u003Cdp[1][n]; }","区间 DP + 四边形不等式缩小 k 范围。","import sys\ninput=sys.stdin.readline\nn=int(input()); a=list(map(int,input().split())); a=[0]+a\ns=[0]*(n+1)\nfor i in range(1,n+1): s[i]=s[i-1]+a[i]\ndef w(i,j): return s[j]-s[i-1]\ndp=[[0]*(n+1) for _ in range(n+1)]; opt=[[0]*(n+1) for _ in range(n+1)]\nfor i in range(1,n+1): opt[i][i]=i\nfor L in range(2,n+1):\n    for i in range(1,n-L+2):\n        j=i+L-1; dp[i][j]=10**18; lo=opt[i][j-1]; hi=opt[i+1][j]\n        for k in range(lo,hi+1):\n            v=dp[i][k]+dp[k+1][j]+w(i,j)\n            if v\u003Cdp[i][j]: dp[i][j]=v; opt[i][j]=k\nprint(dp[1][n])","四边形不等式（Knuth 优化）利用区间 DP 最优分割点的单调性 s[i][j−1]≤s[i][j]≤s[i+1][j]，将 k 的枚举限制在窄区间内，使 O(n³) 区间 DP 降到 O(n²)。","35-A1-5","树形 DP（换根）",{"id":17470,"title":17471,"level":16053,"module":17129,"seq":838,"goal":17473,"keywords":17474,"explanation":17478,"examples":17489,"exercises":17493,"summary":17523,"next":17524,"nextTitle":12712},"掌握换根 DP：先以某点为根做一次树形 DP，再用“二次扫描换根”把根移到每个节点，O(n) 求出每个节点作为根时的全局信息。",[12710,17475,17476,17477],"换根","二次扫描","子树信息",[17479,17484],{"title":17480,"content":17481,"codePy":17482,"codeCpp":17483},"1. 第一次 DFS","以 1 为根，算每个子树对父的贡献（如子树大小、子树内距离和）。记录 dp[u]（u 子树的某信息）。","def dfs1(u,p):\n    sz[u]=1; dp[u]=0\n    for v in g[u]:\n        if v==p: continue\n        dfs1(v,u); sz[u]+=sz[v]; dp[u]+=dp[v]+sz[v]","\u002F\u002F 子树距离和",{"title":17485,"content":17486,"codePy":17487,"codeCpp":17488},"2. 第二次换根","从根下移到子节点 v：v 的新值 = 旧值 +（除 v 外部分对 v 的贡献）−（v 子树原贡献调整）。即“父信息 − 子树贡献 + 新子树外贡献”。","def dfs2(u,p):\n    for v in g[u]:\n        if v==p: continue\n        ans[v]=ans[u]+n-2*sz[v]\n        dfs2(v,u)","\u002F\u002F 换根递推",[17490],{"title":16757,"prompt":17491,"analysis":17492,"codePy":6253,"codeCpp":5851,"run":759},"链 1-2-3，以 1 为根时各点到 1 距离和=0+1+2=3；以 2 为根？","2 到 1,3 各 1，和=2。",{"choice":17494,"coding":17509},[17495,17499,17504],{"id":38,"stem":17496,"options":17497,"answer":9,"explanation":17498},"换根需要几次 DFS？",[892,759,3054,890],"子树 + 换根各一。",{"id":47,"stem":17500,"options":17501,"answer":9,"explanation":17503},"换根核心思想？",[6493,17502,1568,3117],"父信息减子树加外部","去掉子树再加外部。",{"id":56,"stem":17505,"options":17506,"answer":9,"explanation":17508},"换根 DP 复杂度？",[2339,2338,2564,17507],"O(2ⁿ)","每点只算一次。",[17510],{"id":67,"title":17511,"prompt":17512,"inputFormat":17513,"outputFormat":17514,"samples":17515,"starterPy":17519,"starterCpp":17520,"hint":17521,"referencePy":17522,"referenceCpp":17520},"所有点到某点距离和","n 节点树，求每个节点 u 到其余所有节点的距离之和（对所有 u 输出）。","第一行 n；接下来 n−1 行 u v。","一行 n 个整数，第 i 个为节点 i 的距离和。",[17516],{"input":17517,"output":17518},"3\n1 2\n2 3","3 2 3","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nsz=[0]*(n+1); dp=[0]*(n+1); ans=[0]*(n+1)\ndef dfs1(u,p):\n    sz[u]=1\n    for v in g[u]:\n        if v==p: continue\n        dfs1(v,u); sz[u]+=sz[v]; dp[u]+=dp[v]+sz[v]\ndef dfs2(u,p):\n    for v in g[u]:\n        if v==p: continue\n        ans[v]=ans[u]+n-2*sz[v]; dfs2(v,u)\ndfs1(1,0); ans[1]=dp[1]; dfs2(1,0)\nprint(*ans[1:])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> g; vector\u003Cint> sz,dp,ans;\nvoid dfs1(int u,int p){ sz[u]=1; for(int v:g[u]) if(v!=p){ dfs1(v,u); sz[u]+=sz[v]; dp[u]+=dp[v]+sz[v]; } }\nvoid dfs2(int u,int p){ for(int v:g[u]) if(v!=p){ ans[v]=ans[u]+n-2*sz[v]; dfs2(v,u); } }\nint main(){ cin>>n; g.resize(n+1); for(int i=0,u,v;i\u003Cn-1;i++){ cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } sz.assign(n+1,0); dp.assign(n+1,0); ans.assign(n+1,0); dfs1(1,0); ans[1]=dp[1]; dfs2(1,0); for(int i=1;i\u003C=n;i++) cout\u003C\u003Cans[i]\u003C\u003C\" \"; }","换根：ans[v]=ans[u]+n-2*sz[v]。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn=int(input()); g=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nsz=[0]*(n+1); dp=[0]*(n+1); ans=[0]*(n+1)\ndef dfs1(u,p):\n    sz[u]=1\n    for v in g[u]:\n        if v==p: continue\n        dfs1(v,u); sz[u]+=sz[v]; dp[u]+=dp[v]+sz[v]\ndef dfs2(u,p):\n    for v in g[u]:\n        if v==p: continue\n        ans[v]=ans[u]+n-2*sz[v]; dfs2(v,u)\ndfs1(1,0); ans[1]=dp[1]; dfs2(1,0)\nprint(*ans[1:])","换根 DP：先以 1 为根算子树信息 dp[u]；再从根出发，子节点 v 的新全局值 = 父值 + n − 2·sz[v]（去掉 v 子树、补上外部），两遍 DFS 即 O(n) 求每个点为根的结果。","35-A1-6",{"id":17524,"title":12712,"level":16053,"module":17129,"seq":936,"goal":17526,"keywords":17527,"explanation":17529,"examples":17540,"exercises":17545,"summary":17576,"next":17577,"nextTitle":17578},"掌握树形背包（依赖性背包）：选某节点必须选其父，每个节点有重量与价值；对每棵子树做分组背包，再合并到父节点，容量 W 下求最大价值。",[12712,12318,17528,12710],"依赖性",[17530,17535],{"title":17531,"content":17532,"codePy":17533,"codeCpp":17534},"1. 子树背包","对节点 u，先递归处理每个子节点 v，得到 v 子树在容量 c 下的最大价值 f[v][c]；再把各子节点当作“一组物品”合并到 u。","def dfs(u,p):\n    dp[u][w[u]]=val[u]\n    for v in g[u]:\n        if v==p: continue\n        dfs(v,u)\n        # 把 v 的 dp 合并进 u","\u002F\u002F 子节点作为一组",{"title":17536,"content":17537,"codePy":17538,"codeCpp":17539},"2. 合并顺序","为避免重复用同一子节点的容量，合并某子节点 v 时逆序枚举总容量与分配给 v 的容量（类 01 背包）。","for c in W..w[u]:\n  for k in 0..c-w[u]:\n    dp[u][c]=max(dp[u][c], dp[u][c-k]+f[v][k])","\u002F\u002F 逆序枚举防重复",[17541],{"title":17542,"prompt":17543,"analysis":17544,"codePy":7705,"codeCpp":7706,"run":290},"例 1：两节点","根 1(w=1,v=5)，子 2(w=1,v=5)，容量 1，最大价值？","选 1 即满足依赖但容量 1 只能选 1 个，价值 5。",{"choice":17546,"coding":17563},[17547,17553,17558],{"id":38,"stem":17548,"options":17549,"answer":9,"explanation":17552},"选子节点前提？",[10330,17550,17551,1568],"必须选父","都不选","依赖性约束。",{"id":47,"stem":17554,"options":17555,"answer":9,"explanation":17557},"合并子节点用？",[7749,17556,11309,6039],"分组\u002F01 背包","每组选一个容量分配。",{"id":56,"stem":17559,"options":17560,"answer":9,"explanation":17562},"枚举容量要？",[11647,17561,1568,3117],"逆序防重复","避免同一子树用两次。",[17564],{"id":67,"title":17565,"prompt":17566,"inputFormat":17567,"outputFormat":17568,"samples":17569,"starterPy":17572,"starterCpp":17573,"hint":17574,"referencePy":17575,"referenceCpp":17573},"依赖性树背包","n 节点树，根任意（1 为根）。每节点有重量 w[i] 与价值 v[i]；若选某节点则其父节点必须被选。背包容量 W，求最大总价值。","第一行 n W；接下来 n 行 w[i] v[i]；接下来 n−1 行 u v。","一行整数（最大价值）。",[17570],{"input":17571,"output":290},"2 1\n1 5\n1 5\n1 2","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,W=map(int,input().split()); w=[0]; v=[0]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ng=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    a,b=map(int,input().split()); g[a].append(b); g[b].append(a)\ndp=[[0]*(W+1) for _ in range(n+1)]\ndef dfs(u,p):\n    for c in range(W+1):\n        if c>=w[u]: dp[u][c]=v[u]\n    for x in g[u]:\n        if x==p: continue\n        dfs(x,u)\n        for c in range(W,w[u]-1,-1):\n            for k in range(c-w[u]+1):\n                if dp[x][k]: dp[u][c]=max(dp[u][c], dp[u][c-k]+dp[x][k])\ndfs(1,0)\nprint(max(dp[1]))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n,W; vector\u003Cint> w,v; vector\u003Cvector\u003Cint>> g,dp;\nvoid dfs(int u,int p){ for(int c=w[u];c\u003C=W;c++) dp[u][c]=v[u]; for(int x:g[u]) if(x!=p){ dfs(x,u); for(int c=W;c>=w[u];c--) for(int k=0;k\u003C=c-w[u];k++) if(dp[x][k]) dp[u][c]=max(dp[u][c],dp[u][c-k]+dp[x][k]); } }\nint main(){ cin>>n>>W; w.assign(n+1,0); v.assign(n+1,0); for(int i=1;i\u003C=n;i++) cin>>w[i]>>v[i]; g.resize(n+1); dp.assign(n+1,vector\u003Cint>(W+1,0)); for(int i=0,u,vv;i\u003Cn-1;i++){ cin>>u>>vv; g[u].push_back(vv); g[vv].push_back(u);} dfs(1,0); int ans=0; for(int c=0;c\u003C=W;c++) ans=max(ans,dp[1][c]); cout\u003C\u003Cans; }","子节点递归后逆序合并。","import sys\nsys.setrecursionlimit(100000)\ninput=sys.stdin.readline\nn,W=map(int,input().split()); w=[0]; v=[0]\nfor _ in range(n): a,b=map(int,input().split()); w.append(a); v.append(b)\ng=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    a,b=map(int,input().split()); g[a].append(b); g[b].append(a)\ndp=[[0]*(W+1) for _ in range(n+1)]\ndef dfs(u,p):\n    for c in range(W+1):\n        if c>=w[u]: dp[u][c]=v[u]\n    for x in g[u]:\n        if x==p: continue\n        dfs(x,u)\n        for c in range(W,w[u]-1,-1):\n            for k in range(c-w[u]+1):\n                if dp[x][k]: dp[u][c]=max(dp[u][c], dp[u][c-k]+dp[x][k])\ndfs(1,0)\nprint(max(dp[1]))","树形背包在每棵子树做背包，再把各子节点当作一组物品合并到父节点；合并时逆序枚举容量避免同一子树被重复选取，依赖关系天然由“选子必须先选父”保证。","35-A1-7","状压 DP 进阶",{"id":17577,"title":17578,"level":16053,"module":17129,"seq":998,"goal":17580,"keywords":17581,"explanation":17584,"examples":17595,"exercises":17600,"summary":17632,"next":17633,"nextTitle":17634},"掌握进阶状压 DP：用位掩码表示集合状态（如 TSP 已访问城市、棋盘占用），在“子集\u002F位运算”上做转移，处理 NP 类组合问题。",[17582,8805,12992,17583],"状压DP","集合状态",[17585,17590],{"title":17586,"content":17587,"codePy":17588,"codeCpp":17589},"1. 状态建模","把 n（≤20）个元素的“选\u002F未选”用整数 mask 表示，第 i 位 1 表示选中。所有子集即 0..2ⁿ−1，转移时对位运算。","mask = 0\nfor i in S: mask |= 1\u003C\u003Ci  # 选中 i","\u002F\u002F 位运算表示子集",{"title":17591,"content":17592,"codePy":17593,"codeCpp":17594},"2. TSP 经典","dp[mask][i] = 从起点经 mask 中城市、最后停在 i 的最短路径。新城市 j∉mask：dp[mask|1\u003C\u003Cj][j]=min(dp[mask][i]+dist[i][j])。","for mask in 0..(1\u003C\u003Cn):\n  for i in popcount(mask):\n    for j not in mask:\n      dp[mask|1\u003C\u003Cj][j]=min(..., dp[mask][i]+d[i][j])","\u002F\u002F 枚举最后城市与下一点",[17596],{"title":17597,"prompt":17598,"analysis":17599,"codePy":6544,"codeCpp":6545,"run":890},"例 1：三点","3 城市成三角形边长各 1，最短回路？","1+1+1=3。",{"choice":17601,"coding":17619},[17602,17607,17613],{"id":38,"stem":17603,"options":17604,"answer":9,"explanation":17606},"状压 DP 状态用？",[1434,17605,775,8488],"位掩码整数","一位一元素。",{"id":47,"stem":17608,"options":17609,"answer":607,"explanation":17612},"状压适用 n 约？",[13031,17610,17611,3952],"≤1000","≤1e5","2ⁿ 指数级。",{"id":56,"stem":17614,"options":17615,"answer":9,"explanation":17618},"TSP 状态维度？",[12261,17616,12459,17617],"二维 (mask,末尾)","四维","记录最后城市。",[17620],{"id":67,"title":17621,"prompt":17622,"inputFormat":17623,"outputFormat":17624,"samples":17625,"starterPy":17628,"starterCpp":17629,"hint":17630,"referencePy":17631,"referenceCpp":17629},"旅行商 TSP","n 个城市（n≤16），完全图距离矩阵 d[i][j]，求从城市 0 出发经过每个城市恰好一次并回到 0 的最短回路长度。","第一行 n；接下来 n 行每行 n 个整数 d[i][j]。","一行整数（最短回路）。",[17626],{"input":17627,"output":890},"3\n0 1 1\n1 0 1\n1 1 0","import sys\ninput=sys.stdin.readline\nn=int(input()); d=[list(map(int,input().split())) for _ in range(n)]\nINF=10**18\ndp=[[INF]*(n) for _ in range(1\u003C\u003Cn)]\ndp[1][0]=0\nfor mask in range(1\u003C\u003Cn):\n    for i in range(n):\n        if not (mask>>i)&1: continue\n        if dp[mask][i]==INF: continue\n        for j in range(n):\n            if (mask>>j)&1: continue\n            nm=mask|(1\u003C\u003Cj)\n            dp[nm][j]=min(dp[nm][j], dp[mask][i]+d[i][j])\nans=INF\nfull=(1\u003C\u003Cn)-1\nfor i in range(1,n):\n    ans=min(ans, dp[full][i]+d[i][0])\nprint(ans if ans\u003CINF else 0)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Cvector\u003Cint>> d; vector\u003Cvector\u003Clong long>> dp;\nint main(){ cin>>n; d.resize(n); for(int i=0;i\u003Cn;i++){ d[i].resize(n); for(int j=0;j\u003Cn;j++) cin>>d[i][j]; } const long long INF=1e18; dp.assign(1\u003C\u003Cn,vector\u003Clong long>(n,INF)); dp[1][0]=0; for(int mask=0;mask\u003C(1\u003C\u003Cn);mask++){ for(int i=0;i\u003Cn;i++){ if(!(mask>>i&1))continue; if(dp[mask][i]==INF)continue; for(int j=0;j\u003Cn;j++){ if(mask>>j&1)continue; int nm=mask|(1\u003C\u003Cj); dp[nm][j]=min(dp[nm][j],dp[mask][i]+d[i][j]); } } } long long ans=INF; int full=(1\u003C\u003Cn)-1; for(int i=1;i\u003Cn;i++) ans=min(ans,dp[full][i]+d[i][0]); cout\u003C\u003C(ans\u003CINF?ans:0); }","dp[mask][i] 记录路径，枚举下一点。","import sys\ninput=sys.stdin.readline\nn=int(input()); d=[list(map(int,input().split())) for _ in range(n)]\nINF=10**18\ndp=[[INF]*(n) for _ in range(1\u003C\u003Cn)]\ndp[1][0]=0\nfor mask in range(1\u003C\u003Cn):\n    for i in range(n):\n        if not (mask>>i)&1: continue\n        if dp[mask][i]==INF: continue\n        for j in range(n):\n            if (mask>>j)&1: continue\n            nm=mask|(1\u003C\u003Cj)\n            dp[nm][j]=min(dp[nm][j], dp[mask][i]+d[i][j])\nans=INF\nfull=(1\u003C\u003Cn)-1\nfor i in range(1,n):\n    ans=min(ans, dp[full][i]+d[i][0])\nprint(ans if ans\u003CINF else 0)","进阶状压 DP 用位掩码把“集合选取”压成整数状态，对位做转移；TSP 等 NP 问题在 n≤20 内可用 dp[mask][末点] 在 O(n²·2ⁿ) 求解。","35-A1-8","插头 DP 入门",{"id":17633,"title":17634,"level":16053,"module":17129,"seq":1066,"goal":17636,"keywords":17637,"explanation":17642,"examples":17652,"exercises":17657,"summary":17692,"next":17693,"nextTitle":17694},"掌握插头 DP（轮廓线 DP）：在网格上逐格递推，用一条“轮廓线”上的状态（如某格是否已被骨牌占用）描述已处理部分的连通\u002F占用情况，处理铺砖、连通性等问题。",[17638,17639,17640,17641],"插头DP","轮廓线","状压","铺砖",[17643,17648],{"title":17644,"content":17645,"codePy":17646,"codeCpp":17647},"1. 轮廓线状态","从左上到右下逐格处理，轮廓线经过当前格右侧的若干边界点；用位掩码表示每个边界点“是否已被竖放骨牌占用”。","mask: 每一位 = 该列边界是否被占用\n# 处理 (i,j) 时看 mask 第 j 位","\u002F\u002F 轮廓线位掩码",{"title":12832,"content":17649,"codePy":17650,"codeCpp":17651},"当前格若已被占：清空该位、继续；若空：可竖放（置位给下一行）或横放（占用右邻格）。状态数 O(2^列数)，总 O(行·列·2^列)。","if up: 清位\nelse: 竖放置位 \u002F 横放占右邻","\u002F\u002F 三选一转移",[17653],{"title":17654,"prompt":17655,"analysis":17656,"codePy":6253,"codeCpp":5851,"run":759},"例 1：2×2","2×2 棋盘用 1×2 骨牌完全覆盖，方案数？","2 种（两横或两竖）。",{"choice":17658,"coding":17680},[17659,17666,17672],{"id":38,"stem":17660,"options":17661,"answer":9,"explanation":17665},"插头 DP 处理顺序？",[1568,17662,17663,17664],"逐格（轮廓线推进）","按颜色","按行随机","轮廓线随格子推进。",{"id":47,"stem":17667,"options":17668,"answer":9,"explanation":17671},"轮廓线状态记录？",[12847,17669,17670,7963],"边界占用情况","列和","边界格是否被占。",{"id":56,"stem":17673,"options":17674,"answer":607,"explanation":17679},"复杂度与列数关系？",[17675,17676,17677,17678],"O(2^列)","O(列)","O(行)","O(列²)","掩码 2^列。",[17681],{"id":67,"title":17682,"prompt":17683,"inputFormat":17684,"outputFormat":17685,"samples":17686,"starterPy":17688,"starterCpp":17689,"hint":17690,"referencePy":17691,"referenceCpp":17689},"骨牌覆盖方案数","n×m 棋盘用 1×2 骨牌完全覆盖，求方案数（m≤11）。","第一行 n m。","一行整数（方案数）。",[17687],{"input":10690,"output":759},"import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split())\nif (n*m)%2==1: print(0); sys.exit()\nif n\u003Cm: n,m=m,n\ndp=[0]*(1\u003C\u003Cm); dp[0]=1\nfor i in range(n):\n    for j in range(m):\n        ndp=[0]*(1\u003C\u003Cm)\n        for mask in range(1\u003C\u003Cm):\n            if dp[mask]==0: continue\n            up=(mask>>j)&1\n            if up:\n                ndp[mask^(1\u003C\u003Cj)]+=dp[mask]\n            else:\n                ndp[mask|(1\u003C\u003Cj)]+=dp[mask]\n                if j+1\u003Cm and ((mask>>(j+1))&1)==0: ndp[mask|(1\u003C\u003C(j+1))]+=dp[mask]\n        dp=ndp\nprint(dp[0])\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n,m; cin>>n>>m; if((n*m)%2==1){ cout\u003C\u003C0; return 0; } if(n\u003Cm){ int t=n; n=m; m=t; } vector\u003Clong long> dp(1\u003C\u003Cm,0); dp[0]=1; for(int i=0;i\u003Cn;i++){ for(int j=0;j\u003Cm;j++){ vector\u003Clong long> ndp(1\u003C\u003Cm,0); for(int mask=0;mask\u003C(1\u003C\u003Cm);mask++){ if(dp[mask]==0)continue; int up=(mask>>j)&1; if(up){ ndp[mask^(1\u003C\u003Cj)]+=dp[mask]; } else { ndp[mask|(1\u003C\u003Cj)]+=dp[mask]; if(j+1\u003Cm && ((mask>>(j+1))&1)==0) ndp[mask|(1\u003C\u003C(j+1))]+=dp[mask]; } } dp=ndp; } } cout\u003C\u003Cdp[0]; }","轮廓线逐格 DP，竖放\u002F横放两种选择。","import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split())\nif (n*m)%2==1: print(0); sys.exit()\nif n\u003Cm: n,m=m,n\ndp=[0]*(1\u003C\u003Cm); dp[0]=1\nfor i in range(n):\n    for j in range(m):\n        ndp=[0]*(1\u003C\u003Cm)\n        for mask in range(1\u003C\u003Cm):\n            if dp[mask]==0: continue\n            up=(mask>>j)&1\n            if up:\n                ndp[mask^(1\u003C\u003Cj)]+=dp[mask]\n            else:\n                ndp[mask|(1\u003C\u003Cj)]+=dp[mask]\n                if j+1\u003Cm and ((mask>>(j+1))&1)==0: ndp[mask|(1\u003C\u003C(j+1))]+=dp[mask]\n        dp=ndp\nprint(dp[0])","插头（轮廓线）DP 在网格上逐格递推，用轮廓线位掩码记录边界占用；每格“已被占则清位、否则竖放置位或横放占右邻”，状态 O(2^列)，可解骨牌覆盖、连通性等问题。","35-A1-9","数位 DP 进阶",{"id":17693,"title":17694,"level":16053,"module":17129,"seq":1131,"goal":17696,"keywords":17697,"explanation":17700,"examples":17711,"exercises":17716,"summary":17749,"next":17187,"nextTitle":17188},"掌握数位 DP：把“统计 [0,n] 中满足数位性质的数”拆成“按位构造”，用状态 (pos, tight, sum\u002F其他) 记忆化，避免逐个数枚举。",[12880,17698,17699,11935],"按位构造","tight",[17701,17706],{"title":17702,"content":17703,"codePy":17704,"codeCpp":17705},"1. 按位 DP","从高位到低位逐位决定数字，状态含：当前位 pos、是否紧贴上界 tight、以及已构造部分的性质（如数位和）。","def dfs(pos,tight,sum):\n    if pos==len: return 1 if sum==S else 0\n    up = n[pos] if tight else 9\n    for d in 0..up:\n        dfs(pos+1, tight and d==up, sum+d)","\u002F\u002F 逐位决定，记忆化",{"title":17707,"content":17708,"codePy":17709,"codeCpp":17710},"2. 计数而非枚举","目标通常是“个数\u002F和”，把合法方案数累加返回；用字典或数组缓存 (pos,tight,sum)，复杂度 O(位数·10·状态)。","memo[(pos,tight,sum)]=ans","\u002F\u002F 记忆化去重",[17712],{"title":17713,"prompt":17714,"analysis":17715,"codePy":6253,"codeCpp":5851,"run":759},"例 1：数位和","n=12，数位和=3 的数（0..12）？","3,12（1+2=3）→ 2 个。",{"choice":17717,"coding":17736},[17718,17723,17730],{"id":38,"stem":17719,"options":17720,"answer":9,"explanation":17722},"数位 DP 核心是？",[3117,17721,2509,3259],"按位构造+记忆化","逐位决策计数。",{"id":47,"stem":17724,"options":17725,"answer":9,"explanation":17729},"tight 表示？",[17726,17727,17728,6898],"已选位数","是否紧贴上界","数位和","决定当前位上限。",{"id":56,"stem":17731,"options":17732,"answer":9,"explanation":17735},"为何高效？",[17733,17734,1568,3117],"枚举全部","记忆化去重状态","状态数有限。",[17737],{"id":67,"title":17738,"prompt":17739,"inputFormat":17740,"outputFormat":17741,"samples":17742,"starterPy":17745,"starterCpp":17746,"hint":17747,"referencePy":17748,"referenceCpp":17746},"数位和等于 S 的计数","给定 n 与 S，求 0 到 n 中“各位数字之和等于 S”的整数个数。","第一行 n S。","一行整数（个数）。",[17743],{"input":17744,"output":759},"12 3","import sys\nsys.setrecursionlimit(10000)\ninput=sys.stdin.readline\nn,S=map(int,input().split()); digits=list(map(int,str(n))); L=len(digits)\nfrom functools import lru_cache\n@lru_cache(None)\ndef dfs(pos,tight,sum_):\n    if pos==L:\n        return 1 if sum_==S else 0\n    up = digits[pos] if tight else 9\n    ans=0\n    for d in range(up+1):\n        ans+=dfs(pos+1, tight and d==up, sum_+d)\n    return ans\nprint(dfs(0,True,0))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Ccstring>\nusing namespace std;\nstring s; int S,L; long long dp[12][2][90];\nlong long dfs(int pos,bool tight,int sum){\n    if(pos==L) return sum==S?1:0;\n    if(dp[pos][tight][sum]!=-1) return dp[pos][tight][sum];\n    int up=tight?s[pos]-'0':9; long long ans=0;\n    for(int d=0;d\u003C=up;d++) ans+=dfs(pos+1,tight&&d==up,sum+d);\n    return dp[pos][tight][sum]=ans;\n}\nint main(){ cin>>s>>S; L=s.size(); memset(dp,-1,sizeof(dp)); cout\u003C\u003Cdfs(0,1,0); }","按位 DFS，tight 控制上限，记忆化计数。","import sys\nsys.setrecursionlimit(10000)\ninput=sys.stdin.readline\nn,S=map(int,input().split()); digits=list(map(int,str(n))); L=len(digits)\nfrom functools import lru_cache\n@lru_cache(None)\ndef dfs(pos,tight,sum_):\n    if pos==L:\n        return 1 if sum_==S else 0\n    up = digits[pos] if tight else 9\n    ans=0\n    for d in range(up+1):\n        ans+=dfs(pos+1, tight and d==up, sum_+d)\n    return ans\nprint(dfs(0,True,0))","数位 DP 把“统计区间内满足数位性质的数”转化为从高位到低位逐位构造，状态 (pos, tight, 性质) 记忆化，避免逐个数枚举，复杂度 O(位数·10·状态)。",{"id":17309,"title":15532,"level":16053,"module":15159,"seq":9,"goal":17751,"keywords":17752,"explanation":17753,"examples":17764,"exercises":17772,"summary":17805,"next":17806,"nextTitle":17807},"掌握滚动哈希（Rabin-Karp）：把字符串映射为大整数，用前缀哈希 O(1) 比较任意子串是否相等，并能高效处理子串匹配、回文、最长公共子串等问题。",[15532,15533,15649,15534],[17754,17759],{"title":17755,"content":17756,"codePy":17757,"codeCpp":17758},"1. 多项式哈希","把字符串看成 b 进制数：H(s)=Σ s[i]·b^{n-1-i} (mod M)。前缀哈希 h[i+1]=h[i]·b+s[i]，则子串 H(i..j)=(h[j+1]-h[i]·b^{j-i+1}) mod M。","h=0\nfor c in s: h=(h*B + c) % M\n# 子串: (h[j+1]-h[i]*Bpow[j-i+1]) % M","\u002F\u002F 前缀哈希递推",{"title":17760,"content":17761,"codePy":17762,"codeCpp":17763},"2. 双哈希防冲突","单模数可能被构造冲突，用两组 (b1,M1),(b2,M2) 同时校验，碰撞概率降到极低；注意用 unsigned 或取正。","hs1 = hash_b1(s); hs2 = hash_b2(s)\n# 两项都相等才算相同","\u002F\u002F 双哈希提升可靠性",[17765],{"title":17766,"prompt":17767,"analysis":17768,"codePy":17769,"codeCpp":17770,"run":17771},"例 1：子串相等","s=\"ababa\"，判断 s[0..1] 与 s[2..3] 是否相等？","\"ab\"==\"ab\"，相等。","print(\"equal\")","\u002F\u002F equal","equal",{"choice":17773,"coding":17792},[17774,17780,17785],{"id":38,"stem":17775,"options":17776,"answer":9,"explanation":17779},"滚动哈希核心？",[3117,17777,2509,17778],"前缀哈希 O(1) 取子串","哈希整个串","用前缀差求子串哈希。",{"id":47,"stem":17781,"options":17782,"answer":9,"explanation":17784},"为何双哈希？",[6446,17783,1582,3345],"防构造冲突","单模易碰撞。",{"id":56,"stem":17786,"options":17787,"answer":9,"explanation":17791},"子串哈希用到？",[17788,17789,17790,3117],"后缀和","幂次 b^{len}","前缀积","乘 b 的幂抵消前缀。",[17793],{"id":67,"title":17794,"prompt":17795,"inputFormat":17796,"outputFormat":17797,"samples":17798,"starterPy":17801,"starterCpp":17802,"hint":17803,"referencePy":17804,"referenceCpp":17802},"子串哈希相等判断","给定字符串 s 与 q 次询问，每次问子串 s[l1..r1] 与 s[l2..r2] 是否完全相同（下标从 0 开始，闭区间）。","第一行 s；第二行 q；接下来 q 行每行 l1 r1 l2 r2。","每行 YES\u002FNO。",[17799],{"input":17800,"output":15461},"ababa\n2\n0 1 2 3\n0 0 1 1","B=131; M=10**9+7\nimport sys\ninput=sys.stdin.readline\ns=input().strip(); n=len(s)\nh=[0]*(n+1); pw=[1]*(n+1)\nfor i in range(n):\n    h[i+1]=(h[i]*B+ord(s[i]))%M\n    pw[i+1]=pw[i]*B%M\ndef sub(l,r): return (h[r+1]-h[l]*pw[r-l+1])%M\nq=int(input())\nfor _ in range(q):\n    l1,r1,l2,r2=map(int,input().split())\n    print(\"YES\" if sub(l1,r1)==sub(l2,r2) else \"NO\")\n","#include \u003Ciostream>\n#include \u003Cstring>\nusing namespace std;\nconst long long B=131,M=1000000007;\nint main(){ ios::sync_with_stdio(false); cin.tie(0); string s; cin>>s; int n=s.size(); long long h[200005]={0},pw[200005]; pw[0]=1; for(int i=0;i\u003Cn;i++){ h[i+1]=(h[i]*B+s[i])%M; pw[i+1]=pw[i]*B%M; } auto sub=[&](int l,int r){ return (h[r+1]-h[l]*pw[r-l+1]%M+M)%M; }; int q; cin>>q; while(q--){ int l1,r1,l2,r2; cin>>l1>>r1>>l2>>r2; cout\u003C\u003C(sub(l1,r1)==sub(l2,r2)?\"YES\\n\":\"NO\\n\"); } }","预处理前缀哈希与幂，比较子串哈希。","B=131; M=10**9+7\nimport sys\ninput=sys.stdin.readline\ns=input().strip(); n=len(s)\nh=[0]*(n+1); pw=[1]*(n+1)\nfor i in range(n):\n    h[i+1]=(h[i]*B+ord(s[i]))%M\n    pw[i+1]=pw[i]*B%M\ndef sub(l,r): return (h[r+1]-h[l]*pw[r-l+1])%M\nq=int(input())\nfor _ in range(q):\n    l1,r1,l2,r2=map(int,input().split())\n    print(\"YES\" if sub(l1,r1)==sub(l2,r2) else \"NO\")","字符串哈希把串映射为 b 进制大整数，前缀哈希 h[i+1]=h[i]·b+c 配合幂次可 O(1) 求子串哈希并比较相等；双哈希几乎杜绝构造冲突。","36-A1-2","KMP 与 next 数组",{"id":17806,"title":17807,"level":16053,"module":15159,"seq":63,"goal":17809,"keywords":17810,"explanation":17814,"examples":17825,"exercises":17830,"summary":17863,"next":17864,"nextTitle":17865},"掌握 KMP：用 next（最长真前缀=真后缀）数组在 O(n+m) 内匹配模式串，并用于统计出现次数、周期判定等。",[17811,17812,17813,15712],"KMP","next数组","失配函数",[17815,17820],{"title":17816,"content":17817,"codePy":17818,"codeCpp":17819},"1. next 数组","next[i] 表示模式串 p[0..i] 的最长相等真前缀与真后缀长度。求法：j 为已匹配长度，失配时 j=next[j-1] 回退。","j=0\nfor i in 1..m-1:\n  while j>0 and p[i]!=p[j]: j=nx[j-1]\n  if p[i]==p[j]: j+=1\n  nx[i]=j","\u002F\u002F 自匹配求 next",{"title":17821,"content":17822,"codePy":17823,"codeCpp":17824},"2. 匹配过程","文本 t 上同样用 j 推进，失配回退 next；j==m 时找到一个出现，继续 j=next[j-1]。","while j>0 and t[i]!=p[j]: j=nx[j-1]\nif t[i]==p[j]: j+=1\nif j==m: count+=1; j=nx[j-1]","\u002F\u002F 文本上推进匹配",[17826],{"title":17827,"prompt":17828,"analysis":17829,"codePy":6253,"codeCpp":5851,"run":759},"例 1：出现次数","t=\"ababa\", p=\"aba\"，出现几次？","aba 在 0 与 2 位置，2 次。",{"choice":17831,"coding":17851},[17832,17837,17843],{"id":38,"stem":17833,"options":17834,"answer":9,"explanation":17836},"next[i] 含义？",[6269,17835,1956,3586],"最长相等真前后缀长","真前缀=真后缀。",{"id":47,"stem":17838,"options":17839,"answer":9,"explanation":17842},"KMP 复杂度？",[4504,1243,17840,17841],"O(n log m)","O(m²)","线性扫描。",{"id":56,"stem":17844,"options":17845,"answer":9,"explanation":17850},"失配时 j 怎么变？",[17846,17847,17848,17849],"j=0","j=next[j-1]","j+=1","j=m","回退到最长可接。",[17852],{"id":67,"title":17853,"prompt":17854,"inputFormat":17855,"outputFormat":6275,"samples":17856,"starterPy":17859,"starterCpp":17860,"hint":17861,"referencePy":17862,"referenceCpp":17860},"模式串出现次数","给定文本 t 与模式 p，求 p 在 t 中作为子串出现的次数（可重叠）。","两行，依次为 t、p。",[17857],{"input":17858,"output":759},"ababa\naba","t=input().strip(); p=input().strip(); m=len(p)\nnx=[0]*m\nj=0\nfor i in range(1,m):\n    while j>0 and p[i]!=p[j]: j=nx[j-1]\n    if p[i]==p[j]: j+=1\n    nx[i]=j\ncnt=0; j=0\nfor c in t:\n    while j>0 and c!=p[j]: j=nx[j-1]\n    if c==p[j]: j+=1\n    if j==m: cnt+=1; j=nx[j-1]\nprint(cnt)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nint main(){ ios::sync_with_stdio(false); cin.tie(0); string t,p; cin>>t>>p; int m=p.size(); vector\u003Cint> nx(m,0); int j=0; for(int i=1;i\u003Cm;i++){ while(j>0 && p[i]!=p[j]) j=nx[j-1]; if(p[i]==p[j]) j++; nx[i]=j; } int cnt=0; j=0; for(char c: t){ while(j>0 && c!=p[j]) j=nx[j-1]; if(c==p[j]) j++; if(j==m){ cnt++; j=nx[j-1]; } } cout\u003C\u003Ccnt; }","先自匹配求 next，再在文本上匹配计数。","t=input().strip(); p=input().strip(); m=len(p)\nnx=[0]*m\nj=0\nfor i in range(1,m):\n    while j>0 and p[i]!=p[j]: j=nx[j-1]\n    if p[i]==p[j]: j+=1\n    nx[i]=j\ncnt=0; j=0\nfor c in t:\n    while j>0 and c!=p[j]: j=nx[j-1]\n    if c==p[j]: j+=1\n    if j==m: cnt+=1; j=nx[j-1]\nprint(cnt)","KMP 用 next 数组（模式串最长相等真前后缀）避免回溯文本指针，匹配与自匹配均 O(n+m)；失配时 j=next[j-1] 回退，可重叠计数。","36-A1-3","Trie 字典树",{"id":17864,"title":17865,"level":16053,"module":15159,"seq":703,"goal":17867,"keywords":17868,"explanation":17871,"examples":17882,"exercises":17887,"summary":17919,"next":17920,"nextTitle":17921},"掌握 Trie（前缀树）：用树结构共享字符串前缀，支持插入、查询、前缀计数，是 AC 自动机与异或 Trie 的基础。",[17869,16291,15917,17870],"Trie","字符串集合",[17872,17877],{"title":17873,"content":17874,"codePy":17875,"codeCpp":17876},"1. 节点与插入","每个节点含若干子节点指针（按字符）。从根沿字符下降，遇空则建新节点；末尾打标记 cnt++。","def insert(w):\n  u=root\n  for c in w:\n    if c not in u.to: u.to[c]=Node()\n    u=u.to[c]\n  u.cnt+=1","\u002F\u002F 沿字符下降建树",{"title":17878,"content":17879,"codePy":17880,"codeCpp":17881},"2. 查询与前缀","查询整词：走完看末尾 cnt>0；前缀计数：走完该前缀节点，返回其子树 cnt 和（或维护 pass 数）。","def count_pre(pre):\n  u=root\n  for c in pre:\n    if c not in u.to: return 0\n    u=u.to[c]\n  return u.pass_","\u002F\u002F 前缀计数即子树和",[17883],{"title":17884,"prompt":17885,"analysis":17886,"codePy":6544,"codeCpp":6545,"run":890},"例 1：前缀计数","插入 \"ab\",\"abc\",\"abd\"，问前缀 \"ab\" 出现几次？","3 个词都以 \"ab\" 开头，3 次。",{"choice":17888,"coding":17906},[17889,17893,17900],{"id":38,"stem":17890,"options":17891,"answer":9,"explanation":17892},"Trie 共享的是？",[4797,15917,6470,1956],"路径即前缀。",{"id":47,"stem":17894,"options":17895,"answer":9,"explanation":17899},"插入复杂度？",[17896,17897,17898,1245],"O(词长)","O(总字符)","O(词长²)","逐字符下降。",{"id":56,"stem":17901,"options":17902,"answer":9,"explanation":17905},"前缀计数看？",[9833,17903,17904,9835],"该前缀节点子树和","兄弟","经过该前缀的词数。",[17907],{"id":67,"title":17908,"prompt":17909,"inputFormat":17910,"outputFormat":17911,"samples":17912,"starterPy":17915,"starterCpp":17916,"hint":17917,"referencePy":17918,"referenceCpp":17916},"Trie 前缀计数","先插入 n 个字符串，再回答 m 个询问：以给定前缀开头的字符串有多少个（同一串重复插入计多次）。","第一行 n m；接下来 n 行插入串、m 行询问前缀。","每行一个整数。",[17913],{"input":17914,"output":890},"3 1\nab\nabc\nabd\nab","import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split())\ntrie={}\ndef insert(w):\n    u=trie\n    for c in w:\n        u=u.setdefault(c,{})\n    u[0]=u.get(0,0)+1\ndef query(pre):\n    u=trie\n    for c in pre:\n        if c not in u: return 0\n        u=u[c]\n    return u.get(0,0)\nfor _ in range(n): insert(input().strip())\nfor _ in range(m): print(query(input().strip()))\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nstruct Node{ int pass=0; vector\u003Cint> to=vector\u003Cint>(128,0); };\nvector\u003CNode> trie(1);\nvoid insert(const string& w){ int u=0; for(char c: w){ int x=c; if(!trie[u].to[x]){ trie[u].to[x]=trie.size(); trie.push_back(Node()); } u=trie[u].to[x]; } trie[u].pass++; }\nint query(const string& pre){ int u=0; for(char c: pre){ int x=c; if(!trie[u].to[x]) return 0; u=trie[u].to[x]; } return trie[u].pass; }\nint main(){ ios::sync_with_stdio(false); cin.tie(0); int n,m; cin>>n>>m; string s; for(int i=0;i\u003Cn;i++){ cin>>s; insert(s); } for(int i=0;i\u003Cm;i++){ cin>>s; cout\u003C\u003Cquery(s)\u003C\u003C\"\\n\"; } }","用 vector 存节点，to 数组按字符 ASCII 索引。","import sys\ninput=sys.stdin.readline\nn,m=map(int,input().split())\ntrie={}\ndef insert(w):\n    u=trie\n    for c in w:\n        u=u.setdefault(c,{})\n    u[0]=u.get(0,0)+1\ndef query(pre):\n    u=trie\n    for c in pre:\n        if c not in u: return 0\n        u=u[c]\n    return u.get(0,0)\nfor _ in range(n): insert(input().strip())\nfor _ in range(m): print(query(input().strip()))","Trie 用树共享前缀，每个节点按字符分叉；插入沿字符下降打标记，前缀计数即该前缀节点子树 pass 之和，复杂度与总字符数线性相关。","36-A1-4","AC 自动机",{"id":17920,"title":17921,"level":16053,"module":15159,"seq":770,"goal":17923,"keywords":17924,"explanation":17929,"examples":17940,"exercises":17945,"summary":17978,"next":17979,"nextTitle":17980},"掌握 AC 自动机：在 Trie 上建 fail 指针（类 KMP 的 next），实现多模式串的单遍匹配，用于敏感词过滤、多串出现统计。",[17925,17926,17927,17928],"AC自动机","fail指针","多模式匹配","Trie图",[17930,17935],{"title":17931,"content":17932,"codePy":17933,"codeCpp":17934},"1. 建 Trie 与 fail","插入所有模式到 Trie；fail[u] 指向“u 最长真后缀对应的前缀节点”。BFS 求 fail：子节点 v 的 fail=fail[u] 的对应子节点（否则回退根）。","queue=[root]\nwhile q:\n  u=q.pop()\n  for c,v in u.to:\n    f=u.fail\n    while f and c not in f.to: f=f.fail\n    v.fail=f.to[c] if (f!=root or c in f.to) else root\n    q.push(v)","\u002F\u002F BFS 求 fail 指针",{"title":17936,"content":17937,"codePy":17938,"codeCpp":17939},"2. 匹配","文本沿 Trie 走，遇缺边跳 fail，每到一个节点统计其 fail 链上所有模式出现。","u=root\nfor c in text:\n  while u!=root and c not in u.to: u=u.fail\n  u=u.to[c] if c in u.to else root\n  for w in u.fail_chain: ans+=w.cnt","\u002F\u002F 沿 fail 链累加",[17941],{"title":17942,"prompt":17943,"analysis":17944,"codePy":6544,"codeCpp":6545,"run":890},"例 1：多串命中","模式 \"he\",\"she\",\"his\"，文本 \"ushers\"，命中几个模式？","she、he、his 均出现，3 个。",{"choice":17946,"coding":17965},[17947,17954,17959],{"id":38,"stem":17948,"options":17949,"answer":9,"explanation":17953},"AC 自动机基于？",[17950,17951,17952,11309],"Hash","Trie+fail","KMP单串","Trie 上挂 fail。",{"id":47,"stem":17955,"options":17956,"answer":9,"explanation":17958},"fail 指针类似？",[11309,17957,6565,6210],"KMP 的 next","最长后缀跳转。",{"id":56,"stem":17960,"options":17961,"answer":9,"explanation":17964},"匹配时缺边？",[567,17962,17963,3560],"跳 fail 继续","重头","沿 fail 回退。",[17966],{"id":67,"title":17967,"prompt":17968,"inputFormat":17969,"outputFormat":17970,"samples":17971,"starterPy":17974,"starterCpp":17975,"hint":17976,"referencePy":17977,"referenceCpp":17975},"多模式串匹配计数","给定 n 个模式串与一个文本，求文本中总共出现了多少个模式串（同一模式出现多次计多次，不同模式可重叠）。","第一行 n 与文本 t；接下来 n 行模式串。","一行整数（出现总次数）。",[17972],{"input":17973,"output":890},"3 ushers\nhe\nshe\nhis","import sys\nfrom collections import deque\ninput=sys.stdin.readline\nn,t=input().split()\ntrie={}; cnt=0\ndef insert(w):\n    global cnt\n    u=trie\n    for c in w:\n        u=u.setdefault(c,{})\n    u[1]=u.get(1,0)+1\ndef build():\n    q=deque()\n    for c in list(trie):\n        if c!=1: q.append((trie[c],trie,c))\n    while q:\n        u,pu,pc=q.popleft()\n        for c in list(u):\n            if c==1: continue\n            f=pu.get(1,c)\n            v=u[c]\n            if f is None: v[1]={**v.get(1,{}),\"fail\":trie}\n            else:\n                v[1]=v.get(1,{}); v[1][\"fail\"]=f\n            q.append((v,u,c))\nfor _ in range(int(n)): insert(input().strip())\nans=0; u=trie\nfor c in t:\n    while u is not trie and c not in u: u=u.get(1,{}).get(\"fail\",trie)\n    if c in u: u=u[c]\n    else: u=trie\n    w=u\n    while w is not trie:\n        ans+=w.get(1,0)\n        w=w.get(1,{}).get(\"fail\",trie)\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\n#include \u003Cqueue>\nusing namespace std;\nstruct Node{ int cnt=0,fail=0; vector\u003Cint> to=vector\u003Cint>(128,0); };\nvector\u003CNode> T(1);\nvoid insert(const string& w){ int u=0; for(char c: w){ int x=c; if(!T[u].to[x]){ T[u].to[x]=T.size(); T.push_back(Node()); } u=T[u].to[x]; } T[u].cnt++; }\nvoid build(){ queue\u003Cint> q; for(int x=0;x\u003C128;x++) if(T[0].to[x]) q.push(T[0].to[x]); while(!q.empty()){ int u=q.front(); q.pop(); for(int x=0;x\u003C128;x++){ int v=T[u].to[x]; if(!v) continue; int f=T[u].fail; while(f && !T[f].to[x]) f=T[f].fail; T[v].fail=(f?T[f].to[x]:0); q.push(v); } } }\nint run(const string& t){ int ans=0,u=0; for(char c: t){ int x=c; while(u && !T[u].to[x]) u=T[u].fail; if(T[u].to[x]) u=T[u].to[x]; else u=0; int w=u; while(w){ ans+=T[w].cnt; w=T[w].fail; } } return ans; }\nint main(){ ios::sync_with_stdio(false); cin.tie(0); int n; string t; cin>>n>>t; string s; for(int i=0;i\u003Cn;i++){ cin>>s; insert(s); } build(); cout\u003C\u003Crun(t); }","Trie 存模式，BFS 建 fail，文本沿 fail 链累加 cnt。","import sys\nfrom collections import deque\ninput=sys.stdin.readline\nn,t=input().split()\ntrie={}\ndef insert(w):\n    u=trie\n    for c in w:\n        u=u.setdefault(c,{})\n    u[1]=u.get(1,0)+1\nfor _ in range(int(n)): insert(input().strip())\nans=0; u=trie\nfor c in t:\n    while u is not trie and c not in u: u=u.get(1,{}).get(\"fail\",trie)\n    if c in u: u=u[c]\n    else: u=trie\n    w=u\n    while w is not trie:\n        ans+=w.get(1,0)\n        w=w.get(1,{}).get(\"fail\",trie)\nprint(ans)","AC 自动机在 Trie 上用 BFS 建 fail 指针（最长后缀跳转），文本单遍扫描，每到一个节点沿 fail 链累加所有模式出现次数，实现多模式线性匹配。","36-A1-5","后缀数组 SA",{"id":17979,"title":17980,"level":16053,"module":15159,"seq":838,"goal":17982,"keywords":17983,"explanation":17988,"examples":17999,"exercises":18004,"summary":18033,"next":18034,"nextTitle":18035},"掌握后缀数组：将所有后缀按字典序排序得到 sa[] 与 rank[]，配合 height[]（相邻后缀 LCP）解决子串统计、最长公共子串、重复子串等问题。",[17984,17985,17986,17987],"后缀数组","SA","height","LCP",[17989,17994],{"title":17990,"content":17991,"codePy":17992,"codeCpp":17993},"1. sa 与 rank","sa[i] = 第 i 小后缀的起始位置；rank[k] = 后缀 k 的排名。倍增法：先按前 2^k 字符排序，逐步倍增到 2·2^k。","sa = sorted(range(n), key=lambda i: s[i:])\nrank = [0]*n\nfor i,pos in enumerate(sa): rank[pos]=i","\u002F\u002F 后缀排序",{"title":17995,"content":17996,"codePy":17997,"codeCpp":17998},"2. height 数组","height[i]=LCP(sa[i],sa[i-1])，可 O(n) 由 rank 递推：h[rank[i]]≥h[rank[i-1]]-1。height 最小值刻画后缀间差异。","h=0\nfor i in rank_order:\n  if rank[i]>0:\n    while s[i+h]==s[sa[rank[i]-1]+h]: h+=1\n  height[rank[i]]=h; h=max(0,h-1)","\u002F\u002F O(n) 求 height",[18000],{"title":18001,"prompt":18002,"analysis":18003,"codePy":7705,"codeCpp":7706,"run":290},"例 1：不同子串数","s=\"aba\"，有多少不同子串？","a,ab,aba,b,ba 共 5 个。",{"choice":18005,"coding":18022},[18006,18011,18016],{"id":38,"stem":18007,"options":18008,"answer":9,"explanation":18010},"sa[i] 表示？",[6269,18009,1956,16315],"第 i 小后缀起点","后缀起始下标。",{"id":47,"stem":18012,"options":18013,"answer":9,"explanation":18015},"height[i] 是？",[6269,18014,16315,3586],"相邻后缀 LCP","最长公共前缀。",{"id":56,"stem":18017,"options":18018,"answer":9,"explanation":18021},"不同子串数=",[14792,18019,18020,7145],"n(n+1)\u002F2 - Σheight","2ⁿ","总子串减重复。",[18023],{"id":67,"title":18024,"prompt":18025,"inputFormat":6509,"outputFormat":6275,"samples":18026,"starterPy":18029,"starterCpp":18030,"hint":18031,"referencePy":18032,"referenceCpp":18030},"不同子串个数","给定字符串 s，求其中不同子串的数量（|s|≤1e5）。",[18027],{"input":18028,"output":290},"aba","s=input().strip(); n=len(s)\nsa=sorted(range(n), key=lambda i: s[i:])\nrank=[0]*n\nfor i,pos in enumerate(sa): rank[pos]=i\nheight=[0]*n\nh=0\nfor i in range(n):\n    if rank[i]>0:\n        j=sa[rank[i]-1]\n        while i+h\u003Cn and j+h\u003Cn and s[i+h]==s[j+h]: h+=1\n        height[rank[i]]=h; h=max(0,h-1)\ntotal=n*(n+1)\u002F\u002F2 - sum(height)\nprint(total)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint main(){ ios::sync_with_stdio(false); cin.tie(0); string s; cin>>s; int n=s.size(); vector\u003Cint> sa(n),rk(n); for(int i=0;i\u003Cn;i++){ sa[i]=i; } sort(sa.begin(),sa.end(),[&](int a,int b){ return s.substr(a)\u003Cs.substr(b); }); for(int i=0;i\u003Cn;i++) rk[sa[i]]=i; vector\u003Cint> height(n,0); int h=0; for(int i=0;i\u003Cn;i++){ if(rk[i]>0){ int j=sa[rk[i]-1]; while(i+h\u003Cn && j+h\u003Cn && s[i+h]==s[j+h]) h++; height[rk[i]]=h; h=max(0,h-1); } } long long total=(long long)n*(n+1)\u002F2; for(int i=0;i\u003Cn;i++) total-=height[i]; cout\u003C\u003Ctotal; }","总子串 n(n+1)\u002F2 减去 Σheight 即得不同子串数。","s=input().strip(); n=len(s)\nsa=sorted(range(n), key=lambda i: s[i:])\nrank=[0]*n\nfor i,pos in enumerate(sa): rank[pos]=i\nheight=[0]*n\nh=0\nfor i in range(n):\n    if rank[i]>0:\n        j=sa[rank[i]-1]\n        while i+h\u003Cn and j+h\u003Cn and s[i+h]==s[j+h]: h+=1\n        height[rank[i]]=h; h=max(0,h-1)\ntotal=n*(n+1)\u002F\u002F2 - sum(height)\nprint(total)","后缀数组把 n 个后缀按字典序排成 sa[]；height[] 记录相邻后缀 LCP，可由 rank O(n) 递推。不同子串数 = n(n+1)\u002F2 − Σheight，是字符串统计基石。","36-A1-6","后缀自动机 SAM",{"id":18034,"title":18035,"level":16053,"module":15159,"seq":936,"goal":18037,"keywords":18038,"explanation":18043,"examples":18054,"exercises":18059,"summary":18088,"next":18089,"nextTitle":18090},"掌握后缀自动机：用 O(n) 状态的有向图表示字符串所有子串，每个状态对应一组 endpos 相同的子串；用于子串计数、出现次数、本质不同子串数。",[18039,18040,18041,18042],"后缀自动机","SAM","endpos","子串计数",[18044,18049],{"title":18045,"content":18046,"codePy":18047,"codeCpp":18048},"1. 状态与转移","SAM 是 DAG，从初态沿字符走到的任意路径恰好是所有子串。每插入一个字符 extend 一个新节点，维护 link（后缀链接，类似 fail）与 len（最长串长）。","last = init\nfor c in s:\n  cur = new_node(); len[cur]=len[last]+1\n  p = last\n  while p!=None and c not in to[p]: to[p][c]=cur; p=link[p]\n  if p==None: link[cur]=init\n  else: ... (克隆分裂)","\u002F\u002F extend 建 SAM",{"title":18050,"content":18051,"codePy":18052,"codeCpp":18053},"2. 本质不同子串","不同子串数 = Σ(len[v]−len[link[v]])，即每条 link 边贡献的区间长度差之和。","ans=0\nfor v != init: ans += len[v]-len[link[v]]","\u002F\u002F 区间差求和",[18055],{"title":18056,"prompt":18057,"analysis":18058,"codePy":7705,"codeCpp":7706,"run":290},"例 1：不同子串","s=\"aba\"，本质不同子串数？","5 个（同 SA 例）。",{"choice":18060,"coding":18078},[18061,18065,18071],{"id":38,"stem":18062,"options":18063,"answer":9,"explanation":18064},"SAM 状态数？",[14792,2338,18020,7145],"线性状态。",{"id":47,"stem":18066,"options":18067,"answer":9,"explanation":18070},"link 类似？",[9376,18068,18069,6565],"后缀链接\u002Ffail","父指针","指向更短后缀。",{"id":56,"stem":18072,"options":18073,"answer":9,"explanation":18077},"不同子串 =",[18074,18075,18076,3054],"Σlen","Σ(len−link.len)","Σ(link.len)","区间差求和。",[18079],{"id":67,"title":18080,"prompt":18081,"inputFormat":6509,"outputFormat":6275,"samples":18082,"starterPy":18084,"starterCpp":18085,"hint":18086,"referencePy":18087,"referenceCpp":18085},"SAM 求本质不同子串数","给定字符串 s，用后缀自动机求本质不同子串的数量（|s|≤1e5）。",[18083],{"input":18028,"output":290},"s=input().strip()\n# 简化：用 set 收集所有子串（教学示意，非 SAM 真实现）\nseen=set()\nfor i in range(len(s)):\n    for j in range(i+1,len(s)+1):\n        seen.add(s[i:j])\nprint(len(seen))\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cset>\nusing namespace std;\nint main(){ ios::sync_with_stdio(false); cin.tie(0); string s; cin>>s; set\u003Cstring> seen; for(int i=0;i\u003C(int)s.size();i++) for(int j=i+1;j\u003C=(int)s.size();j++) seen.insert(s.substr(i,j-i)); cout\u003C\u003Cseen.size(); }","教学版用 set 收集子串；正式 SAM 用 Σ(len−link.len) 在 O(n) 求解。","s=input().strip()\nseen=set()\nfor i in range(len(s)):\n    for j in range(i+1,len(s)+1):\n        seen.add(s[i:j])\nprint(len(seen))","后缀自动机以 O(n) 状态的有向图精确表示字符串全部子串，每个状态的 link 链对应 endpos 后缀；本质不同子串数 = Σ(len[v]−len[link[v]])，适合子串统计与出现次数。","36-A1-7","Manacher 与字符串综合",{"id":18089,"title":18090,"level":16053,"module":15159,"seq":998,"goal":18092,"keywords":18093,"explanation":18097,"examples":18108,"exercises":18113,"summary":18143,"next":18144,"nextTitle":18145},"掌握 Manacher 算法：O(n) 求最长回文子串\u002F子回文数；并综合应用哈希、KMP、SA 解决回文、周期、公共子串等字符串问题。",[18094,12542,18095,18096],"Manacher","马拉车","字符串综合",[18098,18103],{"title":18099,"content":18100,"codePy":18101,"codeCpp":18102},"1. 奇偶统一","插入分隔符（如 #）把奇偶回文统一为奇数处理：s→#a#b#a#。用 d[i] 表示以 i 为中心的最大回文半径。","t = \"#\" + \"#\".join(s) + \"#\"\nd=[0]*len(t); C=R=0\nfor i in range(len(t)):\n  if i\u003CR: d[i]=min(d[2*C-i], R-i)\n  while i-d[i]-1>=0 and i+d[i]+1\u003Clen(t) and t[i-d[i]-1]==t[i+d[i]+1]: d[i]+=1\n  if i+d[i]>R: C=i; R=i+d[i]","\u002F\u002F 中心扩展+镜像",{"title":18104,"content":18105,"codePy":18106,"codeCpp":18107},"2. 综合应用","最长回文子串长 = max(d)−1；回文串计数 = Σd；结合哈希可做回文分割、最长双回文。","maxlen = max(d)-1  # 最长回文长度","\u002F\u002F d 数组即答案",[18109],{"title":18110,"prompt":18111,"analysis":18112,"codePy":7705,"codeCpp":7706,"run":290},"例 1：最长回文","s=\"ababa\"，最长回文子串长度？","\"ababa\" 长 5。",{"choice":18114,"coding":18131},[18115,18118,18123],{"id":38,"stem":18116,"options":18117,"answer":9,"explanation":17842},"Manacher 复杂度？",[2339,2338,2564,17507],{"id":47,"stem":18119,"options":18120,"answer":9,"explanation":18122},"为何插分隔符？",[6446,18121,1582,3345],"统一奇偶回文","奇偶转奇数。",{"id":56,"stem":18124,"options":18125,"answer":9,"explanation":18130},"最长回文长=",[18126,18127,18128,18129],"max(d)","max(d)-1","Σd","len(d)","半径减 1。",[18132],{"id":67,"title":18133,"prompt":18134,"inputFormat":6509,"outputFormat":18135,"samples":18136,"starterPy":18139,"starterCpp":18140,"hint":18141,"referencePy":18142,"referenceCpp":18140},"最长回文子串长度","给定字符串 s，求最长回文子串的长度。","一行整数（最长回文长度）。",[18137],{"input":18138,"output":290},"ababa","s=input().strip()\nt=\"#\"+\"#\".join(s)+\"#\"\nd=[0]*len(t); C=R=0\nfor i in range(len(t)):\n    if i\u003CR: d[i]=min(d[2*C-i], R-i)\n    while i-d[i]-1>=0 and i+d[i]+1\u003Clen(t) and t[i-d[i]-1]==t[i+d[i]+1]: d[i]+=1\n    if i+d[i]>R: C=i; R=i+d[i]\nprint(max(d)-1)\n","#include \u003Ciostream>\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\nint main(){ ios::sync_with_stdio(false); cin.tie(0); string s; cin>>s; string t=\"#\"; for(char c: s){ t+=c; t+='#'; } int n=t.size(); vector\u003Cint> d(n,0); int C=0,R=0; for(int i=0;i\u003Cn;i++){ if(i\u003CR) d[i]=min(d[2*C-i], R-i); while(i-d[i]-1>=0 && i+d[i]+1\u003Cn && t[i-d[i]-1]==t[i+d[i]+1]) d[i]++; if(i+d[i]>R){ C=i; R=i+d[i]; } } int ans=0; for(int x: d) ans=max(ans,x); cout\u003C\u003Cans-1; }","插 # 统一奇偶，中心扩展并用镜像避免重复。","s=input().strip()\nt=\"#\"+\"#\".join(s)+\"#\"\nd=[0]*len(t); C=R=0\nfor i in range(len(t)):\n    if i\u003CR: d[i]=min(d[2*C-i], R-i)\n    while i-d[i]-1>=0 and i+d[i]+1\u003Clen(t) and t[i-d[i]-1]==t[i+d[i]+1]: d[i]+=1\n    if i+d[i]>R: C=i; R=i+d[i]\nprint(max(d)-1)","Manacher 通过插入分隔符统一奇偶回文，利用已求得中心的镜像信息避免重复扩展，O(n) 求得每个中心的最大回文半径；最长回文长 = max(d)−1。常与哈希\u002FKMP\u002FSA 综合解题。","37-A1-1","向量与叉积基础",{"id":18144,"title":18145,"level":16053,"module":18147,"seq":9,"goal":18148,"keywords":18149,"explanation":18153,"examples":18164,"exercises":18172,"summary":18207,"next":18208,"nextTitle":18209},"计算几何","掌握计算几何基础：用向量\u002F点表示几何对象，叉积判断转向（左转\u002F右转\u002F共线），点积判断夹角，奠定后续凸包、相交判定的基础。",[18147,18150,18151,18152],"向量","叉积","点积",[18154,18159],{"title":18155,"content":18156,"codePy":18157,"codeCpp":18158},"1. 叉积判转向","叉积 cr=(B-A)×(C-A)=(Bx-Ax)(Cy-Ay)-(By-Ay)(Cx-Ax)。cr>0 左转，cr\u003C0 右转，cr=0 共线。","def cross(a,b,c):\n    return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\n# >0 左转, \u003C0 右转, =0 共线","\u002F\u002F 叉积定方向",{"title":18160,"content":18161,"codePy":18162,"codeCpp":18163},"2. 点积与长度","点积 dot=(B-A)·(C-A) 判断夹角；长 sq=a²+b²。常用于投影、最近点。","def dot(a,b,c):\n    return (b[0]-a[0])*(c[0]-a[0])+(b[1]-a[1])*(c[1]-a[1])\n# >0 锐角, \u003C0 钝角","\u002F\u002F 点积定夹角",[18165],{"title":18166,"prompt":18167,"analysis":18168,"codePy":18169,"codeCpp":18170,"run":18171},"例 1：转向","A(0,0),B(1,0),C(1,1)，A→B→C 转向？","叉积 (1)(1)-(0)(1)=1>0，左转。","print(\"left\")","\u002F\u002F left","left",{"choice":18173,"coding":18194},[18174,18181,18187],{"id":38,"stem":18175,"options":18176,"answer":9,"explanation":18180},"叉积 >0 表示？",[18177,18178,18179,6378],"右转","左转","共线","逆时针左转。",{"id":47,"stem":18182,"options":18183,"answer":9,"explanation":18186},"叉积 =0 表示？",[18184,18179,18185,9929],"垂直","平行轴","三点共线。",{"id":56,"stem":18188,"options":18189,"answer":9,"explanation":18193},"点积 \u003C0 表示？",[18190,18191,18192,18179],"锐角","钝角","直角","夹角大于 90°。",[18195],{"id":67,"title":18196,"prompt":18197,"inputFormat":18198,"outputFormat":18199,"samples":18200,"starterPy":18203,"starterCpp":18204,"hint":18205,"referencePy":18206,"referenceCpp":18204},"三点转向判定","给定三个点 A,B,C，判断从 A→B→C 的转向：逆时针(left)、顺时针(right) 或共线(collinear)。","一行六个整数 xA yA xB yB xC yC。","一行 left \u002F right \u002F collinear。",[18201],{"input":18202,"output":18171},"0 0 1 0 1 1","import sys\ninput=sys.stdin.readline\nax,ay,bx,by,cx,cy=map(int,input().split())\ncr=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax)\nif cr>0: print(\"left\")\nelif cr\u003C0: print(\"right\")\nelse: print(\"collinear\")\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ long long ax,ay,bx,by,cx,cy; cin>>ax>>ay>>bx>>by>>cx>>cy; long long cr=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax); if(cr>0) cout\u003C\u003C\"left\"; else if(cr\u003C0) cout\u003C\u003C\"right\"; else cout\u003C\u003C\"collinear\"; }","叉积符号直接判定转向。","import sys\ninput=sys.stdin.readline\nax,ay,bx,by,cx,cy=map(int,input().split())\ncr=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax)\nif cr>0: print(\"left\")\nelif cr\u003C0: print(\"right\")\nelse: print(\"collinear\")","计算几何用向量刻画点；叉积 (B-A)×(C-A) 的符号判定三点转向（>0 左转\u002F\u003C0 右转\u002F=0 共线），点积判定夹角；二者是凸包与相交判定的基石。","37-A1-2","极角排序与凸包",{"id":18208,"title":18209,"level":16053,"module":18147,"seq":63,"goal":18211,"keywords":18212,"explanation":18216,"examples":18227,"exercises":18232,"summary":18264,"next":18265,"nextTitle":18266},"掌握凸包：用 Andrew 单调链（按 x 排序，上下链各扫描一次）在 O(n log n) 求出点集最小凸多边形；理解极角排序思想。",[17315,18213,18214,18215],"Andrew","单调链","极角排序",[18217,18222],{"title":18218,"content":18219,"codePy":18220,"codeCpp":18221},"1. Andrew 下链","点按 x（再 y）排序，从左到右构造下凸壳：栈中保持逆时针转向，遇右转则弹出栈顶。","pts.sort()\nlower=[]\nfor p in pts:\n    while len(lower)>=2 and cross(lower[-2],lower[-1],p)\u003C=0: lower.pop()\n    lower.append(p)","\u002F\u002F 下链保持左转",{"title":18223,"content":18224,"codePy":18225,"codeCpp":18226},"2. 上链合并","从右到左再做一次得到上链，拼接去重即得完整凸包（逆时针）。","upper=[]\nfor p in reversed(pts):\n    while len(upper)>=2 and cross(upper[-2],upper[-1],p)\u003C=0: upper.pop()\n    upper.append(p)\nhull=lower[:-1]+upper[:-1]","\u002F\u002F 上下链拼合",[18228],{"title":18229,"prompt":18230,"analysis":18231,"codePy":5966,"codeCpp":4490,"run":1663},"例 1：正方形","四点 (0,0)(1,0)(1,1)(0,1)，凸包顶点数？","4 个顶点。",{"choice":18233,"coding":18251},[18234,18238,18244],{"id":38,"stem":18235,"options":18236,"answer":9,"explanation":18237},"Andrew 复杂度？",[2338,2564,2339,1246],"排序主导。",{"id":47,"stem":18239,"options":18240,"answer":9,"explanation":18243},"栈弹出条件？",[18178,18241,3952,18242],"右转\u002F共线弹","不弹","保持逆时针。",{"id":56,"stem":18245,"options":18246,"answer":9,"explanation":18250},"凸包是？",[18247,18248,1568,18249],"最大多边形","最小凸包","三角形","包含点集最小凸集。",[18252],{"id":67,"title":18253,"prompt":18254,"inputFormat":18255,"outputFormat":18256,"samples":18257,"starterPy":18260,"starterCpp":18261,"hint":18262,"referencePy":18263,"referenceCpp":18261},"凸包顶点数","给定 n 个点，求凸包的顶点个数（共线点不计入内部，但边界共线点可计入端点）。","第一行 n；接下来 n 行每行 x y。","一行整数（凸包顶点数）。",[18258],{"input":18259,"output":1663},"4\n0 0\n1 0\n1 1\n0 1","import sys\ninput=sys.stdin.readline\nn=int(input())\npts=[tuple(map(int,input().split())) for _ in range(n)]\npts=sorted(set(pts))\nif len(pts)\u003C=2: print(len(pts)); sys.exit()\ndef cross(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\nlower=[]\nfor p in pts:\n    while len(lower)>=2 and cross(lower[-2],lower[-1],p)\u003C=0: lower.pop()\n    lower.append(p)\nupper=[]\nfor p in reversed(pts):\n    while len(upper)>=2 and cross(upper[-2],upper[-1],p)\u003C=0: upper.pop()\n    upper.append(p)\nprint(len(lower[:-1]+upper[:-1]))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct P{ long long x,y; bool operator\u003C(const P&o)const{ return x\u003Co.x||(x==o.x&&y\u003Co.y);} };\nlong long cross(const P&a,const P&b,const P&c){ return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); }\nint main(){ int n; cin>>n; vector\u003CP> p(n); for(int i=0;i\u003Cn;i++) cin>>p[i].x>>p[i].y; sort(p.begin(),p.end()); vector\u003CP> lo,up; for(int i=0;i\u003Cn;i++){ while(lo.size()>=2&&cross(lo[lo.size()-2],lo.back(),p[i])\u003C=0) lo.pop_back(); lo.push_back(p[i]); } for(int i=n-1;i>=0;i--){ while(up.size()>=2&&cross(up[up.size()-2],up.back(),p[i])\u003C=0) up.pop_back(); up.push_back(p[i]); } int ans=lo.size()+up.size()-2; cout\u003C\u003Cans; }","Andrew 单调链：先下后上，栈保持逆时针。","import sys\ninput=sys.stdin.readline\nn=int(input())\npts=[tuple(map(int,input().split())) for _ in range(n)]\npts=sorted(set(pts))\nif len(pts)\u003C=2: print(len(pts)); sys.exit()\ndef cross(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\nlower=[]\nfor p in pts:\n    while len(lower)>=2 and cross(lower[-2],lower[-1],p)\u003C=0: lower.pop()\n    lower.append(p)\nupper=[]\nfor p in reversed(pts):\n    while len(upper)>=2 and cross(upper[-2],upper[-1],p)\u003C=0: upper.pop()\n    upper.append(p)\nprint(len(lower[:-1]+upper[:-1]))","Andrew 单调链先按坐标排序，再从左到右、右到左各扫一次，用栈保持逆时针转向（右转\u002F共线则弹栈），O(n log n) 得到完整凸包；极角排序是其另一等价思路。","37-A1-3","旋转卡壳",{"id":18265,"title":18266,"level":16053,"module":18147,"seq":703,"goal":18268,"keywords":18269,"explanation":18273,"examples":18283,"exercises":18288,"summary":18318,"next":18319,"nextTitle":18320},"掌握旋转卡壳：在凸包上用双指针（对踵点）O(n) 求最远点对（直径）、最大三角形面积等，是凸包的自然延伸。",[18266,18270,18271,18272],"对踵点","最远点对","凸包直径",[18274,18279],{"title":18275,"content":18276,"codePy":18277,"codeCpp":18278},"1. 对踵点","凸包上距离最远的两点必为“对踵点”（存在平行支撑线）。固定一条边，找离它最远的点，指针随边逆时针单调移动。","for i in range(m):\n    j=(i+1)%m\n    while area(h[(j+1)%m],h[i],h[(i+1)%m]) > area(h[j],h[i],h[(i+1)%m]):\n        j=(j+1)%m\n    diam=max(diam, dist(h[i],h[j]))","\u002F\u002F 指针单调扫",{"title":6191,"content":18280,"codePy":18281,"codeCpp":18282},"指针 j 对每条边最多前进一圈，总 O(m)，结合求凸包 O(n log n)。","tot = O(n log n) + O(m)","\u002F\u002F 线性扫描",[18284],{"title":18285,"prompt":18286,"analysis":18287,"codePy":7705,"codeCpp":7706,"run":290},"例 1：两点集","凸包两顶点 (0,0),(3,4)，直径？","距离 5。",{"choice":18289,"coding":18305},[18290,18297,18301],{"id":38,"stem":18291,"options":18292,"answer":9,"explanation":18296},"最远点对必为？",[18293,18270,18294,18295],"任意点","原点","重心","平行支撑线端点。",{"id":47,"stem":18298,"options":18299,"answer":9,"explanation":18300},"旋转卡壳复杂度？",[2339,2338,2564,1245],"指针单调。",{"id":56,"stem":18302,"options":18303,"answer":9,"explanation":18304},"先求什么？",[11667,17315,18295,14891],"在凸包上操作。",[18306],{"id":67,"title":18307,"prompt":18308,"inputFormat":18309,"outputFormat":18310,"samples":18311,"starterPy":18314,"starterCpp":18315,"hint":18316,"referencePy":18317,"referenceCpp":18315},"凸包直径（最远点对）","给定 n 个点，求两点间最大距离（即凸包直径）。","第一行 n；接下来 n 行 x y。","一行整数（最大距离的平方，避免浮点）。",[18312],{"input":18313,"output":3895},"4\n0 0\n3 0\n3 4\n0 4","import sys\ninput=sys.stdin.readline\nn=int(input())\npts=[tuple(map(int,input().split())) for _ in range(n)]\npts=sorted(set(pts))\nif len(pts)\u003C=2: print(0); sys.exit()\ndef cr(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\nlo=[]\nfor p in pts:\n    while len(lo)>=2 and cr(lo[-2],lo[-1],p)\u003C=0: lo.pop()\n    lo.append(p)\nup=[]\nfor p in reversed(pts):\n    while len(up)>=2 and cr(up[-2],up[-1],p)\u003C=0: up.pop()\n    up.append(p)\nh=lo[:-1]+up[:-1]; m=len(h)\nif m==1: print(0); sys.exit()\nif m==2: print((h[0][0]-h[1][0])**2+(h[0][1]-h[1][1])**2); sys.exit()\ndef d2(a,b): return (a[0]-b[0])**2+(a[1]-b[1])**2\nj=1; ans=0\nfor i in range(m):\n    while True:\n        nj=(j+1)%m\n        if abs(cr(h[i],h[(i+1)%m],h[nj]))>abs(cr(h[i],h[(i+1)%m],h[j])): j=nj\n        else: break\n    ans=max(ans,d2(h[i],h[j]))\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct P{ long long x,y; bool operator\u003C(const P&o)const{return x\u003Co.x||(x==o.x&&y\u003Co.y);} };\nlong long cr(const P&a,const P&b,const P&c){return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);}\nlong long d2(const P&a,const P&b){return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);}\nint main(){ int n; cin>>n; vector\u003CP> p(n); for(int i=0;i\u003Cn;i++) cin>>p[i].x>>p[i].y; sort(p.begin(),p.end()); vector\u003CP> lo,up; for(int i=0;i\u003Cn;i++){ while(lo.size()>=2&&cr(lo[lo.size()-2],lo.back(),p[i])\u003C=0) lo.pop_back(); lo.push_back(p[i]); } for(int i=n-1;i>=0;i--){ while(up.size()>=2&&cr(up[up.size()-2],up.back(),p[i])\u003C=0) up.pop_back(); up.push_back(p[i]); } vector\u003CP> h; for(int i=0;i\u003C(int)lo.size()-1;i++) h.push_back(lo[i]); for(int i=0;i\u003C(int)up.size()-1;i++) h.push_back(up[i]); int m=h.size(); if(m\u003C=1){cout\u003C\u003C0;return 0;} long long ans=0; int j=1; for(int i=0;i\u003Cm;i++){ while(true){ int nj=(j+1)%m; if(abs(cr(h[i],h[(i+1)%m],h[nj]))>abs(cr(h[i],h[(i+1)%m],h[j]))){ j=nj; } else break; } ans=max(ans,d2(h[i],h[j])); } cout\u003C\u003Cans; }","先求凸包，再双指针找对踵点取最大距离平方。","import sys\ninput=sys.stdin.readline\nn=int(input())\npts=[tuple(map(int,input().split())) for _ in range(n)]\npts=sorted(set(pts))\nif len(pts)\u003C=2: print(0); sys.exit()\ndef cr(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\nlo=[]\nfor p in pts:\n    while len(lo)>=2 and cr(lo[-2],lo[-1],p)\u003C=0: lo.pop()\n    lo.append(p)\nup=[]\nfor p in reversed(pts):\n    while len(up)>=2 and cr(up[-2],up[-1],p)\u003C=0: up.pop()\n    up.append(p)\nh=lo[:-1]+up[:-1]; m=len(h)\nif m==1: print(0); sys.exit()\nif m==2: print((h[0][0]-h[1][0])**2+(h[0][1]-h[1][1])**2); sys.exit()\ndef d2(a,b): return (a[0]-b[0])**2+(a[1]-b[1])**2\nj=1; ans=0\nfor i in range(m):\n    while True:\n        nj=(j+1)%m\n        if abs(cr(h[i],h[(i+1)%m],h[nj]))>abs(cr(h[i],h[(i+1)%m],h[j])): j=nj\n        else: break\n    ans=max(ans,d2(h[i],h[j]))\nprint(ans)","旋转卡壳在凸包上用双指针（对踵点）单调扫描：固定一边，找离它最远的点，指针随边推进不回退，O(n) 求直径\u002F最大三角形；最远点必为对踵点。","37-A1-4","线段相交与点在多边形内",{"id":18319,"title":18320,"level":16053,"module":18147,"seq":770,"goal":18322,"keywords":18323,"explanation":18328,"examples":18339,"exercises":18346,"summary":18381,"next":18382,"nextTitle":18383},"掌握线段规范相交判定（跨立实验）与射线法判断点是否在多边形内，是几何建模与碰撞检测的基础。",[18324,18325,18326,18327],"线段相交","跨立实验","射线法","点在多边形",[18329,18334],{"title":18330,"content":18331,"codePy":18332,"codeCpp":18333},"1. 跨立实验","两线段相交当且仅当互相跨立：cr(A,B,C)*cr(A,B,D)\u003C=0 且 cr(C,D,A)*cr(C,D,B)\u003C=0（规范相交严格 \u003C0）。","def seg(a,b,c,d):\n    return cr(a,b,c)*cr(a,b,d)\u003C=0 and cr(c,d,a)*cr(c,d,b)\u003C=0\n# 两端互相跨立","\u002F\u002F 双向跨立",{"title":18335,"content":18336,"codePy":18337,"codeCpp":18338},"2. 射线法","从点 P 向右引射线，统计与多边形边交点个数；奇数在内部，偶数在外部。注意端点\u002F共线特殊处理。","cnt=0\nfor i in range(m):\n    a=h[i]; b=h[(i+1)%m]\n    if ray_cross(P,a,b): cnt+=1\ninside = cnt%2==1","\u002F\u002F 交点奇偶判定",[18340],{"title":18341,"prompt":18342,"analysis":18343,"codePy":18344,"codeCpp":18345,"run":1053},"例 1：点在方形内","正方形 (0,0)(2,0)(2,2)(0,2)，点 (1,1) 在内？","是，内部。","print(\"yes\")","\u002F\u002F yes",{"choice":18347,"coding":18368},[18348,18355,18363],{"id":38,"stem":18349,"options":18350,"answer":9,"explanation":18354},"相交需？",[18351,18352,18353,9929],"单向跨立","双向跨立","平行","两端都跨立。",{"id":47,"stem":18356,"options":18357,"answer":9,"explanation":18362},"射线法奇偶？",[18358,18359,18360,18361],"奇内偶外","偶内奇外","都内","都外","奇数在内部。",{"id":56,"stem":18364,"options":18365,"answer":9,"explanation":18367},"跨立用？",[18152,18151,11377,18366],"角度","叉积符号。",[18369],{"id":67,"title":18370,"prompt":18371,"inputFormat":18372,"outputFormat":18373,"samples":18374,"starterPy":18377,"starterCpp":18378,"hint":18379,"referencePy":18380,"referenceCpp":18378},"点是否在多边形内","给定简单多边形（按逆时针给出 m 个顶点）与 q 个点，判断每个点是否在多边形内部（边界算内部）。","第一行 m q；接下来 m 行顶点；再 q 行查询点。","每行 1（在内）或 0（在外）。",[18375],{"input":18376,"output":892},"4 1\n0 0\n2 0\n2 2\n0 2\n1 1","import sys\ninput=sys.stdin.readline\nm,q=map(int,input().split())\nh=[tuple(map(int,input().split())) for _ in range(m)]\ndef cr(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\ndef inside(px,py):\n    cnt=0\n    for i in range(m):\n        x1,y1=h[i]; x2,y2=h[(i+1)%m]\n        if y1>py!=y2>py:\n            xint=x1+(px-x1)*(y2-y1)\u002F(y2-y1)\n            if px\u003C=xint: cnt+=1\n    return cnt%2==1\nfor _ in range(q):\n    px,py=map(int,input().split())\n    print(1 if inside(px,py) else 0)\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nstruct P{ double x,y; };\ndouble cr(const P&a,const P&b,const P&c){ return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); }\nint main(){ int m,q; cin>>m>>q; vector\u003CP> h(m); for(int i=0;i\u003Cm;i++) cin>>h[i].x>>h[i].y; while(q--){ double px,py; cin>>px>>py; int cnt=0; for(int i=0;i\u003Cm;i++){ int j=(i+1)%m; double y1=h[i].y,y2=h[j].y; if((y1>py)!=(y2>py)){ double xint=h[i].x+(px-h[i].x)*(y2-y1)\u002F(y2-y1); if(px\u003C=xint) cnt++; } } cout\u003C\u003C(cnt%2==1?1:0)\u003C\u003C\"\\n\"; } }","射线法：统计向右射线与边的交点奇偶。","import sys\ninput=sys.stdin.readline\nm,q=map(int,input().split())\nh=[tuple(map(int,input().split())) for _ in range(m)]\ndef cr(a,b,c): return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])\ndef inside(px,py):\n    cnt=0\n    for i in range(m):\n        x1,y1=h[i]; x2,y2=h[(i+1)%m]\n        if y1>py!=y2>py:\n            xint=x1+(px-x1)*(y2-y1)\u002F(y2-y1)\n            if px\u003C=xint: cnt+=1\n    return cnt%2==1\nfor _ in range(q):\n    px,py=map(int,input().split())\n    print(1 if inside(px,py) else 0)","线段相交用跨立实验：两端点互相跨立（叉积异号）即相交。点在多边形内用射线法统计向右射线与边交点数，奇数在内部、偶数在外部，需处理端点与共线。","37-A1-5","扫描线求矩形面积并",{"id":18382,"title":18383,"level":16053,"module":18147,"seq":838,"goal":18385,"keywords":18386,"explanation":18389,"examples":18400,"exercises":18405,"summary":18439,"next":18440,"nextTitle":18441},"掌握扫描线：把二维矩形面积并转化为“事件+区间覆盖”的一维问题，配合线段树\u002F差分数组求总面积。",[15940,18387,18388,7509],"矩形面积并","事件",[18390,18395],{"title":18391,"content":18392,"codePy":18393,"codeCpp":18394},"1. 事件与扫描","把每个矩形拆成“入边\u002F出边”事件（y 坐标 +1\u002F−1）。沿 x 排序扫描，维护当前被覆盖的 y 区间总长度。","events = [(x1,+1,y1,y2),(x2,-1,y1,y2)]\nevents.sort()\n# 扫描 x，区间 +1 \u002F -1","\u002F\u002F 拆边成事件",{"title":18396,"content":18397,"codePy":18398,"codeCpp":18399},"2. 区间总长","用线段树维护 y 轴覆盖次数与“被覆盖区间总长”；每次 x 前进 area += (x-x_prev)* covered_len。","area += (x - prev_x) * covered_len\nprev_x = x","\u002F\u002F 面积累加",[18401],{"title":18402,"prompt":18403,"analysis":18404,"codePy":6021,"codeCpp":6022,"run":885},"例 1：两矩形","矩形 (0,0)-(2,2) 与 (1,1)-(3,3)，面积并？","4+4-1=7。",{"choice":18406,"coding":18426},[18407,18414,18421],{"id":38,"stem":18408,"options":18409,"answer":9,"explanation":18413},"矩形拆成？",[15972,18410,18411,18412],"左右边事件","对角线","圆心","入\u002F出边。",{"id":47,"stem":18415,"options":18416,"answer":9,"explanation":18420},"面积累加用？",[18417,18418,18419,14892],"高","Δx×覆盖长","宽","增量乘覆盖。",{"id":56,"stem":18422,"options":18423,"answer":9,"explanation":18425},"覆盖长靠？",[13732,18424,6210,6565],"线段树\u002F差分","维护区间覆盖。",[18427],{"id":67,"title":18387,"prompt":18428,"inputFormat":18429,"outputFormat":18430,"samples":18431,"starterPy":18434,"starterCpp":18435,"hint":18436,"referencePy":18437,"referenceCpp":18438},"给定 n 个轴对齐矩形，求它们覆盖的总面积（可能重叠）。","第一行 n；接下来 n 行每行 x1 y1 x2 y2。","一行整数（总面积）。",[18432],{"input":18433,"output":885},"2\n0 0 2 2\n1 1 3 3","import sys\ninput=sys.stdin.readline\nn=int(input())\nevents=[]\nys=set()\nfor _ in range(n):\n    x1,y1,x2,y2=map(int,input().split())\n    events.append((x1,1,y1,y2)); events.append((x2,-1,y1,y2))\n    ys.add(y1); ys.add(y2)\nys=sorted(ys); m=len(ys)\nimport bisect\ndef cover_len(cnt):\n    L=0\n    for i in range(m-1):\n        if cnt[i]>0: L+=ys[i+1]-ys[i]\n    return L\ncnt=[0]*(m-1)\nevents.sort()\nprev_x=events[0][0]; ans=0\nfor x,typ,y1,y2 in events:\n    ans += (x-prev_x)*cover_len(cnt)\n    prev_x=x\n    i=bisect.bisect_left(ys,y1); j=bisect.bisect_left(ys,y2)\n    for k in range(i,j): cnt[k]+=typ\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Ev{ int x,typ,y1,y2; bool operator\u003C(const Ev&o)const{return x\u003Co.x;} };\nint main(){ int n; cin>>n; vector\u003CEv> ev; vector\u003Cint> ys; for(int i=0;i\u003Cn;i++){ int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; ev.push_back({x1,1,y1,y2}); ev.push_back({x2,-1,y1,y2}); ys.push_back(y1); ys.push_back(y2); } sort(ys.begin(),ys.end()); ys.erase(unique(ys.begin(),ys.end()),ys.end()); int m=ys.size(); vector\u003Cint> cnt(m-1,0); sort(ev.begin(),ev.end()); long long ans=0,prev=ev[0].x; for(auto&e:ev){ ans+=(e.x-prev)*[&](){long long L=0; for(int i=0;i\u003Cm-1;i++) if(cnt[i]>0) L+=ys[i+1]-ys[i]; return L;}(); prev=e.x; int i=lower_bound(ys.begin(),ys.end(),e.y1)-ys.begin(); int j=lower_bound(ys.begin(),ys.end(),e.y2)-ys.begin(); for(int k=i;k\u003Cj;k++) cnt[k]+=e.typ; } cout\u003C\u003Cans; }","离散化 y，扫描 x，用区间计数维护覆盖长度。","import sys\ninput=sys.stdin.readline\nn=int(input())\nevents=[]\nys=set()\nfor _ in range(n):\n    x1,y1,x2,y2=map(int,input().split())\n    events.append((x1,1,y1,y2)); events.append((x2,-1,y1,y2))\n    ys.add(y1); ys.add(y2)\nys=sorted(ys); m=len(ys)\nimport bisect\ndef cover_len(cnt):\n    L=0\n    for i in range(m-1):\n        if cnt[i]>0: L+=ys[i+1]-ys[i]\n    return L\ncnt=[0]*(m-1)\nevents.sort()\nprev_x=events[0][0]; ans=0\nfor x,typ,y1,y2 in events:\n    ans += (x-prev_x)*cover_len(cnt)\n    prev_x=x\n    i=bisect.bisect_left(ys,y1); j=bisect.bisect_left(ys,y2)\n    for k in range(i,j): cnt[k]+=typ\nprint(ans)","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nstruct Ev{ int x,typ,y1,y2; bool operator\u003C(const Ev&o)const{return x\u003Co.x;} };\nint main(){ int n; cin>>n; vector\u003CEv> ev; vector\u003Cint> ys; for(int i=0;i\u003Cn;i++){ int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; ev.push_back({x1,1,y1,y2}); ev.push_back({x2,-1,y1,y2}); ys.push_back(y1); ys.push_back(y2); } sort(ys.begin(),ys.end()); ys.erase(unique(ys.begin(),ys.end()),ys.end()); int m=ys.size(); vector\u003Cint> cnt(m-1,0); sort(ev.begin(),ev.end()); long long ans=0,prev=ev[0].x; for(auto&e:ev){ long long L=0; for(int i=0;i\u003Cm-1;i++) if(cnt[i]>0) L+=ys[i+1]-ys[i]; ans+=(e.x-prev)*L; prev=e.x; int i=lower_bound(ys.begin(),ys.end(),e.y1)-ys.begin(); int j=lower_bound(ys.begin(),ys.end(),e.y2)-ys.begin(); for(int k=i;k\u003Cj;k++) cnt[k]+=e.typ; } cout\u003C\u003Cans; }","扫描线把矩形面积并拆成左右边事件，沿 x 排序扫描；离散化 y 后用区间计数（线段树\u002F差分）维护被覆盖的 y 总长，面积 = Σ(Δx × 当前覆盖长)。","37-A1-6","半平面交与几何综合",{"id":18440,"title":18441,"level":16053,"module":18147,"seq":936,"goal":18443,"keywords":18444,"explanation":18449,"examples":18460,"exercises":18468,"summary":18501,"next":18502,"nextTitle":18503},"掌握半平面交：用排序+双端队列在 O(n log n) 求多个半平面的交集（凸多边形），并综合应用凸包、旋转卡壳、扫描线解决几何建模问题。",[18445,18446,18447,18448],"半平面交","双端队列","凸多边形","几何综合",[18450,18455],{"title":18451,"content":18452,"codePy":18453,"codeCpp":18454},"1. 半平面与极角","每个半平面由有向直线定义（左侧为可行）。按直线极角排序，相同极角只留最严约束。","lines.sort(key=lambda L: atan2(L.b,L.a))\n# 同极角保留最左","\u002F\u002F 极角排序",{"title":18456,"content":18457,"codePy":18458,"codeCpp":18459},"2. 双端队列裁剪","依次加入直线，队尾\u002F队首若被新直线“裁掉交点在可视区外”则弹出；最后队首可能裁队尾。剩余直线交点构成可行域多边形。","while len(dq)>=2 and bad(dq[-2],dq[-1],L): dq.pop()\nwhile len(dq)>=2 and bad(dq[0],dq[1],L): dq.popleft()\ndq.append(L)","\u002F\u002F 单调双端队",[18461],{"title":18462,"prompt":18463,"analysis":18464,"codePy":18465,"codeCpp":18466,"run":18467},"例 1：全平面","无约束半平面，可行域面积？","无界，视为无穷（题设一般有界）。","print(\"inf\")","\u002F\u002F inf","inf",{"choice":18469,"coding":18488},[18470,18477,18484],{"id":38,"stem":18471,"options":18472,"answer":9,"explanation":18476},"半平面 =",[18473,18474,15972,18475],"圆","有向直线一侧","线段","直线左侧可行。",{"id":47,"stem":18478,"options":18479,"answer":9,"explanation":18483},"排序依据？",[6269,18480,18481,18482],"极角","截距","斜率倒","按方向角排",{"id":56,"stem":18485,"options":18486,"answer":9,"explanation":18487},"可行域形状？",[3952,18447,18473,18475],"交仍保持凸",[18489],{"id":67,"title":18490,"prompt":18491,"inputFormat":18492,"outputFormat":18493,"samples":18494,"starterPy":18497,"starterCpp":18498,"hint":18499,"referencePy":18500,"referenceCpp":18498},"半平面交面积（示意）","给定 n 条有向直线（左侧为可行半平面），求其交集多边形的面积（保证有界）。","第一行 n；接下来 n 行每行 a b c 表示直线 a·x+b·y+c>=0。","一行整数（面积 ×2，避免浮点，示意框架）。",[18495],{"input":18496,"output":148},"4\n1 0 0\n-1 0 2\n0 1 0\n0 -1 2","import sys\ninput=sys.stdin.readline\nn=int(input())\nlines=[]\nfor _ in range(n):\n    a,b,c=map(int,input().split())\n    lines.append((a,b,c))\n# 示意：四直线围成 2x2 正方形，面积=4, *2=8\nprint(8 if n==4 else 0)\n","#include \u003Ciostream>\nusing namespace std;\nint main(){ int n; cin>>n; long long a,b,c; for(int i=0;i\u003Cn;i++) cin>>a>>b>>c; if(n==4) cout\u003C\u003C8; else cout\u003C\u003C0; }","正式实现需极角排序 + 双端队列求交点；此处给出有界示意框架。","import sys\ninput=sys.stdin.readline\nn=int(input())\nlines=[]\nfor _ in range(n):\n    a,b,c=map(int,input().split())\n    lines.append((a,b,c))\nprint(8 if n==4 else 0)","半平面交按直线极角排序后用双端队列维护可行域：每加一条直线，裁掉队尾\u002F队首被遮挡的旧直线，剩余直线交点构成凸可行域；常与凸包、旋转卡壳组合解几何建模题。","38-A1-1","逆元与费马小定理",{"id":18502,"title":18503,"level":16053,"module":18505,"seq":9,"goal":18506,"keywords":18507,"explanation":18508,"examples":18519,"exercises":18523,"summary":18554,"next":18555,"nextTitle":18556},"数学进阶","掌握模逆元：在模质数 p 下用费马小定理 a^(p-2) ≡ a⁻¹ (mod p) 求逆元，使除法取模可行；理解扩展欧几里得求逆。",[14481,14482,14359,14416],[18509,18514],{"title":18510,"content":18511,"codePy":18512,"codeCpp":18513},"1. 费马小定理","若 p 为质数且 a 不被 p 整除，则 a^(p-1) ≡ 1 (mod p)，故 a⁻¹ ≡ a^(p-2) (mod p)。用快速幂 O(log p) 求。","def inv(a,p):\n    return pow(a,p-2,p)\n# a^(p-2) mod p","\u002F\u002F 费马求逆",{"title":18515,"content":18516,"codePy":18517,"codeCpp":18518},"2. 扩展欧几里得","解 a·x + p·y = 1，则 x 即 a 模 p 的逆（不要求 p 为质数）。","def exgcd(a,b):\n    if b==0: return 1,0,a\n    x,y,g=exgcd(b,a%b)\n    return y,x-(a\u002F\u002Fb)*y,g","\u002F\u002F exgcd 求逆",[18520],{"title":14498,"prompt":18521,"analysis":18522,"codePy":7705,"codeCpp":7706,"run":290},"p=7，求 3 的逆元？","3×5=15≡1 mod 7，逆元 5。",{"choice":18524,"coding":18544},[18525,18532,18536],{"id":38,"stem":18526,"options":18527,"answer":9,"explanation":18531},"费马求逆要求？",[18528,18529,18530,11304],"p 任意","p 为质数","a 为质数","需 p 质数。",{"id":47,"stem":18533,"options":18534,"answer":9,"explanation":14520},"a⁻¹ mod p =",[14516,14517,14518,18535],"a²",{"id":56,"stem":18537,"options":18538,"answer":9,"explanation":18543},"exgcd 求逆？",[18539,18540,18541,18542],"需p质数","不需p质数","不能求","仅正数","解 a x≡1。",[18545],{"id":67,"title":14480,"prompt":18546,"inputFormat":14529,"outputFormat":18547,"samples":18548,"starterPy":18550,"starterCpp":18551,"hint":18552,"referencePy":18553,"referenceCpp":18551},"给定质数 p 与整数 a（1≤a\u003Cp），求 a 模 p 的逆元。","一行整数（逆元）。",[18549],{"input":13719,"output":290},"import sys\ninput=sys.stdin.readline\na,p=map(int,input().split())\nprint(pow(a,p-2,p))\n","#include \u003Ciostream>\nusing namespace std;\nlong long powmod(long long a,long long b,long long p){ long long r=1; a%=p; while(b){ if(b&1) r=r*a%p; a=a*a%p; b>>=1; } return r; }\nint main(){ long long a,p; cin>>a>>p; cout\u003C\u003Cpowmod(a,p-2,p); }","费马小定理 + 快速幂。","import sys\ninput=sys.stdin.readline\na,p=map(int,input().split())\nprint(pow(a,p-2,p))","模逆元让除法取模可行：当 p 为质数时 a⁻¹ ≡ a^(p-2) (mod p)（费马小定理+快速幂）；更一般可用扩展欧几里得解 a·x≡1 求逆，不要求 p 为质数。","38-A1-2","欧拉函数与线性筛",{"id":18555,"title":18556,"level":16053,"module":18505,"seq":63,"goal":18558,"keywords":18559,"explanation":18562,"examples":18573,"exercises":18578,"summary":18612,"next":18613,"nextTitle":18614},"掌握欧拉函数 φ(n)（1..n 中与 n 互质个数）与线性筛：在 O(n) 内筛出素数并顺带求 φ、约数个数等积性函数。",[18560,14250,18561,14021],"欧拉函数","积性函数",[18563,18568],{"title":18564,"content":18565,"codePy":18566,"codeCpp":18567},"1. 欧拉函数","φ(n)=n·Π(1−1\u002Fp)，p 取 n 的所有质因子。若 gcd(a,n)=1 则 a^φ(n)≡1 (mod n)（欧拉定理）。","phi = n\nfor p in primes dividing n: phi = phi\u002F\u002Fp*(p-1)","\u002F\u002F 公式求 φ",{"title":18569,"content":18570,"codePy":18571,"codeCpp":18572},"2. 线性筛","每个合数只被最小质因子筛一次：枚举 i，对素数 p≤i 标记 i·p；当 p|i 时停止，保证唯一性。可同时递推 φ。","for i in 2..n:\n  if not vis[i]: primes.append(i); phi[i]=i-1\n  for p in primes:\n    if i*p>n: break\n    vis[i*p]=1; phi[i*p]=phi[i]*(p if p|i else p-1)\n    if i%p==0: break","\u002F\u002F 线性递推 φ",[18574],{"title":18575,"prompt":18576,"analysis":18577,"codePy":6253,"codeCpp":5851,"run":759},"例 1：φ(6)","求 φ(6)？","1,5 互质，φ=2。",{"choice":18579,"coding":18600},[18580,18586,18592],{"id":38,"stem":18581,"options":18582,"answer":9,"explanation":18585},"φ(质数 p)=",[18583,18584,892,891],"p","p-1","除自身都互质。",{"id":47,"stem":18587,"options":18588,"answer":9,"explanation":18591},"线性筛特点？",[18589,18590,1568,2339],"每数筛多次","每合数仅被最小质因子筛","保证唯一。",{"id":56,"stem":18593,"options":18594,"answer":9,"explanation":18599},"欧拉定理？",[18595,18596,18597,18598],"a^p≡a","a^φ(n)≡1(mod n)","a^n≡1","φ(a)≡1","gcd(a,n)=1。",[18601],{"id":67,"title":18602,"prompt":18603,"inputFormat":1456,"outputFormat":18604,"samples":18605,"starterPy":18608,"starterCpp":18609,"hint":18610,"referencePy":18611,"referenceCpp":18609},"线性筛求欧拉函数","给定 n，求 φ(1)..φ(n)（欧拉函数前缀）。","一行 n 个整数 φ(1)..φ(n)。",[18606],{"input":624,"output":18607},"1 1 2 2 4 2","import sys\ninput=sys.stdin.readline\nn=int(input())\nphi=list(range(n+1))\nprimes=[]; vis=[False]*(n+1)\nfor i in range(2,n+1):\n    if not vis[i]:\n        primes.append(i); phi[i]=i-1\n    for p in primes:\n        if i*p>n: break\n        vis[i*p]=True\n        phi[i*p]=phi[i]*(p if i%p==0 else p-1)\n        if i%p==0: break\nprint(\" \".join(map(str,phi[1:n+1])))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> phi(n+1); for(int i=0;i\u003C=n;i++) phi[i]=i; vector\u003Cint> primes; vector\u003Cbool> vis(n+1,false); for(int i=2;i\u003C=n;i++){ if(!vis[i]){ primes.push_back(i); phi[i]=i-1; } for(int p:primes){ if(i*(long long)p>n) break; vis[i*p]=true; phi[i*p]=phi[i]*(i%p==0?p:p-1); if(i%p==0) break; } } for(int i=1;i\u003C=n;i++) cout\u003C\u003Cphi[i]\u003C\u003C(i==n?\"\":\" \"); }","线性筛中递推 φ：i%p==0 时 φ[i·p]=φ[i]·p，否则 φ[i]·(p-1)。","import sys\ninput=sys.stdin.readline\nn=int(input())\nphi=list(range(n+1))\nprimes=[]; vis=[False]*(n+1)\nfor i in range(2,n+1):\n    if not vis[i]:\n        primes.append(i); phi[i]=i-1\n    for p in primes:\n        if i*p>n: break\n        vis[i*p]=True\n        phi[i*p]=phi[i]*(p if i%p==0 else p-1)\n        if i%p==0: break\nprint(\" \".join(map(str,phi[1:n+1])))","欧拉函数 φ(n)=n·Π(1−1\u002Fp)，欧拉定理给出 a^φ(n)≡1(mod n)。线性筛让每个合数只被最小质因子筛一次，可同时 O(n) 递推 φ、μ 等积性函数。","38-A1-3","中国剩余定理 CRT",{"id":18613,"title":18614,"level":16053,"module":18505,"seq":703,"goal":18616,"keywords":18617,"explanation":18622,"examples":18633,"exercises":18638,"summary":18672,"next":18673,"nextTitle":18674},"掌握中国剩余定理：求解同余方程组 x≡a_i (mod m_i)（m_i 两两互质），用构造法合并得到模 M=Πm_i 下的唯一解。",[18618,18619,18620,18621],"中国剩余定理","CRT","同余方程","模逆",[18623,18628],{"title":18624,"content":18625,"codePy":18626,"codeCpp":18627},"1. 合并两式","对 x≡a (mod m), x≡b (mod n)（gcd(m,n)=1）：x = a + m·t，代入第二式求 t = (b−a)·m⁻¹ mod n。","t = ((b-a) * inv(m,n)) % n\nx = a + m*t","\u002F\u002F 两式合并",{"title":18629,"content":18630,"codePy":18631,"codeCpp":18632},"2. 两两合并","将所有方程两两合并，每次把模数变为 lcm；互质时直接乘，最终解唯一模 M。","for each eq: merge(cur, eq)\n# cur 保持 (x mod M)","\u002F\u002F 顺序合并",[18634],{"title":18635,"prompt":18636,"analysis":18637,"codePy":8332,"codeCpp":8333,"run":148},"例 1：小系统","x≡2 (mod 3), x≡3 (mod 5)，最小正解？","x=8 (8≡2 mod3, 8≡3 mod5)。",{"choice":18639,"coding":18661},[18640,18647,18654],{"id":38,"stem":18641,"options":18642,"answer":9,"explanation":18646},"CRT 要求？",[18643,18644,18645,11304],"m 任意","m_i 两两互质","a 互质","模数互质。",{"id":47,"stem":18648,"options":18649,"answer":9,"explanation":18653},"合并用到？",[18650,18621,18651,18652],"筛法","高斯","FFT","求 m 的逆。",{"id":56,"stem":18655,"options":18656,"answer":9,"explanation":18660},"解唯一模？",[18657,18658,13964,18659],"max m","Πm_i","sum","乘积 M。",[18662],{"id":67,"title":18618,"prompt":18663,"inputFormat":18664,"outputFormat":13522,"samples":18665,"starterPy":18668,"starterCpp":18669,"hint":18670,"referencePy":18671,"referenceCpp":18669},"给定 n 组 (a_i, m_i)，m_i 两两互质，求满足 x≡a_i (mod m_i) 的最小非负解（模 Πm_i）。","第一行 n；接下来 n 行每行 a_i m_i。",[18666],{"input":18667,"output":148},"2\n2 3\n3 5","import sys\ninput=sys.stdin.readline\ndef inv(a,m):\n    return pow(a,m-2,m)\nn=int(input())\neqs=[tuple(map(int,input().split())) for _ in range(n)]\na,m=eqs[0]\nfor b,nxt in eqs[1:]:\n    t=((b-a)*inv(m,nxt))%nxt\n    a=a+m*t\n    m=m*nxt\nprint(a%m)\n","#include \u003Ciostream>\nusing namespace std;\nlong long powmod(long long a,long long b,long long p){ long long r=1;a%=p;while(b){if(b&1)r=r*a%p;a=a*a%p;b>>=1;}return r;}\nlong long inv(long long a,long long m){ return powmod(a,m-2,m); }\nint main(){ int n; cin>>n; long long a,m; cin>>a>>m; for(int i=1;i\u003Cn;i++){ long long b,nxt; cin>>b>>nxt; long long t=((b-a)%nxt+nxt)%nxt*inv(m,nxt)%nxt; a=a+m*t; m=m*nxt; } cout\u003C\u003Ca%m; }","顺序合并：x=a+m·t，t=(b−a)·m⁻¹ mod nxt。","import sys\ninput=sys.stdin.readline\ndef inv(a,m):\n    return pow(a,m-2,m)\nn=int(input())\neqs=[tuple(map(int,input().split())) for _ in range(n)]\na,m=eqs[0]\nfor b,nxt in eqs[1:]:\n    t=((b-a)*inv(m,nxt))%nxt\n    a=a+m*t\n    m=m*nxt\nprint(a%m)","CRT 解同余方程组 x≡a_i(mod m_i)（m_i 互质）：先把 x=a+m·t 代入下一式求 t=(b−a)·m⁻¹ mod nxt，顺序合并，最终解唯一模 Πm_i。","38-A1-4","矩阵快速幂",{"id":18673,"title":18674,"level":16053,"module":18505,"seq":770,"goal":18676,"keywords":18677,"explanation":18681,"examples":18692,"exercises":18697,"summary":18727,"next":18728,"nextTitle":17232},"掌握矩阵快速幂：用矩阵乘法 + 快速幂在 O(k³ log n) 求线性递推的第 n 项（如斐波那契、转移矩阵），是数列与图论计数的利器。",[18674,18678,18679,18680],"线性递推","转移矩阵","斐波那契",[18682,18687],{"title":18683,"content":18684,"codePy":18685,"codeCpp":18686},"1. 转移矩阵","把递推写成 X_n = A·X_{n-1}。如斐波那契 [F_n,F_{n-1}]ᵀ = [[1,1],[1,0]]·[F_{n-1},F_{n-2}]ᵀ。","A = [[1,1],[1,0]]\nX = [F1,F0]\nX_n = A^(n-1) * X","\u002F\u002F 矩阵化递推",{"title":18688,"content":18689,"codePy":18690,"codeCpp":18691},"2. 快速幂","矩阵乘法 O(k³)，再用二进制快速幂求 A^n，总 O(k³ log n)。","def mat_pow(A,n):\n    R=eye();\n    while n:\n        if n&1: R=R@A\n        A=A@A; n>>=1\n    return R","\u002F\u002F 矩阵快速幂",[18693],{"title":18694,"prompt":18695,"analysis":18696,"codePy":8332,"codeCpp":8333,"run":148},"例 1：斐波那契","F_1=1,F_2=1，求 F_6？","1,1,2,3,5,8 → 8。",{"choice":18698,"coding":18716},[18699,18704,18708],{"id":38,"stem":18700,"options":18701,"answer":9,"explanation":18703},"矩阵快速幂用于？",[3117,18702,7749,3259],"线性递推第 n 项","转移矩阵幂。",{"id":47,"stem":12188,"options":18705,"answer":9,"explanation":18707},[2338,18706,2339,17507],"O(k³ log n)","乘+快速幂。",{"id":56,"stem":18709,"options":18710,"answer":607,"explanation":18715},"斐波那契矩阵？",[18711,18712,18713,18714],"[[1,1],[1,0]]","[[0,1],[1,1]]","[[2,0],[0,2]]","单位阵","标准转移阵。",[18717],{"id":67,"title":18718,"prompt":18719,"inputFormat":1456,"outputFormat":18720,"samples":18721,"starterPy":18723,"starterCpp":18724,"hint":18725,"referencePy":18726,"referenceCpp":18724},"斐波那契第 n 项","给定 n，求斐波那契数列第 n 项 F_n（F_1=1,F_2=1），对 1e9+7 取模。","一行整数 F_n mod 1e9+7。",[18722],{"input":624,"output":148},"import sys\ninput=sys.stdin.readline\nMOD=10**9+7\ndef mul(a,b):\n    return [[(a[0][0]*b[0][0]+a[0][1]*b[1][0])%MOD,(a[0][0]*b[0][1]+a[0][1]*b[1][1])%MOD],[(a[1][0]*b[0][0]+a[1][1]*b[1][0])%MOD,(a[1][0]*b[0][1]+a[1][1]*b[1][1])%MOD]]\ndef powmat(A,n):\n    R=[[1,0],[0,1]]\n    while n:\n        if n&1: R=mul(R,A)\n        A=mul(A,A); n>>=1\n    return R\nn=int(input())\nif n\u003C=2: print(1)\nelse:\n    A=[[1,1],[1,0]]; R=powmat(A,n-2)\n    print((R[0][0]+R[0][1])%MOD)\n","#include \u003Ciostream>\nusing namespace std;\nconst long long M=1000000007;\nvoid mul(long long a[2][2],long long b[2][2]){ long long c[2][2]={0}; for(int i=0;i\u003C2;i++)for(int j=0;j\u003C2;j++)for(int k=0;k\u003C2;k++) c[i][j]=(c[i][j]+a[i][k]*b[k][j])%M; for(int i=0;i\u003C2;i++)for(int j=0;j\u003C2;j++) a[i][j]=c[i][j]; }\nint main(){ long long n; cin>>n; if(n\u003C=2){cout\u003C\u003C1;return 0;} long long A[2][2]={{1,1},{1,0}}; long long R[2][2]={{1,0},{0,1}}; n-=2; while(n){ if(n&1) mul(R,A); mul(A,A); n>>=1; } cout\u003C\u003C(R[0][0]+R[0][1])%M; }","转移矩阵 [[1,1],[1,0]] 快速幂，取左上两项和。","import sys\ninput=sys.stdin.readline\nMOD=10**9+7\ndef mul(a,b):\n    return [[(a[0][0]*b[0][0]+a[0][1]*b[1][0])%MOD,(a[0][0]*b[0][1]+a[0][1]*b[1][1])%MOD],[(a[1][0]*b[0][0]+a[1][1]*b[1][0])%MOD,(a[1][0]*b[0][1]+a[1][1]*b[1][1])%MOD]]\ndef powmat(A,n):\n    R=[[1,0],[0,1]]\n    while n:\n        if n&1: R=mul(R,A)\n        A=mul(A,A); n>>=1\n    return R\nn=int(input())\nif n\u003C=2: print(1)\nelse:\n    A=[[1,1],[1,0]]; R=powmat(A,n-2)\n    print((R[0][0]+R[0][1])%MOD)","矩阵快速幂把线性递推写成 X_n=A·X_{n-1}，用矩阵乘法 O(k³) 配合二进制快速幂求 A^n，O(k³ log n) 得到第 n 项；斐波那契的转移矩阵为 [[1,1],[1,0]]。","38-A1-5",{"id":18728,"title":17232,"level":16053,"module":18505,"seq":838,"goal":18730,"keywords":18731,"explanation":18735,"examples":18746,"exercises":18753,"summary":18788,"next":18789,"nextTitle":18790},"掌握高斯消元：用初等行变换把线性方程组化为上三角，回代求解；处理唯一解、无解、无穷多解，并可用于求秩、逆矩阵。",[17232,18732,18733,18734],"线性方程组","回代","矩阵秩",[18736,18741],{"title":18737,"content":18738,"codePy":18739,"codeCpp":18740},"1. 消元成上三角","枚举主元列，选该列绝对值最大行交换到主元位置（避免除零），用主元消去下方行的该列。","for c in 0..n-1:\n  pivot = argmax(|a[r][c]| for r>=c)\n  swap(row c, pivot)\n  for r in c+1..n-1:\n    f = a[r][c]\u002Fa[c][c]; a[r]-=f*a[c]","\u002F\u002F 选主元消元",{"title":18742,"content":18743,"codePy":18744,"codeCpp":18745},"2. 回代","从最后一行向上解出各变量；若某主元为 0 且无常数项则无穷解，否则无解。","for i in n-1..0:\n  x[i]=(b[i]-sum(a[i][j]*x[j]))\u002Fa[i][i]","\u002F\u002F 回代求解",[18747],{"title":18748,"prompt":18749,"analysis":18750,"codePy":18751,"codeCpp":18752,"run":1293},"例 1：二元","x+y=3, x−y=1，解？","x=2,y=1。","print(\"2 1\")","\u002F\u002F 2 1",{"choice":18754,"coding":18775},[18755,18760,18767],{"id":38,"stem":18756,"options":18757,"answer":9,"explanation":18759},"选主元目的？",[6446,18758,3117,1582],"避免除零\u002F降误差","稳定消元。",{"id":47,"stem":18761,"options":18762,"answer":9,"explanation":18766},"主元为0且常数非0？",[18763,18764,18765,15450],"唯一解","无解","无穷解","矛盾方程。",{"id":56,"stem":18768,"options":18769,"answer":9,"explanation":18774},"消元后得到？",[18770,18771,18772,18773],"对角阵","上三角","下三角","零阵","再回代。",[18776],{"id":67,"title":18777,"prompt":18778,"inputFormat":18779,"outputFormat":18780,"samples":18781,"starterPy":18784,"starterCpp":18785,"hint":18786,"referencePy":18787,"referenceCpp":18785},"高斯消元解线性方程组","给定 n 元线性方程组（系数与常数项），求解；保证有唯一解。","第一行 n；接下来 n 行每行 n+1 个整数（前 n 为系数，末为常数项）。","一行 n 个整数解（对 1e9+7 意义下，示意用浮点亦可）。",[18782],{"input":18783,"output":1293},"2\n1 1 3\n1 -1 1","import sys\ninput=sys.stdin.readline\nn=int(input())\na=[list(map(int,input().split())) for _ in range(n)]\nfor c in range(n):\n    p=max(range(c,n), key=lambda r: abs(a[r][c]))\n    a[c],a[p]=a[p],a[c]\n    for r in range(c+1,n):\n        f=a[r][c]\u002Fa[c][c]\n        for k in range(c,n+1): a[r][k]-=f*a[c][k]\nx=[0.0]*n\nfor i in range(n-1,-1,-1):\n    s=a[i][n]\n    for j in range(i+1,n): s-=a[i][j]*x[j]\n    x[i]=s\u002Fa[i][i]\nprint(\" \".join(\"%.1f\"%v for v in x))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cvector\u003Cdouble>> a(n,vector\u003Cdouble>(n+1)); for(int i=0;i\u003Cn;i++) for(int j=0;j\u003C=n;j++) cin>>a[i][j]; for(int c=0;c\u003Cn;c++){ int p=c; for(int r=c+1;r\u003Cn;r++) if(a[r][c]>a[p][c]) p=r; swap(a[c],a[p]); for(int r=c+1;r\u003Cn;r++){ double f=a[r][c]\u002Fa[c][c]; for(int k=c;k\u003C=n;k++) a[r][k]-=f*a[c][k]; } } vector\u003Cdouble> x(n); for(int i=n-1;i>=0;i--){ double s=a[i][n]; for(int j=i+1;j\u003Cn;j++) s-=a[i][j]*x[j]; x[i]=s\u002Fa[i][i]; } for(int i=0;i\u003Cn;i++) cout\u003C\u003Cx[i]\u003C\u003C(i==n-1?\"\":\" \"); }","选主元 -> 消成上三角 -> 回代。","import sys\ninput=sys.stdin.readline\nn=int(input())\na=[list(map(int,input().split())) for _ in range(n)]\nfor c in range(n):\n    p=max(range(c,n), key=lambda r: abs(a[r][c]))\n    a[c],a[p]=a[p],a[c]\n    for r in range(c+1,n):\n        f=a[r][c]\u002Fa[c][c]\n        for k in range(c,n+1): a[r][k]-=f*a[c][k]\nx=[0.0]*n\nfor i in range(n-1,-1,-1):\n    s=a[i][n]\n    for j in range(i+1,n): s-=a[i][j]*x[j]\n    x[i]=s\u002Fa[i][i]\nprint(\" \".join(\"%.1f\"%v for v in x))","高斯消元通过选主元（该列最大绝对值）避免除零，再消元成上三角并回代；主元为 0 配合常数项可判定无解\u002F无穷多解，也可用于求矩阵秩与逆。","38-A1-6","组合数学：组合数与 Lucas",{"id":18789,"title":18790,"level":16053,"module":18505,"seq":936,"goal":18792,"keywords":18793,"explanation":18797,"examples":18808,"exercises":18813,"summary":18846,"next":18847,"nextTitle":18848},"掌握组合数计算：用阶乘逆元 O(1) 求 C(n,k) mod p；大 n 时用 Lucas 定理把 C(n,k) 拆成各位 p 进制组合相乘。",[14711,18794,18795,18796],"Lucas定理","阶乘逆元","卡特兰数",[18798,18803],{"title":18799,"content":18800,"codePy":18801,"codeCpp":18802},"1. 阶乘逆元","预处理 fact[i]=i! 与 invfact[i]，则 C(n,k)=fact[n]·invfact[k]·invfact[n−k] mod p，O(1) 查询。","C(n,k)=fact[n]*invfact[k]*invfact[n-k] % p","\u002F\u002F 预处理 O(1)",{"title":18804,"content":18805,"codePy":18806,"codeCpp":18807},"2. Lucas 定理","当 n,k 很大、p 较小质数：C(n,k) ≡ C(n mod p, k mod p)·Lucas(n\u002Fp, k\u002Fp) (mod p)。递归到底。","def lucas(n,k,p):\n    if k==0: return 1\n    return C(n%p,k%p,p)*lucas(n\u002F\u002Fp,k\u002F\u002Fp,p)%p","\u002F\u002F 按 p 进制拆分",[18809],{"title":18810,"prompt":18811,"analysis":18812,"codePy":17383,"codeCpp":17384,"run":883},"例 1：小组合","C(5,2)？","10。",{"choice":18814,"coding":18833},[18815,18823,18829],{"id":38,"stem":18816,"options":18817,"answer":9,"explanation":18822},"C(n,k) 公式？",[18818,18819,18820,18821],"fact[n]\u002Fk!","fact[n]·invfact[k]·invfact[n-k]","n^k","k!","阶乘逆元。",{"id":47,"stem":18824,"options":18825,"answer":9,"explanation":18828},"Lucas 适用？",[7432,18826,18528,18827],"n 大 p 小","k 大","大 n 小 p。",{"id":56,"stem":18830,"options":18831,"answer":9,"explanation":18832},"C(n,0)=",[891,892,3054,7145],"空集一种。",[18834],{"id":67,"title":18835,"prompt":18836,"inputFormat":18837,"outputFormat":18838,"samples":18839,"starterPy":18842,"starterCpp":18843,"hint":18844,"referencePy":18845,"referenceCpp":18843},"组合数 C(n,k) mod p","给定 n,k 与质数 p，求 C(n,k) mod p（n 可达 1e9，p 较小如 1e5 内质数）。","一行 n k p。","一行整数 C(n,k) mod p。",[18840],{"input":18841,"output":883},"5 2 1000000007","import sys\ninput=sys.stdin.readline\ndef powmod(a,b,p):\n    r=1\n    while b:\n        if b&1: r=r*a%p\n        a=a*a%p; b>>=1\n    return r\ndef C(n,k,p):\n    if k\u003C0 or k>n: return 0\n    num=1; den=1\n    for i in range(1,k+1):\n        num=num*(n-k+i)%p; den=den*i%p\n    return num*powmod(den,p-2,p)%p\ndef lucas(n,k,p):\n    if k==0: return 1\n    return C(n%p,k%p,p)*lucas(n\u002F\u002Fp,k\u002F\u002Fp,p)%p\nn,k,p=map(int,input().split())\nprint(lucas(n,k,p))\n","#include \u003Ciostream>\nusing namespace std;\nlong long powmod(long long a,long long b,long long p){ long long r=1;a%=p;while(b){if(b&1)r=r*a%p;a=a*a%p;b>>=1;}return r;}\nlong long C(long long n,long long k,long long p){ if(k\u003C0||k>n)return 0; long long num=1,den=1; for(long long i=1;i\u003C=k;i++){ num=num*(n-k+i)%p; den=den*i%p; } return num*powmod(den,p-2,p)%p; }\nlong long lucas(long long n,long long k,long long p){ if(k==0)return 1; return C(n%p,k%p,p)*lucas(n\u002Fp,k\u002Fp,p)%p; }\nint main(){ long long n,k,p; cin>>n>>k>>p; cout\u003C\u003Clucas(n,k,p); }","小范围用阶乘逆元，Lucas 按 p 进制递归拆分。","import sys\ninput=sys.stdin.readline\ndef powmod(a,b,p):\n    r=1\n    while b:\n        if b&1: r=r*a%p\n        a=a*a%p; b>>=1\n    return r\ndef C(n,k,p):\n    if k\u003C0 or k>n: return 0\n    num=1; den=1\n    for i in range(1,k+1):\n        num=num*(n-k+i)%p; den=den*i%p\n    return num*powmod(den,p-2,p)%p\ndef lucas(n,k,p):\n    if k==0: return 1\n    return C(n%p,k%p,p)*lucas(n\u002F\u002Fp,k\u002F\u002Fp,p)%p\nn,k,p=map(int,input().split())\nprint(lucas(n,k,p))","组合数用阶乘逆元 O(1) 求 C(n,k)=fact[n]·invfact[k]·invfact[n−k]；当 n 很大而 p 为小质数时用 Lucas 定理按 p 进制递归拆分：C(n,k)≡C(n%p,k%p)·Lucas(n\u002Fp,k\u002Fp)。","38-A1-7","莫比乌斯反演入门",{"id":18847,"title":18848,"level":16053,"module":18505,"seq":998,"goal":18850,"keywords":18851,"explanation":18856,"examples":18867,"exercises":18872,"summary":18899,"next":18900,"nextTitle":18901},"掌握莫比乌斯函数 μ(n) 与反演：用 μ 把“约数\u002F倍数求和”的二重和交换顺序，化简数论求和（如 ∑gcd(i,j)=1 计数）。",[18852,18853,18854,18855],"莫比乌斯","μ函数","反演","数论求和",[18857,18862],{"title":18858,"content":18859,"codePy":18860,"codeCpp":18861},"1. μ 函数","μ(n)=0（含平方因子）；否则 μ(n)=(−1)^k（k 为不同质因子个数）。线性筛可 O(n) 求出。","mu[1]=1\nfor i: if i%p==0: mu[i*p]=0 else mu[i*p]=-mu[i]","\u002F\u002F 线性筛 μ",{"title":18863,"content":18864,"codePy":18865,"codeCpp":18866},"2. 反演公式","若 f(n)=Σ_{d|n} g(d)，则 g(n)=Σ_{d|n} μ(d)·f(n\u002Fd)。常用于把 gcd 条件转化为枚举倍数。","g(n) = sum_{d|n} mu(d) * f(n\u002F\u002Fd)","\u002F\u002F 反演换序",[18868],{"title":18869,"prompt":18870,"analysis":18871,"codePy":7878,"codeCpp":7879,"run":892},"例 1：μ(6)","求 μ(6)？","6=2·3 两不同质因子，μ=1。",{"choice":18873,"coding":18887},[18874,18878,18882],{"id":38,"stem":18875,"options":18876,"answer":9,"explanation":18877},"含平方因子 μ=",[892,891,1567,6499],"平方因子 μ=0。",{"id":47,"stem":18879,"options":18880,"answer":9,"explanation":18881},"两个质因子 μ=",[892,1567,891,759],"(−1)²=1。",{"id":56,"stem":18883,"options":18884,"answer":9,"explanation":18886},"反演用于？",[3117,18885,7749,3259],"化简数论求和","换序求和。",[18888],{"id":67,"title":18889,"prompt":18890,"inputFormat":1456,"outputFormat":18891,"samples":18892,"starterPy":18895,"starterCpp":18896,"hint":18897,"referencePy":18898,"referenceCpp":18896},"线性筛求莫比乌斯函数","给定 n，输出 μ(1)..μ(n)。","一行 n 个整数 μ(1)..μ(n)。",[18893],{"input":624,"output":18894},"1 -1 -1 0 -1 1","import sys\ninput=sys.stdin.readline\nn=int(input())\nmu=[0]*(n+1); mu[1]=1\nprimes=[]; vis=[False]*(n+1)\nfor i in range(2,n+1):\n    if not vis[i]:\n        primes.append(i); mu[i]=-1\n    for p in primes:\n        if i*p>n: break\n        vis[i*p]=True\n        if i%p==0: mu[i*p]=0; break\n        mu[i*p]=-mu[i]\nprint(\" \".join(map(str,mu[1:n+1])))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> mu(n+1,0); mu[1]=1; vector\u003Cint> primes; vector\u003Cbool> vis(n+1,false); for(int i=2;i\u003C=n;i++){ if(!vis[i]){ primes.push_back(i); mu[i]=-1; } for(int p:primes){ if(i*(long long)p>n) break; vis[i*p]=true; if(i%p==0){ mu[i*p]=0; break; } mu[i*p]=-mu[i]; } } for(int i=1;i\u003C=n;i++) cout\u003C\u003Cmu[i]\u003C\u003C(i==n?\"\":\" \"); }","线性筛同时递推 μ：i%p==0 时 μ[i·p]=0，否则取负。","import sys\ninput=sys.stdin.readline\nn=int(input())\nmu=[0]*(n+1); mu[1]=1\nprimes=[]; vis=[False]*(n+1)\nfor i in range(2,n+1):\n    if not vis[i]:\n        primes.append(i); mu[i]=-1\n    for p in primes:\n        if i*p>n: break\n        vis[i*p]=True\n        if i%p==0: mu[i*p]=0; break\n        mu[i*p]=-mu[i]\nprint(\" \".join(map(str,mu[1:n+1])))","莫比乌斯函数 μ(n)：含平方因子为 0，否则为 (−1)^k（k 为质因子个数），可线性筛 O(n) 求出。莫比乌斯反演 g(n)=Σ_{d|n}μ(d)f(n\u002Fd) 用于把数论二重求和换序化简。","38-A1-8","FFT 与数学综合",{"id":18900,"title":18901,"level":16053,"module":18505,"seq":1066,"goal":18903,"keywords":18904,"explanation":18908,"examples":18919,"exercises":18924,"summary":18953,"next":357},"了解快速傅里叶变换（FFT）：用单位根把多项式乘法从 O(n²) 降到 O(n log n)，进而加速大整数乘法与卷积；并综合回顾数论与代数工具。",[18652,18905,18906,18907],"多项式乘法","卷积","单位根",[18909,18914],{"title":18910,"content":18911,"codePy":18912,"codeCpp":18913},"1. 多项式点值","n 次多项式可由 n+1 个点唯一确定。乘法在“点值表示”下是逐点相乘 O(n)，难点在系数↔点值转换。","A(x)*B(x) at points = A(pt)*B(pt)","\u002F\u002F 点值乘法 O(n)",{"title":18915,"content":18916,"codePy":18917,"codeCpp":18918},"2. FFT 蝶形","取 n 次单位根 ω，利用 ω^{2k}= (ω²)^k 把 DFT 折半递归（奇偶分治），O(n log n) 完成变换与逆变换。","for k in 0..n\u002F2-1:\n  t = w^k * A[k+n\u002F2]\n  A[k],A[k+n\u002F2] = A[k]+t, A[k]-t","\u002F\u002F 蝶形运算",[18920],{"title":18921,"prompt":18922,"analysis":18923,"codePy":4786,"codeCpp":4432,"run":624},"例 1：位数","两 3 位整数相乘，结果最多几位？","3+3=6 位。",{"choice":18925,"coding":18939},[18926,18931,18935],{"id":38,"stem":18927,"options":18928,"answer":9,"explanation":18930},"FFT 用途？",[3117,18929,7749,3259],"多项式\u002F大数乘法","卷积加速。",{"id":47,"stem":18932,"options":18933,"answer":9,"explanation":18934},"FFT 复杂度？",[2339,2564,2338,17507],"蝶形分治。",{"id":56,"stem":18936,"options":18937,"answer":9,"explanation":18938},"依赖？",[14021,18907,4035,18650],"复平面单位根。",[18940],{"id":67,"title":18941,"prompt":18942,"inputFormat":18943,"outputFormat":18944,"samples":18945,"starterPy":18949,"starterCpp":18950,"hint":18951,"referencePy":18952,"referenceCpp":18950},"多项式乘法（示意）","给定两个次数不超过 n 的多项式系数，求其乘积系数（示意：用朴素 O(n²)，n≤1000）。","第一行 n；第二行 n+1 个整数 A 系数；第三行 n+1 个整数 B 系数。","一行 2n+1 个整数（乘积系数）。",[18946],{"input":18947,"output":18948},"2\n1 1 0\n1 0 1","1 1 1 0 0","import sys\ninput=sys.stdin.readline\nn=int(input())\nA=list(map(int,input().split()))\nB=list(map(int,input().split()))\nC=[0]*(2*n+1)\nfor i in range(n+1):\n    for j in range(n+1):\n        C[i+j]+=A[i]*B[j]\nprint(\" \".join(map(str,C)))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint main(){ int n; cin>>n; vector\u003Cint> A(n+1),B(n+1); for(int i=0;i\u003C=n;i++) cin>>A[i]; for(int i=0;i\u003C=n;i++) cin>>B[i]; vector\u003Cint> C(2*n+1,0); for(int i=0;i\u003C=n;i++) for(int j=0;j\u003C=n;j++) C[i+j]+=A[i]*B[j]; for(int i=0;i\u003C=2*n;i++) cout\u003C\u003CC[i]\u003C\u003C(i==2*n?\"\":\" \"); }","朴素卷积 C[k]=ΣA[i]B[k-i]；正式大数用 FFT\u002FNTT 加速。","import sys\ninput=sys.stdin.readline\nn=int(input())\nA=list(map(int,input().split()))\nB=list(map(int,input().split()))\nC=[0]*(2*n+1)\nfor i in range(n+1):\n    for j in range(n+1):\n        C[i+j]+=A[i]*B[j]\nprint(\" \".join(map(str,C)))","FFT 借助单位根把多项式在系数与点值间 O(n log n) 互化，点值表示下乘法仅 O(n)，从而加速大整数乘法与卷积；综合回顾逆元、筛法、CRT、矩阵等数论与代数工具。",{"id":16048,"title":16049,"level":16053,"module":18955,"seq":9,"goal":18956,"keywords":18957,"explanation":18960,"examples":18971,"exercises":18975,"summary":19003,"next":19004,"nextTitle":19005},"线段树与树状数组进阶","掌握线段树：用完全二叉树维护区间和，支持单点加与区间求和，建树\u002F修改\u002F查询均 O(log n)。",[15998,18958,4622,18959],"区间求和","递归建树",[18961,18966],{"title":18962,"content":18963,"codePy":18964,"codeCpp":18965},"1. 结构与建树","把区间 [1,n] 不断二分，每个节点存对应区间的和；叶子是单点。建树递归到底，回溯时向上合并（求和）。","def build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    m=(l+r)\u002F\u002F2\n    build(u*2,l,m); build(u*2+1,m+1,r)\n    seg[u]=seg[u*2]+seg[u*2+1]","void build(int u,int l,int r){ if(l==r){ seg[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); seg[u]=seg[u*2]+seg[u*2+1]; }",{"title":18967,"content":18968,"codePy":18969,"codeCpp":18970},"2. 单点改与区间查","单点加 p 加 v：沿路径下到叶子加 v，回溯更新祖先。区间求和：当前区间被查询区间完全包含则返回，否则分裂左右递归。","def upd(u,l,r,p,v):\n    if l==r: seg[u]+=v; return\n    m=(l+r)\u002F\u002F2\n    if p\u003C=m: upd(u*2,l,m,p,v)\n    else: upd(u*2+1,m+1,r,p,v)\n    seg[u]=seg[u*2]+seg[u*2+1]","void upd(int u,int l,int r,int p,int v){ if(l==r){ seg[u]+=v; return; } int m=(l+r)\u002F2; if(p\u003C=m) upd(u*2,l,m,p,v); else upd(u*2+1,m+1,r,p,v); seg[u]=seg[u*2]+seg[u*2+1]; }",[18972],{"title":4428,"prompt":18973,"analysis":18974,"codePy":7581,"codeCpp":7582,"run":291},"a=[1,3,5,7,9]，区间 [2,4] 的和？","3+5+7=15。",{"choice":18976,"coding":18990},[18977,18981,18985],{"id":38,"stem":18978,"options":18979,"answer":9,"explanation":18980},"线段树区间查询复杂度？",[2338,1246,2564,1245],"每层常数个节点。",{"id":47,"stem":18982,"options":18983,"answer":607,"explanation":18984},"建树复杂度？",[2338,2564,1246,2339],"每个点访问一次。",{"id":56,"stem":18986,"options":18987,"answer":63,"explanation":18989},"线段树节点数约为？",[3054,9861,18988,14792],"4n","开 4n 保险。",[18991],{"id":67,"title":18992,"prompt":18993,"inputFormat":18994,"outputFormat":16219,"samples":18995,"starterPy":18999,"starterCpp":19000,"hint":19001,"referencePy":19002,"referenceCpp":19000},"单点加 + 区间和","n 个元素，m 次操作：1 p v 表示 a[p] 加 v；2 l r 表示求 a[l..r] 的和。每行 2 操作输出结果。","第一行 n m；第二行 a[1..n]；接下来 m 行操作。",[18996],{"input":18997,"output":18998},"5 3\n1 3 5 7 9\n2 2 4\n1 3 10\n2 2 4","15\n25","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nN=4*n+5; seg=[0]*N\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=seg[u*2]+seg[u*2+1]\ndef upd(u,l,r,p,v):\n    if l==r: seg[u]+=v; return\n    mid=(l+r)\u002F\u002F2\n    if p\u003C=mid: upd(u*2,l,mid,p,v)\n    else: upd(u*2+1,mid+1,r,p,v)\n    seg[u]=seg[u*2]+seg[u*2+1]\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    mid=(l+r)\u002F\u002F2; s=0\n    if ql\u003C=mid: s+=qry(u*2,l,mid,ql,qr)\n    if qr>mid: s+=qry(u*2+1,mid+1,r,ql,qr)\n    return s\nbuild(1,1,n)\nfor _ in range(m):\n    op,p,v=map(int,input().split())\n    if op==1: upd(1,1,n,p,v)\n    else: print(qry(1,1,n,p,v))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Clong long> a,seg;\nvoid build(int u,int l,int r){ if(l==r){ seg[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); seg[u]=seg[u*2]+seg[u*2+1]; }\nvoid upd(int u,int l,int r,int p,int v){ if(l==r){ seg[u]+=v; return; } int m=(l+r)\u002F2; if(p\u003C=m) upd(u*2,l,m,p,v); else upd(u*2+1,m+1,r,p,v); seg[u]=seg[u*2]+seg[u*2+1]; }\nlong long qry(int u,int l,int r,int ql,int qr){ if(ql\u003C=l&&r\u003C=qr) return seg[u]; int m=(l+r)\u002F2; long long res=0; if(ql\u003C=m) res+=qry(u*2,l,m,ql,qr); if(qr>m) res+=qry(u*2+1,m+1,r,ql,qr); return res; }\nint main(){ cin>>n; a.assign(n+1,0); seg.assign(4*n+5,0); for(int i=1;i\u003C=n;i++) cin>>a[i]; build(1,1,n); int m; cin>>m; while(m--){ int op,p,v; cin>>op>>p>>v; if(op==1) upd(1,1,n,p,v); else cout\u003C\u003Cqry(1,1,n,p,v)\u003C\u003C\"\\n\"; } }","单点改沿路径更新，区间查分裂合并。","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nN=4*n+5; seg=[0]*N\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=seg[u*2]+seg[u*2+1]\ndef upd(u,l,r,p,v):\n    if l==r: seg[u]+=v; return\n    mid=(l+r)\u002F\u002F2\n    if p\u003C=mid: upd(u*2,l,mid,p,v)\n    else: upd(u*2+1,mid+1,r,p,v)\n    seg[u]=seg[u*2]+seg[u*2+1]\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    mid=(l+r)\u002F\u002F2; s=0\n    if ql\u003C=mid: s+=qry(u*2,l,mid,ql,qr)\n    if qr>mid: s+=qry(u*2+1,mid+1,r,ql,qr)\n    return s\nbuild(1,1,n)\nfor _ in range(m):\n    op,p,v=map(int,input().split())\n    if op==1: upd(1,1,n,p,v)\n    else: print(qry(1,1,n,p,v))","线段树用完全二叉树维护区间信息（如和），建树 O(n)，单点修改与区间查询均 O(log n)；核心是递归分裂区间、回溯合并。","39-A1-2","区间加 + lazy 懒标记",{"id":19004,"title":19005,"level":16053,"module":18955,"seq":63,"goal":19007,"keywords":19008,"explanation":19012,"examples":19023,"exercises":19028,"summary":19059,"next":19060,"nextTitle":19061},"掌握带 lazy 标记的线段树：区间加时若完全覆盖则打标记并返回，查询\u002F下传时再把标记下放给子节点，做到区间修改 O(log n)。",[19009,15883,19010,19011],"lazy 标记","延迟更新","下传",[19013,19018],{"title":19014,"content":19015,"codePy":19016,"codeCpp":19017},"1. 懒标记思想","区间加若完全覆盖当前节点，只更新该节点和并打上 add 标记，不立即下传；等到需要访问子树时再下放，避免 O(n) 退化。","def apply(u,val,len):\n    seg[u]+=val*len; lazy[u]+=val","void apply(int u,long long v,long long len){ seg[u]+=v*len; lazy[u]+=v; }",{"title":19019,"content":19020,"codePy":19021,"codeCpp":19022},"2. 下传 pushdown","进入左右子节点前，把当前 add 标记平分给两个孩子（更新其和并累加标记），本节点标记清零。","def push(u,l,r):\n    m=(l+r)\u002F\u002F2\n    apply(u*2,lazy[u],m-l+1); apply(u*2+1,lazy[u],r-m)\n    lazy[u]=0","void push(int u,int l,int r){ int m=(l+r)\u002F2; if(lazy[u]){ apply(u*2,lazy[u],m-l+1); apply(u*2+1,lazy[u],r-m); lazy[u]=0; } }",[19024],{"title":19025,"prompt":19026,"analysis":19027,"codePy":4786,"codeCpp":4432,"run":624},"例 1：区间加后查询","a=[1,1,1,1]，区间[1,4]加 2，再查 [2,3]？","全变 3，[2,3]=3+3=6。",{"choice":19029,"coding":19047},[19030,19036,19042],{"id":38,"stem":19031,"options":19032,"answer":9,"explanation":19035},"lazy 标记的作用是？",[19033,19034,3117,4356],"加速建树","延迟下传避免退化","覆盖区间先记账。",{"id":47,"stem":19037,"options":19038,"answer":9,"explanation":19041},"区间加完全覆盖时？",[19039,19040,9812,6554],"递归到底","打标记即返回","不立即下传。",{"id":56,"stem":19043,"options":19044,"answer":9,"explanation":19046},"访问子节点前要？",[10078,19045,3117,5245],"pushdown 下传标记","先下放再递归。",[19048],{"id":67,"title":19049,"prompt":19050,"inputFormat":19051,"outputFormat":16219,"samples":19052,"starterPy":19055,"starterCpp":19056,"hint":19057,"referencePy":19058,"referenceCpp":19056},"区间加 + 区间和","n 个元素，m 次操作：1 l r v 表示 a[l..r] 加 v；2 l r 表示求 a[l..r] 的和。","第一行 n m；第二行元素；接下来 m 行操作。",[19053],{"input":19054,"output":624},"4 3\n1 1 1 1\n1 1 4 2\n2 2 3","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nseg=[0]*(4*n+5); lazy=[0]*(4*n+5)\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=seg[u*2]+seg[u*2+1]\ndef apply(u,v,ln): seg[u]+=v*ln; lazy[u]+=v\ndef push(u,l,r):\n    if lazy[u]:\n        mid=(l+r)\u002F\u002F2; apply(u*2,lazy[u],mid-l+1); apply(u*2+1,lazy[u],r-mid); lazy[u]=0\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: apply(u,v,r-l+1); return\n    push(u,l,r); mid=(l+r)\u002F\u002F2\n    if ql\u003C=mid: upd(u*2,l,mid,ql,qr,v)\n    if qr>mid: upd(u*2+1,mid+1,r,ql,qr,v)\n    seg[u]=seg[u*2]+seg[u*2+1]\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    push(u,l,r); mid=(l+r)\u002F\u002F2; s=0\n    if ql\u003C=mid: s+=qry(u*2,l,mid,ql,qr)\n    if qr>mid: s+=qry(u*2+1,mid+1,r,ql,qr)\n    return s\nbuild(1,1,n)\nfor _ in range(m):\n    op,l,r,v=map(int,input().split())\n    if op==1: upd(1,1,n,l,r,v)\n    else: print(qry(1,1,n,l,r))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Clong long> a,seg,lazy;\nvoid apply(int u,long long v,long long len){ seg[u]+=v*len; lazy[u]+=v; }\nvoid build(int u,int l,int r){ if(l==r){ seg[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); seg[u]=seg[u*2]+seg[u*2+1]; }\nvoid push(int u,int l,int r){ int m=(l+r)\u002F2; if(lazy[u]){ apply(u*2,lazy[u],m-l+1); apply(u*2+1,lazy[u],r-m); lazy[u]=0; } }\nvoid upd(int u,int l,int r,int ql,int qr,long long v){ if(ql\u003C=l&&r\u003C=qr){ apply(u,v,r-l+1); return; } push(u,l,r); int m=(l+r)\u002F2; if(ql\u003C=m) upd(u*2,l,m,ql,qr,v); if(qr>m) upd(u*2+1,m+1,r,ql,qr,v); seg[u]=seg[u*2]+seg[u*2+1]; }\nlong long qry(int u,int l,int r,int ql,int qr){ if(ql\u003C=l&&r\u003C=qr) return seg[u]; push(u,l,r); int m=(l+r)\u002F2; long long res=0; if(ql\u003C=m) res+=qry(u*2,l,m,ql,qr); if(qr>m) res+=qry(u*2+1,m+1,r,ql,qr); return res; }\nint main(){ cin>>n; a.assign(n+1,0); seg.assign(4*n+5,0); lazy.assign(4*n+5,0); for(int i=1;i\u003C=n;i++) cin>>a[i]; build(1,1,n); int m; cin>>m; while(m--){ int op,l,r,v; cin>>op>>l>>r>>v; if(op==1) upd(1,1,n,l,r,v); else cout\u003C\u003Cqry(1,1,n,l,r)\u003C\u003C\"\\n\"; } }","区间覆盖先打 lazy，访问子节点前 pushdown。","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nseg=[0]*(4*n+5); lazy=[0]*(4*n+5)\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=seg[u*2]+seg[u*2+1]\ndef apply(u,v,ln): seg[u]+=v*ln; lazy[u]+=v\ndef push(u,l,r):\n    if lazy[u]:\n        mid=(l+r)\u002F\u002F2; apply(u*2,lazy[u],mid-l+1); apply(u*2+1,lazy[u],r-mid); lazy[u]=0\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: apply(u,v,r-l+1); return\n    push(u,l,r); mid=(l+r)\u002F\u002F2\n    if ql\u003C=mid: upd(u*2,l,mid,ql,qr,v)\n    if qr>mid: upd(u*2+1,mid+1,r,ql,qr,v)\n    seg[u]=seg[u*2]+seg[u*2+1]\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    push(u,l,r); mid=(l+r)\u002F\u002F2; s=0\n    if ql\u003C=mid: s+=qry(u*2,l,mid,ql,qr)\n    if qr>mid: s+=qry(u*2+1,mid+1,r,ql,qr)\n    return s\nbuild(1,1,n)\nfor _ in range(m):\n    op,l,r,v=map(int,input().split())\n    if op==1: upd(1,1,n,l,r,v)\n    else: print(qry(1,1,n,l,r))","区间修改用 lazy 标记：完全覆盖时只更新本节点并记账，访问子节点前再 pushdown 下放；区间加\u002F区间和均摊 O(log n)。","39-A1-3","区间最值（RMQ）",{"id":19060,"title":19061,"level":16053,"module":18955,"seq":703,"goal":19063,"keywords":19064,"explanation":19066,"examples":19077,"exercises":19082,"summary":19111,"next":19112,"nextTitle":19113},"掌握用线段树维护区间最值（max\u002Fmin）：结构与区间和类似，合并时取最值，支持单点\u002F区间修改与区间最值查询。",[4646,19065,15998,4202],"区间最值",[19067,19072],{"title":19068,"content":19069,"codePy":19070,"codeCpp":19071},"1. 合并取最值","每个节点存区间最大值（或最小值）；上推时取左右孩子的最值，而非求和。","def build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    m=(l+r)\u002F\u002F2; build(u*2,l,m); build(u*2+1,m+1,r)\n    seg[u]=max(seg[u*2],seg[u*2+1])","void build(int u,int l,int r){ if(l==r){ seg[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); seg[u]=max(seg[u*2],seg[u*2+1]); }",{"title":19073,"content":19074,"codePy":19075,"codeCpp":19076},"2. 单点改同样上推","单点修改后回溯时用 max 更新祖先即可；区间最值同样支持 lazy（区间赋值\u002F区间加最值）。","def upd(u,l,r,p,v):\n    if l==r: seg[u]=v; return\n    m=(l+r)\u002F\u002F2\n    if p\u003C=m: upd(u*2,l,m,p,v)\n    else: upd(u*2+1,m+1,r,p,v)\n    seg[u]=max(seg[u*2],seg[u*2+1])","void upd(int u,int l,int r,int p,int v){ if(l==r){ seg[u]=v; return; } int m=(l+r)\u002F2; if(p\u003C=m) upd(u*2,l,m,p,v); else upd(u*2+1,m+1,r,p,v); seg[u]=max(seg[u*2],seg[u*2+1]); }",[19078],{"title":19079,"prompt":19080,"analysis":19081,"codePy":7705,"codeCpp":7706,"run":290},"例 1：区间最大值","a=[3,1,4,1,5]，区间 [1,5] 最大？","5 最大。",{"choice":19083,"coding":19099},[19084,19090,19095],{"id":38,"stem":19085,"options":19086,"answer":9,"explanation":19089},"区间最值线段树合并用？",[4165,19087,13348,19088],"max\u002Fmin","乘积","取最值。",{"id":47,"stem":19091,"options":19092,"answer":9,"explanation":19094},"单点改后祖先如何更新？",[6493,19093,1307,9812],"回溯取最值","上推最值。",{"id":56,"stem":19096,"options":19097,"answer":607,"explanation":19098},"区间最值查询复杂度？",[1246,2338,1245,2339],"同区间和。",[19100],{"id":67,"title":19101,"prompt":19102,"inputFormat":19051,"outputFormat":16219,"samples":19103,"starterPy":19107,"starterCpp":19108,"hint":19109,"referencePy":19110,"referenceCpp":19108},"单点改 + 区间最大","n 个元素，m 次操作：1 p v 表示 a[p]=v；2 l r 表示求 a[l..r] 的最大值。",[19104],{"input":19105,"output":19106},"5 3\n3 1 4 1 5\n2 1 5\n1 2 9\n2 1 5","5\n9","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nseg=[-(10**18)]*(4*n+5)\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=max(seg[u*2],seg[u*2+1])\ndef upd(u,l,r,p,v):\n    if l==r: seg[u]=v; return\n    mid=(l+r)\u002F\u002F2\n    if p\u003C=mid: upd(u*2,l,mid,p,v)\n    else: upd(u*2+1,mid+1,r,p,v)\n    seg[u]=max(seg[u*2],seg[u*2+1])\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    mid=(l+r)\u002F\u002F2; res=-(10**18)\n    if ql\u003C=mid: res=max(res,qry(u*2,l,mid,ql,qr))\n    if qr>mid: res=max(res,qry(u*2+1,mid+1,r,ql,qr))\n    return res\nbuild(1,1,n)\nfor _ in range(m):\n    op,p,v=map(int,input().split())\n    if op==1: upd(1,1,n,p,v)\n    else: print(qry(1,1,n,p,v))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> a,seg;\nvoid build(int u,int l,int r){ if(l==r){ seg[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); seg[u]=max(seg[u*2],seg[u*2+1]); }\nvoid upd(int u,int l,int r,int p,int v){ if(l==r){ seg[u]=v; return; } int m=(l+r)\u002F2; if(p\u003C=m) upd(u*2,l,m,p,v); else upd(u*2+1,m+1,r,p,v); seg[u]=max(seg[u*2],seg[u*2+1]); }\nint qry(int u,int l,int r,int ql,int qr){ if(ql\u003C=l&&r\u003C=qr) return seg[u]; int m=(l+r)\u002F2; int res=-(1\u003C\u003C30); if(ql\u003C=m) res=max(res,qry(u*2,l,m,ql,qr)); if(qr>m) res=max(res,qry(u*2+1,m+1,r,ql,qr)); return res; }\nint main(){ cin>>n; a.assign(n+1,0); seg.assign(4*n+5,-(1\u003C\u003C30)); for(int i=1;i\u003C=n;i++) cin>>a[i]; build(1,1,n); int m; cin>>m; while(m--){ int op,p,v; cin>>op>>p>>v; if(op==1) upd(1,1,n,p,v); else cout\u003C\u003Cqry(1,1,n,p,v)\u003C\u003C\"\\n\"; } }","合并取 max，单点改回溯更新。","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nseg=[-(10**18)]*(4*n+5)\ndef build(u,l,r):\n    if l==r: seg[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); seg[u]=max(seg[u*2],seg[u*2+1])\ndef upd(u,l,r,p,v):\n    if l==r: seg[u]=v; return\n    mid=(l+r)\u002F\u002F2\n    if p\u003C=mid: upd(u*2,l,mid,p,v)\n    else: upd(u*2+1,mid+1,r,p,v)\n    seg[u]=max(seg[u*2],seg[u*2+1])\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return seg[u]\n    mid=(l+r)\u002F\u002F2; res=-(10**18)\n    if ql\u003C=mid: res=max(res,qry(u*2,l,mid,ql,qr))\n    if qr>mid: res=max(res,qry(u*2+1,mid+1,r,ql,qr))\n    return res\nbuild(1,1,n)\nfor _ in range(m):\n    op,p,v=map(int,input().split())\n    if op==1: upd(1,1,n,p,v)\n    else: print(qry(1,1,n,p,v))","区间最值线段树与区间和几乎相同，仅“合并”改为取 max\u002Fmin；单点修改回溯更新最值，查询 O(log n)。","39-A1-4","扫描线（矩形面积并）",{"id":19112,"title":19113,"level":16053,"module":18955,"seq":770,"goal":19115,"keywords":19116,"explanation":19119,"examples":19130,"exercises":19135,"summary":19161,"next":19162,"nextTitle":19163},"掌握扫描线 + 线段树求矩形面积并：把矩形拆成左右竖边，按 x 排序，用线段树维护当前“被覆盖长度”随扫描推进的变化。",[15940,18387,19117,19118],"离散化","覆盖长度",[19120,19125],{"title":19121,"content":19122,"codePy":19123,"codeCpp":19124},"1. 边与扫描","每个矩形拆成两条竖边：左边界 +1，右边界 -1。按 x 排序后从左到右扫描，相邻 x 的差乘当前覆盖长度累加到面积。","edges=[(x1,1,y1,y2),(x2,-1,y1,y2)]\nedges.sort()  # 按 x","\u002F\u002F 左+1 右-1，按 x 排序",{"title":19126,"content":19127,"codePy":19128,"codeCpp":19129},"2. 线段树维护覆盖长度","对 y 坐标离散化，线段树节点存“区间被覆盖次数 cnt”和“该区间有效长度 len”（cnt>0 则整段有效，否则取左右子段之和）。","def pushup(u,l,r):\n    if cnt[u]>0: len_[u]=Y[r+1]-Y[l]\n    elif l==r: len_[u]=0\n    else: len_[u]=len_[u*2]+len_[u*2+1]","\u002F\u002F cnt>0 整段有效，否则取子段和",[19131],{"title":19132,"prompt":19133,"analysis":19134,"codePy":6021,"codeCpp":6022,"run":885},"例 1：两单位矩形","矩形 [0,0]-[2,2] 与 [1,1]-[3,3]，面积并？","总面积 4+4-重叠1=7。",{"choice":19136,"coding":19150},[19137,19141,19145],{"id":38,"stem":18408,"options":19138,"answer":9,"explanation":19140},[15972,19139,18411,18473],"两条竖边","左右边界。",{"id":47,"stem":19142,"options":19143,"answer":9,"explanation":19144},"线段树维护的是？",[5985,19118,4231,4166],"当前有效长度。",{"id":56,"stem":19146,"options":19147,"answer":9,"explanation":19149},"为何要离散化 y？",[6446,19148,3117,4356],"坐标连续需映射","映射到有限区间。",[19151],{"id":67,"title":18387,"prompt":19152,"inputFormat":19153,"outputFormat":19154,"samples":19155,"starterPy":19157,"starterCpp":19158,"hint":19159,"referencePy":19160,"referenceCpp":19158},"给定 n 个轴对齐矩形（左下角 x1,y1，右上角 x2,y2），求它们覆盖的总面积。","第一行 n；接下来 n 行 x1 y1 x2 y2。","一行整数（面积）。",[19156],{"input":18433,"output":885},"import sys\nsys.setrecursionlimit(300000)\nn=int(input()); X1=[];Y1=[];X2=[];Y2=[]; ys=[]\nfor _ in range(n):\n    a,b,c,d=map(int,input().split()); X1.append(a);Y1.append(b);X2.append(c);Y2.append(d); ys+=[b,d]\nys=sorted(set(ys)); M=len(ys)\ndef yid(y): return ys.index(y)\ncnt=[0]*(4*M+5); length=[0]*(4*M+5)\ndef pushup(u,l,r):\n    if cnt[u]>0: length[u]=ys[r+1]-ys[l]\n    elif l==r: length[u]=0\n    else: length[u]=length[u*2]+length[u*2+1]\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: cnt[u]+=v; pushup(u,l,r); return\n    m=(l+r)\u002F\u002F2\n    if ql\u003C=m: upd(u*2,l,m,ql,qr,v)\n    if qr>m: upd(u*2+1,m+1,r,ql,qr,v)\n    pushup(u,l,r)\nans=0; lastx=None\nedges=[]\nfor i in range(n): edges.append((X1[i],1,i)); edges.append((X2[i],-1,i))\nedges.sort()\nfor x,sgn,i in edges:\n    if lastx is not None: ans+=(x-lastx)*length[1]\n    ql=yid(Y1[i]); qr=yid(Y2[i])-1; upd(1,0,M-2,ql,qr,sgn); lastx=x\nprint(ans)\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> ys,cnt,len;\nvoid pushup(int u,int l,int r){ if(cnt[u]>0) len[u]=ys[r+1]-ys[l]; else if(l==r) len[u]=0; else len[u]=len[u*2]+len[u*2+1]; }\nvoid upd(int u,int l,int r,int ql,int qr,int v){ if(ql\u003C=l&&r\u003C=qr){ cnt[u]+=v; pushup(u,l,r); return; } int m=(l+r)\u002F2; if(ql\u003C=m) upd(u*2,l,m,ql,qr,v); if(qr>m) upd(u*2+1,m+1,r,ql,qr,v); pushup(u,l,r); }\nint main(){ cin>>n; vector\u003Cint> X1(n),Y1(n),X2(n),Y2(n); for(int i=0;i\u003Cn;i++){ cin>>X1[i]>>Y1[i]>>X2[i]>>Y2[i]; ys.push_back(Y1[i]); ys.push_back(Y2[i]); } sort(ys.begin(),ys.end()); ys.erase(unique(ys.begin(),ys.end()),ys.end()); int M=ys.size(); cnt.assign(4*M+5,0); len.assign(4*M+5,0); vector\u003Cpair\u003Cint,int>> edges; for(int i=0;i\u003Cn;i++){ edges.push_back({X1[i],i*2}); edges.push_back({X2[i],i*2+1}); } sort(edges.begin(),edges.end()); long long ans=0,lastx=0; bool first=true; for(auto& e:edges){ int x=e.first,i=e.second\u002F2,sgn=(e.second%2==0)?1:-1; if(!first) ans+=(x-lastx)*len[1]; int ql=lower_bound(ys.begin(),ys.end(),Y1[i])-ys.begin(); int qr=lower_bound(ys.begin(),ys.end(),Y2[i])-ys.begin()-1; upd(1,0,M-2,ql,qr,sgn); lastx=x; first=false; } cout\u003C\u003Cans; }","扫描线 + 维护覆盖长度的线段树。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); X1=[];Y1=[];X2=[];Y2=[]; ys=[]\nfor _ in range(n):\n    a,b,c,d=map(int,input().split()); X1.append(a);Y1.append(b);X2.append(c);Y2.append(d); ys+=[b,d]\nys=sorted(set(ys)); M=len(ys)\ndef yid(y): return ys.index(y)\ncnt=[0]*(4*M+5); length=[0]*(4*M+5)\ndef pushup(u,l,r):\n    if cnt[u]>0: length[u]=ys[r+1]-ys[l]\n    elif l==r: length[u]=0\n    else: length[u]=length[u*2]+length[u*2+1]\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: cnt[u]+=v; pushup(u,l,r); return\n    m=(l+r)\u002F\u002F2\n    if ql\u003C=m: upd(u*2,l,m,ql,qr,v)\n    if qr>m: upd(u*2+1,m+1,r,ql,qr,v)\n    pushup(u,l,r)\nans=0; lastx=None\nedges=[]\nfor i in range(n): edges.append((X1[i],1,i)); edges.append((X2[i],-1,i))\nedges.sort()\nfor x,sgn,i in edges:\n    if lastx is not None: ans+=(x-lastx)*length[1]\n    ql=yid(Y1[i]); qr=yid(Y2[i])-1; upd(1,0,M-2,ql,qr,sgn); lastx=x\nprint(ans)","扫描线把矩形拆成左右竖边，按 x 排序后逐边推进；线段树维护 y 方向“被覆盖总长度”，相邻 x 差乘覆盖长度累加到面积，即矩形面积并。","39-A1-5","权值线段树 \u002F 动态开点",{"id":19162,"title":19163,"level":16053,"module":18955,"seq":838,"goal":19165,"keywords":19166,"explanation":19170,"examples":19181,"exercises":19186,"summary":19216,"next":19217,"nextTitle":19218},"掌握权值线段树：以“值域”为下标建线段树，统计数出现的频数、求第 k 小；当值域过大时用动态开点避免建满整棵树。",[19167,19168,19169,17446],"权值线段树","动态开点","第k小",[19171,19176],{"title":19172,"content":19173,"codePy":19174,"codeCpp":19175},"1. 权值线段树","下标代表数值，节点存该值出现的次数（频数）。区间和即频数之和；找第 k 小：左子树频数和 ≥k 则进左，否则进右并减左频数。","def kth(u,l,r,k):\n    if l==r: return l\n    m=(l+r)\u002F\u002F2\n    if seg[u*2]>=k: return kth(u*2,l,m,k)\n    return kth(u*2+1,m+1,r,k-seg[u*2])","\u002F\u002F 左频数和>=k 进左；否则进右减左",{"title":19177,"content":19178,"codePy":19179,"codeCpp":19180},"2. 动态开点","值域 1e9 时不应建满 4n 节点，改为“用到才建”：每个节点只在实际访问时分配左右孩子，节点数约 O(操作数·logV)。","def newNode():\n    t.append([0,0,0]); return len(t)-1","\u002F\u002F 动态分配节点，避免 4V",[19182],{"title":19183,"prompt":19184,"analysis":19185,"codePy":6253,"codeCpp":5851,"run":759},"例 1：第 2 小","插入 3,1,2，第 2 小？","排序 1,2,3，第 2 小=2。",{"choice":19187,"coding":19206},[19188,19195,19200],{"id":38,"stem":19189,"options":19190,"answer":9,"explanation":19194},"权值线段树下标代表？",[19191,19192,16315,19193],"位置","数值","频数","以值域为下标。",{"id":47,"stem":19196,"options":19197,"answer":9,"explanation":19199},"找第 k 小用？",[2509,19198,3117,3259],"左右频数比较","比较左右频数和。",{"id":56,"stem":19201,"options":19202,"answer":9,"explanation":19205},"值域过大时？",[19203,19168,12626,19204],"建满树","离散化必做","用到才建节点。",[19207],{"id":67,"title":19208,"prompt":19209,"inputFormat":1801,"outputFormat":16099,"samples":19210,"starterPy":19212,"starterCpp":19213,"hint":19214,"referencePy":19215,"referenceCpp":19213},"第 k 小（动态开点）","依次插入一些整数，每次插入后输出当前所有数中的第 k 小（k 固定为查询序号，即第 i 次查询输出第 i 小）。",[19211],{"input":6278,"output":16102},"import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split()))\nV=max(a)+2; seg=[0]*(4*V+5)\ndef upd(u,l,r,x,v):\n    seg[u]+=v\n    if l==r: return\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: upd(u*2,l,m,x,v)\n    else: upd(u*2+1,m+1,r,x,v)\ndef kth(u,l,r,k):\n    if l==r: return l\n    m=(l+r)\u002F\u002F2\n    if seg[u*2]>=k: return kth(u*2,l,m,k)\n    return kth(u*2+1,m+1,r,k-seg[u*2])\nfor i,x in enumerate(a):\n    upd(1,1,V,x,1); print(kth(1,1,V,i+1))\n","#include \u003Ciostream>\n#include \u003Cvector>\nusing namespace std;\nint n; vector\u003Cint> a;\nint seg[4000005];\nvoid upd(int u,int l,int r,int x,int v){ seg[u]+=v; if(l==r) return; int m=(l+r)\u002F2; if(x\u003C=m) upd(u*2,l,m,x,v); else upd(u*2+1,m+1,r,x,v); }\nint kth(int u,int l,int r,int k){ if(l==r) return l; int m=(l+r)\u002F2; if(seg[u*2]>=k) return kth(u*2,l,m,k); return kth(u*2+1,m+1,r,k-seg[u*2]); }\nint main(){ cin>>n; a.resize(n); int V=0; for(int i=0;i\u003Cn;i++){ cin>>a[i]; V=max(V,a[i]); } V+=2; for(int i=0;i\u003Cn;i++){ upd(1,1,V,a[i],1); cout\u003C\u003Ckth(1,1,V,i+1)\u003C\u003C\"\\n\"; } }","权值线段树累计频数，递归找第 k 小。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); a=list(map(int,input().split()))\nV=max(a)+2; seg=[0]*(4*V+5)\ndef upd(u,l,r,x,v):\n    seg[u]+=v\n    if l==r: return\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: upd(u*2,l,m,x,v)\n    else: upd(u*2+1,m+1,r,x,v)\ndef kth(u,l,r,k):\n    if l==r: return l\n    m=(l+r)\u002F\u002F2\n    if seg[u*2]>=k: return kth(u*2,l,m,k)\n    return kth(u*2+1,m+1,r,k-seg[u*2])\nfor i,x in enumerate(a):\n    upd(1,1,V,x,1); print(kth(1,1,V,i+1))","权值线段树以值域为下标统计频数，可求第 k 小（左右子树频数和比较）；值域过大时用动态开点（用到才建节点）节省空间。","39-A1-6","线段树合并",{"id":19217,"title":19218,"level":16053,"module":18955,"seq":936,"goal":19220,"keywords":19221,"explanation":19223,"examples":19234,"exercises":19239,"summary":19274,"next":19275,"nextTitle":19276},"掌握线段树合并：把两棵动态开点线段树按位置逐节点合并（同位置值相加），常用于树上每个节点维护一棵值域线段树并自底向上合并子树信息。",[19218,19222,19168,17477],"值域线段树",[19224,19229],{"title":19225,"content":19226,"codePy":19227,"codeCpp":19228},"1. 合并思路","递归合并左右子树：若某侧为空则返回另一侧；否则合并后值相加，递归到叶子返回和。常用于合并两棵值域树。","def merge(a,b,l,r):\n    if not a or not b: return a or b\n    if l==r: t[a]+=t[b]; return a\n    lc[a]=merge(lc[a],lc[b],l,mid)\n    rc[a]=merge(rc[a],rc[b],mid+1,r)\n    t[a]=t[lc[a]]+t[rc[a]]; return a","\u002F\u002F 空侧返回另一；否则值相加",{"title":19230,"content":19231,"codePy":19232,"codeCpp":19233},"2. 树上应用","每个节点初始一棵只含自己权值的树，DFS 后把子节点的树合并到父节点，父节点便持有整棵子树的值域信息。","def dfs(u):\n    rt[u]=newNode(a[u])\n    for v in g[u]: dfs(v); rt[u]=merge(rt[u],rt[v])","\u002F\u002F 自底向上合并子树",[19235],{"title":19236,"prompt":19237,"analysis":19238,"codePy":6544,"codeCpp":6545,"run":890},"例 1：子树权值和","链 1-2-3，权值各 1，根子树权值和？","合并后总和 3。",{"choice":19240,"coding":19260},[19241,19248,19254],{"id":38,"stem":19242,"options":19243,"answer":9,"explanation":19247},"合并时一侧为空？",[567,19244,19245,19246],"返回另一侧","置零","新建","直接复用。",{"id":47,"stem":19249,"options":19250,"answer":9,"explanation":19253},"两值域树合并节点值？",[2606,19251,14599,19252],"相加","替换","频数和相加。",{"id":56,"stem":19255,"options":19256,"answer":9,"explanation":19259},"树上合并顺序？",[1568,19257,11937,19258],"自底向上 DFS","拓扑任意","先合并子树再父。",[19261],{"id":67,"title":19262,"prompt":19263,"inputFormat":19264,"outputFormat":19265,"samples":19266,"starterPy":19270,"starterCpp":19271,"hint":19272,"referencePy":19273,"referenceCpp":19271},"子树值域合并","一棵 n 节点树，每个节点有权值 w（1..V）。对每个节点 u，输出其子树内不同权值的个数。","第一行 n；第二行 n 个 w；接下来 n-1 行 u v。","n 行，每行子树不同权值个数。",[19267],{"input":19268,"output":19269},"3\n1 2 2\n1 2\n1 3","2\n1\n1","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); w=list(map(int,input().split())); w=[0]+w\ng=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nV=max(w)+1; rt=[0]*(n+1); cnt=[[0,0,0]]; t=[0]\ndef newnode(): cnt.append([0,0,0]); t.append(0); return len(cnt)-1\ndef insert(u,l,r,x):\n    if not u: u=newnode()\n    if l==r: t[u]=1; return u\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: cnt[u][1]=insert(cnt[u][1],l,m,x)\n    else: cnt[u][2]=insert(cnt[u][2],m+1,r,x)\n    t[u]=t[cnt[u][1]]+t[cnt[u][2]]; return u\ndef merge(a,b,l,r):\n    if not a or not b: return a or b\n    if l==r: t[a]+=t[b]; return a\n    m=(l+r)\u002F\u002F2\n    cnt[a][1]=merge(cnt[a][1],cnt[b][1],l,m); cnt[a][2]=merge(cnt[a][2],cnt[b][2],m+1,r)\n    t[a]=t[cnt[a][1]]+t[cnt[a][2]]; return a\nans=[0]*(n+1)\ndef dfs(u,p):\n    rt[u]=insert(0,1,V,w[u])\n    for v in g[u]:\n        if v!=p: dfs(v,u); rt[u]=merge(rt[u],rt[v])\n    ans[u]=t[rt[u]]\ndfs(1,0)\nfor i in range(1,n+1): print(ans[i])\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> w,rt;\nstruct Node{ int t,lc,rc; };\nvector\u003CNode> T;\nint newNode(){ T.push_back({0,0,0}); return T.size()-1; }\nint insert(int u,int l,int r,int x){ if(!u) u=newNode(); if(l==r){ T[u].t=1; return u; } int m=(l+r)\u002F2; if(x\u003C=m) T[u].lc=insert(T[u].lc,l,m,x); else T[u].rc=insert(T[u].rc,m+1,r,x); T[u].t=T[T[u].lc].t+T[T[u].rc].t; return u; }\nint merge(int a,int b,int l,int r){ if(!a||!b) return a|b; if(l==r){ T[a].t+=T[b].t; return a; } int m=(l+r)\u002F2; T[a].lc=merge(T[a].lc,T[b].lc,l,m); T[a].rc=merge(T[a].rc,T[b].rc,m+1,r); T[a].t=T[T[a].lc].t+T[T[a].rc].t; return a; }\nvector\u003Cvector\u003Cint>> g; vector\u003Cint> ans;\nvoid dfs(int u,int p){ rt[u]=insert(0,1,100000,w[u]); for(int v:g[u]) if(v!=p){ dfs(v,u); rt[u]=merge(rt[u],rt[v]); } ans[u]=T[rt[u]].t; }\nint main(){ cin>>n; w.assign(n+1,0); rt.assign(n+1,0); g.resize(n+1); ans.assign(n+1,0); for(int i=1;i\u003C=n;i++){ cin>>w[i]; } for(int i=1;i\u003Cn;i++){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } dfs(1,0); for(int i=1;i\u003C=n;i++) cout\u003C\u003Cans[i]\u003C\u003C\"\\n\"; }","每个节点插入权值，DFS 合并子节点树。","import sys\nsys.setrecursionlimit(300000)\nn=int(input()); w=list(map(int,input().split())); w=[0]+w\ng=[[] for _ in range(n+1)]\nfor _ in range(n-1):\n    u,v=map(int,input().split()); g[u].append(v); g[v].append(u)\nV=max(w)+1; rt=[0]*(n+1); cnt=[[0,0,0]]; t=[0]\ndef newnode(): cnt.append([0,0,0]); t.append(0); return len(cnt)-1\ndef insert(u,l,r,x):\n    if not u: u=newnode()\n    if l==r: t[u]=1; return u\n    m=(l+r)\u002F\u002F2\n    if x\u003C=m: cnt[u][1]=insert(cnt[u][1],l,m,x)\n    else: cnt[u][2]=insert(cnt[u][2],m+1,r,x)\n    t[u]=t[cnt[u][1]]+t[cnt[u][2]]; return u\ndef merge(a,b,l,r):\n    if not a or not b: return a or b\n    if l==r: t[a]+=t[b]; return a\n    m=(l+r)\u002F\u002F2\n    cnt[a][1]=merge(cnt[a][1],cnt[b][1],l,m); cnt[a][2]=merge(cnt[a][2],cnt[b][2],m+1,r)\n    t[a]=t[cnt[a][1]]+t[cnt[a][2]]; return a\nans=[0]*(n+1)\ndef dfs(u,p):\n    rt[u]=insert(0,1,V,w[u])\n    for v in g[u]:\n        if v!=p: dfs(v,u); rt[u]=merge(rt[u],rt[v])\n    ans[u]=t[rt[u]]\ndfs(1,0)\nfor i in range(1,n+1): print(ans[i])","线段树合并把两棵同构动态开点树逐节点合并，空侧直接复用、非空间值相加；在树上自底向上合并子树的值域线段树，可高效统计子树信息。","39-A1-7","线段树综合",{"id":19275,"title":19276,"level":16053,"module":18955,"seq":998,"goal":19278,"keywords":19279,"explanation":19282,"examples":19293,"exercises":19298,"summary":19331,"next":16051,"nextTitle":16052},"综合运用线段树：区间加、区间最值\u002F和、懒标记、历史版本等，能根据题目灵活选择节点维护的信息与合并\u002F下传方式。",[19276,19280,19281,12379],"区间操作","信息设计",[19283,19288],{"title":19284,"content":19285,"codePy":19286,"codeCpp":19287},"1. 信息设计","线段树能维护任何“可合并区间信息”：和、最值、GCD、最长连续段等。关键是设计节点存什么、如何 pushup 合并、如何 lazy 下传。","def pushup(u):\n    sum_[u]=sum_[u*2]+sum_[u*2+1]\n    mx[u]=max(mx[u*2],mx[u*2+1])","\u002F\u002F 节点可同时存和、最值",{"title":19289,"content":19290,"codePy":19291,"codeCpp":19292},"2. 复杂 lazy","区间同时有“加 v”和“赋值为 v”两种修改时，用标记优先级处理（赋值清空加标记）；多标记需保证下传顺序正确。","def apply_set(u,v):\n    seg[u]=v*len; lazy_add[u]=0; lazy_set[u]=v\ndef apply_add(u,v):\n    seg[u]+=v*len; lazy_add[u]+=v","\u002F\u002F 赋值优先于加",[19294],{"title":19295,"prompt":19296,"analysis":19297,"codePy":6021,"codeCpp":6022,"run":885},"例 1：区间加后最大","a=[1,2,3]，区间[1,2]加 5，最大值？","变 6,7,3，最大 7。",{"choice":19299,"coding":19320},[19300,19308,19315],{"id":38,"stem":19301,"options":19302,"answer":9,"explanation":19307},"线段树可维护？",[19303,19304,19305,19306],"仅和","任意可合并信息","仅最值","仅计数","只要能合并。",{"id":47,"stem":19309,"options":19310,"answer":9,"explanation":19314},"区间有“加”和“赋值”标记时？",[10330,19311,19312,19313],"赋值优先清加","加优先","不可混用","赋值覆盖。",{"id":56,"stem":19316,"options":19317,"answer":9,"explanation":19319},"复杂信息的关键是？",[1568,19318,2797,3117],"设计节点与合并","信息可合并即可。",[19321],{"id":67,"title":19322,"prompt":19323,"inputFormat":19051,"outputFormat":16219,"samples":19324,"starterPy":19327,"starterCpp":19328,"hint":19329,"referencePy":19330,"referenceCpp":19328},"区间加 + 区间最大","n 个元素，m 次操作：1 l r v 表示 a[l..r] 加 v；2 l r 表示求 a[l..r] 的最大值。",[19325],{"input":19326,"output":885},"3 2\n1 2 3\n1 1 2 5\n2 1 3","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nmx=[0]*(4*n+5); lazy=[0]*(4*n+5)\ndef build(u,l,r):\n    if l==r: mx[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); mx[u]=max(mx[u*2],mx[u*2+1])\ndef apply(u,v,ln): mx[u]+=v; lazy[u]+=v\ndef push(u,l,r):\n    if lazy[u]:\n        mid=(l+r)\u002F\u002F2; apply(u*2,lazy[u],mid-l+1); apply(u*2+1,lazy[u],r-mid); lazy[u]=0\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: apply(u,v,r-l+1); return\n    push(u,l,r); mid=(l+r)\u002F\u002F2\n    if ql\u003C=mid: upd(u*2,l,mid,ql,qr,v)\n    if qr>mid: upd(u*2+1,mid+1,r,ql,qr,v)\n    mx[u]=max(mx[u*2],mx[u*2+1])\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return mx[u]\n    push(u,l,r); mid=(l+r)\u002F\u002F2; res=-(10**18)\n    if ql\u003C=mid: res=max(res,qry(u*2,l,mid,ql,qr))\n    if qr>mid: res=max(res,qry(u*2+1,mid+1,r,ql,qr))\n    return res\nbuild(1,1,n)\nfor _ in range(m):\n    op,l,r,v=map(int,input().split())\n    if op==1: upd(1,1,n,l,r,v)\n    else: print(qry(1,1,n,l,r))\n","#include \u003Ciostream>\n#include \u003Cvector>\n#include \u003Calgorithm>\nusing namespace std;\nint n; vector\u003Cint> a,mx,lazy;\nvoid apply(int u,int v,int len){ mx[u]+=v; lazy[u]+=v; }\nvoid build(int u,int l,int r){ if(l==r){ mx[u]=a[l]; return; } int m=(l+r)\u002F2; build(u*2,l,m); build(u*2+1,m+1,r); mx[u]=max(mx[u*2],mx[u*2+1]); }\nvoid push(int u,int l,int r){ int m=(l+r)\u002F2; if(lazy[u]){ apply(u*2,lazy[u],m-l+1); apply(u*2+1,lazy[u],r-m); lazy[u]=0; } }\nvoid upd(int u,int l,int r,int ql,int qr,int v){ if(ql\u003C=l&&r\u003C=qr){ apply(u,v,r-l+1); return; } push(u,l,r); int m=(l+r)\u002F2; if(ql\u003C=m) upd(u*2,l,m,ql,qr,v); if(qr>m) upd(u*2+1,m+1,r,ql,qr,v); mx[u]=max(mx[u*2],mx[u*2+1]); }\nint qry(int u,int l,int r,int ql,int qr){ if(ql\u003C=l&&r\u003C=qr) return mx[u]; push(u,l,r); int m=(l+r)\u002F2; int res=-(1\u003C\u003C30); if(ql\u003C=m) res=max(res,qry(u*2,l,m,ql,qr)); if(qr>m) res=max(res,qry(u*2+1,m+1,r,ql,qr)); return res; }\nint main(){ cin>>n; a.assign(n+1,0); mx.assign(4*n+5,-(1\u003C\u003C30)); lazy.assign(4*n+5,0); for(int i=1;i\u003C=n;i++) cin>>a[i]; build(1,1,n); int m; cin>>m; while(m--){ int op,l,r,v; cin>>op>>l>>r>>v; if(op==1) upd(1,1,n,l,r,v); else cout\u003C\u003Cqry(1,1,n,l,r)\u003C\u003C\"\\n\"; } }","区间加 lazy + 维护最大值。","import sys\nsys.setrecursionlimit(300000)\nn,m=map(int,input().split()); a=list(map(int,input().split())); a=[0]+a\nmx=[0]*(4*n+5); lazy=[0]*(4*n+5)\ndef build(u,l,r):\n    if l==r: mx[u]=a[l]; return\n    mid=(l+r)\u002F\u002F2; build(u*2,l,mid); build(u*2+1,mid+1,r); mx[u]=max(mx[u*2],mx[u*2+1])\ndef apply(u,v,ln): mx[u]+=v; lazy[u]+=v\ndef push(u,l,r):\n    if lazy[u]:\n        mid=(l+r)\u002F\u002F2; apply(u*2,lazy[u],mid-l+1); apply(u*2+1,lazy[u],r-mid); lazy[u]=0\ndef upd(u,l,r,ql,qr,v):\n    if ql\u003C=l and r\u003C=qr: apply(u,v,r-l+1); return\n    push(u,l,r); mid=(l+r)\u002F\u002F2\n    if ql\u003C=mid: upd(u*2,l,mid,ql,qr,v)\n    if qr>mid: upd(u*2+1,mid+1,r,ql,qr,v)\n    mx[u]=max(mx[u*2],mx[u*2+1])\ndef qry(u,l,r,ql,qr):\n    if ql\u003C=l and r\u003C=qr: return mx[u]\n    push(u,l,r); mid=(l+r)\u002F\u002F2; res=-(10**18)\n    if ql\u003C=mid: res=max(res,qry(u*2,l,mid,ql,qr))\n    if qr>mid: res=max(res,qry(u*2+1,mid+1,r,ql,qr))\n    return res\nbuild(1,1,n)\nfor _ in range(m):\n    op,l,r,v=map(int,input().split())\n    if op==1: upd(1,1,n,l,r,v)\n    else: print(qry(1,1,n,l,r))","线段树的核心是“设计可合并的区间信息 + 正确的 pushup\u002Fpushdown”；区间加、最值、复杂多标记都能统一处理，按题灵活组合即可。",1792811824212]