7.26dfs周测复盘

7.26dfs周测复盘

DFS周测三题题解复盘

前言

本次周测覆盖了 DFS/BFS 的五大核心模型:

题号题目模型核心特征
1P2089 烤鸡排列型DFS每个位置有固定选择范围
2P1036 选数组合型DFS不考虑顺序,用start去重
3Perket子集型DFS每个物品选/不选,两分支
4填涂颜色Flood Fill连通块标记,内外判断
5迷宫问题BFS预处理连通块编号,O(1)查询

第一部分:P2089 烤鸡

基本信息

项目内容
题目编号、来源P2089 洛谷 / 烤鸡
训练层级B DFS基础
知识版块DFS、排列枚举、回溯、剪枝

解题前・关键信号识别

维度分析
目标、约束、底层结构目标:10种调料,每种选1~3克,使总重量为 n,输出所有方案;约束:n ≤ 10000;底层结构:每个位置有3种选择,形成多分支搜索树。
数据规模3^10 = 59049,DFS枚举完全可行。
候选算法和依据DFS + 回溯;依据:每个位置有固定选择范围,需要枚举所有方案。
复杂度预判时间复杂度 O(3^10),空间复杂度 O(10)。

解题后・外化复盘

维度内容
实现结构 / 核心思路第一步定义dfs(step, sum),step 表示当前处理第几种调料,sum 表示当前总重量;第二步若step == 10,检查sum == n,满足则记录方案;第三步枚举 i 从 1 到 3,path[step] = i,递归dfs(step+1, sum+i)核心思想:每个位置枚举所有可能取值,递归填下一个位置。
错因回溯1. 出口忘记写return,导致继续执行;2. 剪枝不足:只判断sum > n,未考虑剩余调料的最小/最大贡献;
边界和易错点1. 出口必须return;2. n 的范围是 [10, 30],超出直接输出 0;3. 剪枝条件:sum + (10-step) > nsum + (10-step)*3 < n;4. path 数组保存当前方案,递归返回后自动覆盖,无需显式回溯。
下次看到什么信号,我应该想到这个方法看到「每个位置有多个固定选择 + 枚举所有方案」,用排列型DFS。

AC 完整代码

#include<iostream>#include<vector>#include<string>#include<algorithm>usingnamespacestd;intn;intpath[10];vector<vector<int>>plans;voiddfs(intstep,intsum){if(sum>n)return;if(sum+(10-step)>n)return;if(sum+(10-step)*3<n)return;if(step==10){if(sum==n){plans.push_back(vector<int>(path,path+10));return;}}for(inti=1;i<=3;i++){path[step]=i;dfs(step+1,sum+i);}}intmain(){cin>>n;if(n<10||n>30){cout<<0<<endl;return0;}dfs(0,0);cout<<plans.size()<<endl;for(auto&p:plans){for(inti=0;i<10;i++){cout<<p[i]<<" ";}cout<<endl;}return0;}

第二部分:P1036 选数

基本信息

项目内容
题目编号、来源P1036 洛谷 / NOIP2002 普及组
训练层级A DFS
知识版块DFS、组合枚举、素数判断

解题前・关键信号识别

维度分析
目标、约束、底层结构目标:从 n 个数中选 k 个,求和为素数的方案数;约束:n ≤ 20;底层结构:组合枚举(顺序无关),用 start 参数控制枚举起点。
数据规模n ≤ 20,组合数 C(20,10) = 184756,DFS 完全可行。
候选算法和依据DFS + 回溯;依据:选 k 个数求和,顺序无关,属于组合枚举。
复杂度预判时间复杂度 O(C(n,k)),空间复杂度 O(k)。

解题后・外化复盘

维度内容
实现结构 / 核心思路第一步读入 n, k 和数组 a;第二步定义dfs(step, start, sum),step 表示已选了几个数,start 表示当前从哪个下标开始枚举,sum 表示当前总和;第三步若step == k,检查 sum 是否为素数,若是则 ans++;第四步枚举 i 从 start 到 n,递归dfs(step+1, i+1, sum+a[i])核心思想:组合不计顺序,下一层从 i+1 开始枚举,避免重复。
错因回溯1. 递归写成dfs(step+1, start+1, ...)而不是i+1;2. 出口忘记return
边界和易错点1. 组合用 start 参数,不需要 visited;2. 下一层递归传i+1,不是start+1;3. 出口必须return
下次看到什么信号,我应该想到这个方法看到「从 n 个数中选 k 个 + 顺序无关 + 判断条件」,用组合DFS。

