- 题解
梦熊 J 组 · 松鼠赛· T3心电感应题解
- @ 2025-7-29 16:21:43
问题分析
有 个朋友,每个朋友有 种特征; 小 知道 想的是哪个朋友,但需要通过提问来唯一确定; 每次提问是关于某个特征是否为特定值; 目标是找到最少提问次数,使得这些提问的答案能唯一确定朋友身份; 如果有完全相同的朋友(所有特征都一样),则无法确定,输出 ;
代码解析:
代码的核心流程是:
1.读入数据 → 2. 处理每个朋友 → 3. 排除特殊情况( 或有相同朋友) → 4. 从少到多尝试特征数量 → 5. 生成所有可能的特征组合并检查 → 6. 输出最少需要的特征数量(即提问次数)。
代码整体思路是:
对于每个朋友 ,尝试找到最小的提问次数 ,使得通过这 个特征的提问,能够将朋友 与其他所有朋友区分开来。
详细解释见代码;
主要函数说明:
:
检查对于朋友 ,使用 数组中指定的 个特征,是否能与其他所有朋友区分开; 参数 是当前考虑的朋友索引; 参数 是朋友总数; 参数 是特征总数; 参数 是选择的特征索引数组; 参数 是选择的特征数量; 功能:遍历其他所有朋友,检查是否有朋友在这 个特征上与朋友 完全相同
:
检查对于朋友 是否存在 个特征可以将其与其他朋友区分开; 参数 是当前考虑的朋友索引; 参数 是朋友总数; 参数 是特征总数; 参数 是要检查的特征数量; 功能:通过生成所有可能的 个特征组合,调用 函数检查是否有有效的区分特征组合;
主函数
读取输入数据 对每个朋友进行处理: 特殊情况:如果只有 个朋友,不需要提问,输出 检查是否有完全相同的朋友,若有则输出 否则从 1 到 依次尝试,找到最小的有效提问次数;
算法流程
对于每个朋友 : 检查是否存在与 完全相同的朋友,若有则输出 否则从最少提问次数 开始尝试: 检查是否存在 个特征可以区分朋友 与其他所有朋友; 若存在则记录 ,否则检查是否存在 个特征的组合可以区分; 以此类推,直到找到最小的有效提问次数;
这种方法通过组合枚举的方式寻找最小特征集合,确保能唯一确定朋友身份,符合题目的要求。时间复杂度较高(时间复杂度为 ,但是实际代码会小于此复杂度,所以不知道为什么就过了
代码:
#include<bits/stdc++.h>
using namespace std;
const int a=20,b=20;
long long c[a][b];
// a代表最大朋友数,b代表最大特征数,用二维数组c存储所有朋友的特征值
// e:目标朋友的编号(要区分的朋友)
// f:总朋友数n
// g:总特征数m
// h[]:存储选中的k个特征的编号(比如h=[0,2]表示选中特征0和2)
// i:选中的特征数量k
bool d(int e,int f,int g,int h[],int i){
// 枚举所有其他朋友的特征,检查是否都能被辨别开
for(int j=0;j<f;j++){
if(j==e)continue;//跳过目标朋友自己
bool k=0;// 记录是否能辨别开当前朋友j和目标朋友e
// 检查选中的每个特征
for(int l=0;l<i;l++){
int m=h[l];
// 当前特征的编号(比如m=0表示第0个特征)
// 如果目标朋友e和朋友j在这个特征上的值不同,说明能辨别开
if(c[e][m]!=c[j][m]){
k=1;// 标记为可以辨别开
break;// 不用检查其他特征了
}
}
// 如果有一个朋友j无法区分,这个s数组无效
if(!k)return 0;
}
// 所有朋友都能区分,这个s数组有效
return 1;
}
// o:目标朋友的编号(同d函数的e)
// p:总朋友数n(同d函数的f)
// q:总特征数m(同d函数的g)
// r:要尝试的特征数量k(同d函数的i)
bool n(int o,int p,int q,int r){
if(r==0)return 1;// 特殊情况:选0个特征(仅朋友数为1时)
int s[r];// 记下当前选中的k个特征的编号
// 初始化:选前k个特征编号(比如k=2时,s=[0,1])
for(int t=0;t<r;t++)s[t]=t;
while(1){// 循环生成所有可能的s数组
if(d(o,p,q,s,r))return 1;// 有效则返回true
// 生成下一个s数组
int u=r-1;// 从最后一个特征开始找可以递增特征编号的位置
// 找到能递增特征编号的位置(避免超出特征范围),判断能不能递增的方法:见代码下
while(u>=0&&s[u]==q-(r-u))u--;
if(u<0)return 0;// 所有可能的s数组都试完了,没有有效的s数组
s[u]++;// 递增当前位置的特征编号
// 重置后面的特征为连续编号(保证组合不重复)
for(int t=u+1;t<r;t++)s[t]=s[t-1]+1;
}
}
int main(){
int v,w;cin>>v>>w;
for(int x=0;x<v;x++)
for(int y=0;y<w;y++)
cin>>c[x][y];
// 对每个朋友计算最少提问次数
for(int x=0;x<v;x++){
// 特殊情况1:只有1个朋友,无需提问
if(v==1){
cout<<"0 ";continue;
}
// 检查是否存在和当前朋友x完全相同的朋友(无法区分)
bool z=0;
for(int aa=0;aa<v;aa++){
if(aa==x)continue;// 跳过自己
bool ab=1;//记录是否所有特征都相同
for(int ac=0;ac<w;ac++)
if(c[x][ac]!=c[aa][ac]){// 有一个特征不同就不是相同朋友
ab=0;
break;
}
if(ab){
z=1;
break;// 找到相同朋友,标记后跳出
}
}
if(z){
cout<<"-1 ";// 有相同朋友,输出-1
continue;
}
// 寻找最少提问次数(从1个特征开始试)
int s=-1;//结果
for(int ad=1;ad<=w;ad++)// ad是尝试的特征数量(1到m)
if(n(x,v,w,ad)){// 调用n函数检查ad个特征是否足够
s=ad;// 找到最小数量,记录结果
break;
}
cout<<s<<" ";
}
return 0;
}
判断能不能递增的方法:
先举例子理解如何递增:
假设总共有 4 个特征编号 ,现在要生成 “选 2 个特征” 的所有s数组。正确的顺序应该是:
重要公式:
其中: 是当前s数组中第u个位置的特征编号 是总特征数。 是当前位置后面还剩的特征数量 然后,这个公式用来判断 “当前位置u的特征编号是否已经是最大值,不能再递增了”。
以(总特征 0~3),(选 2 个特征)为例:
对于 (s数组中第 2 个位置): ,所以 即当s[1] == 3时,这个位置不能再递增了(因为最大特征是 3)。 对于(s数组中第 1 个位置): ,所以。 即当 时,这个位置不能再递增了(因为后面还要留 1 个特征的位置,最大只能是 2,后面才能放 3)。
总结:
找到递增的位置” 本质是:
从组合的最后一个特征开始往前找,找到第一个 “还能增大” 的位置(即该位置的特征编号没达到最大值)。 增大这个位置的编号后,后面的位置依次设为 “当前位置 ”,确保组合严格递增且不重复。
这种方式能高效生成所有可能的特征组合,就像按字典序排列组合一样,既不重复也不遗漏。
1 条评论
-
11514zbs LV 7 @ 2025-7-30 9:01:50耐改王
- 1