AC 完整代码

#include<iostream>usingnamespacestd;intn,k,ans;inta[25];boolisPrime(intx){if(x<2)returnfalse;if(x==2)returntrue;if(x%2==0)returnfalse;for(inti=3;i*i<=x;i+=2){if(x%i==0)returnfalse;}returntrue;}voiddfs(intstep,intstart,intsum){if(step==k){if(isPrime(sum))ans++;return;}for(inti=start;i<n;i++){dfs(step+1,i+1,sum+a[i]);}}intmain(){cin>>n>>k;for(inti=0;i<n;i++){cin>>a[i];}dfs(0,0,0);cout<<ans<<endl;return0;}

第三部分:Perket

基本信息

项目内容
题目编号、来源Perket
训练层级B DFS进阶
知识版块DFS、子集枚举、选/不选模型

解题前・关键信号识别

维度分析
目标、约束、底层结构目标:选择若干种食材,使酸度(乘积)和苦度(和)的差的绝对值最小;约束:每个食材只有选/不选两种状态;底层结构:子集枚举,每个物品两个分支。
数据规模n≤10,2^n完全可行。
候选算法和依据DFS+回溯;依据:每个物品选/不选,枚举所有子集。
复杂度预判时间复杂度O(2^n),空间复杂度O(n)。

解题后・外化复盘

维度内容
实现结构 / 核心思路第一步定义dfs(step, sour, bitter, choose),step表示当前处理第几个食材,sour表示当前酸度乘积,bitter表示当前苦度和,choose表示是否至少选了一个;第二步若step==n,若choose==true则更新答案;第三步两个分支:不选(直接递归)和选(sour*=a[step],bitter+=b[step],choose=true)。核心思想:每个物品只有两种状态,形成二叉搜索树。
错因回溯1.忘记记录是否选择了至少一个食材,导致空集合参与计算;2.错误剪枝:if(abs(sour-bitter)>ans) return;因为后面加入食材可能降低差值;3.酸度初始值设为0,但酸度是乘积,应设为1。
边界和易错点1.酸度初始值为1(乘积的单位元);2.必须记录是否至少选了一个食材;3.不能随意剪枝,因为差值可能先增后减;4.选和不选两个分支都要搜索。
下次看到什么信号,我应该想到这个方法看到「每个物品选/不选 + 求最优」,用子集DFS。

AC 完整代码

#include<iostream>#include<cmath>usingnamespacestd;intn;inta[15],b[15];intans=1e9;voiddfs(intstep,intsour,intbitter,boolchoose){if(step==n){if(choose){ans=min(ans,abs(sour-bitter));}return;}// 分支1:不选dfs(step+1,sour,bitter,choose);// 分支2:选dfs(step+1,sour*a[step],bitter+b[step],true);}intmain(){cin>>n;for(inti=0;i<n;i++){cin>>a[i]>>b[i];}dfs(0,1,0,false);cout<<ans<<endl;return0;}

三题对比总结

对比维度P2089 烤鸡P1036 选数Perket
枚举类型排列型组合型子集型
状态参数(step, sum)(step, start, sum)(step, sour, bitter, choose)
下一层起点固定范围 1~3i+1无(只有选/不选)
是否需要 visited
核心判断sum == nisPrime(sum)min(abs(sour-bitter))
典型信号每个位置固定选择n选k,顺序无关每个物品选/不选

第四部分:填涂颜色(Flood Fill)

基本信息

项目内容
题目编号、来源填涂颜色
训练层级B 图搜索基础
知识版块DFS、连通块、Flood Fill

解题前・关键信号识别

维度分析
目标、约束、底层结构目标:将被其他区域包围的0区域染色;约束:棋盘大小有限;底层结构:棋盘上的连通区域问题。
数据规模n≤30,DFS完全可行。
候选算法和依据Flood Fill(洪水填充);依据:能够连接到边界的0一定不是被包围的,从边界开始标记所有外部0,剩下的0就是内部区域。
复杂度预判时间复杂度O(n²),空间复杂度O(n²)。

解题后・外化复盘

维度内容
实现结构 / 核心思路第一步从所有边界上的0开始DFS(或BFS),标记所有外部0为已访问;第二步遍历整个棋盘,所有未被标记的0即为被包围的内部区域,将其改为颜色2;第三步输出修改后的棋盘。核心思想:正难则反——不直接找内部0,而是标记外部0,剩下的就是内部0。
错因回溯1.直接寻找内部0导致判断复杂;2.忘记标记访问导致重复搜索;3.从非边界位置开始搜索,漏掉边界可达的外部0。
边界和易错点1.必须从边界上的0开始DFS;2.访问过的位置需要标记;3.边界上的0永远属于外部;4.DFS结束后恢复/修改状态。
下次看到什么信号,我应该想到这个方法看到「棋盘 + 区域 + 内外判断 + 连通」,用Flood Fill。

AC 完整代码

#include<iostream>#include<vector>#include<set>#include<cmath>#include<algorithm>usingnamespacestd;intn;intv[35][35];boolvis[35][35];intdx[]={-1,0,1,0};intdy[]={0,1,0,-1};voiddfs(intx,inty){if(x>=n||x<0||y>=n||y<0){return;}if(vis[x][y])return;if(v[x][y]!=0)return;vis[x][y]=true;for(inti=0;i<4;i++){intnx=x+dx[i];intny=y+dy[i];dfs(nx,ny);}}intmain(){cin>>n;for(inti=0;i<n;i++){for(intj=0;j<n;j++){cin>>v[i][j];}}for(inti=0;i<n;i++){dfs(i,0);dfs(i,n-1);}for(intj=0;j<n;j++){dfs(0,j);dfs(n-1,j);}for(inti=0;i<n;i++){for(intj=0;j<n;j++){if(v[i][j]==0&&!vis[i][j]){cout<<2<<" ";}else{cout<<v[i][j]<<" ";}}cout<<endl;}return0;

第五部分:迷宫问题(BFS预处理)

基本信息

项目内容
题目编号、来源迷宫问题
训练层级B BFS优化
知识版块BFS、连通块、预处理

解题前・关键信号识别

维度分析
目标、约束、底层结构目标:多次询问某个位置所在连通区域大小;约束:查询次数可能非常大;底层结构:连通区域的大小是固定的,只需预处理一次。
数据规模n≤1000,询问次数可能达1e5。
候选算法和依据BFS/DFS预处理 + 编号统计;依据:一次搜索处理所有连通区域,后续查询O(1)。
复杂度预判预处理O(n²),每次查询O(1)。

解题后・外化复盘

维度内容
实现结构 / 核心思路第一步遍历所有格子,若当前格子未编号且为可走格子,进行BFS/DFS搜索;第二步搜索过程中为所有可走格子分配相同编号;第三步记录该编号对应的连通块大小;第四步每次查询直接输出cnt[id[x][y]]核心思想:一次预处理所有连通区域,避免每次查询重新搜索。
错因回溯1.每次查询重新DFS,时间复杂度太高`;2.BFS入队时忘记立即标记访问,导致重复入队。
边界和易错点1.x表示行,y表示列;2.BFS队列操作正确;3.新加入节点必须立即标记访问,否则可能重复入队;4.数组大小要足够。
下次看到什么信号,我应该想到这个方法看到「大量询问 + 连通区域」,用搜索预处理 + 编号统计。

AC 完整代码

#include<iostream>#include<vector>#include<queue>#include<cmath>#include<algorithm>usingnamespacestd;intn,m;string v[1005];intid[1005][1005];intcnt[1000005];intdx[]={-1,0,1,0};intdy[]={0,1,0,-1};voidbfs(intsx,intsy,intnum){queue<pair<int,int>>q;q.push({sx,sy});id[sx][sy]=num;intsize=0;while(!q.empty()){auto[x,y]=q.front();q.pop();size++;for(inti=0;i<4;i++){intnx=x+dx[i];intny=y+dy[i];if(nx<0||nx>=n||ny<0||ny>=n)continue;if(id[nx][ny])continue;if(v[x][y]==v[nx][ny])continue;id[nx][ny]=num;q.push({nx,ny});}}cnt[num]=size;}intmain(){cin>>n>>m;for(inti=0;i<n;i++){cin>>v[i];}intnum=0;for(inti=0;i<n;i++){for(intj=0;j<n;j++){if(id[i][j]==0){num++;bfs(i,j,num);}}}while(m--){intx,y;cin>>x>>y;x--;y--;cout<<cnt[id[x][y]]<<endl;}return0;}