P1034 五轮启幽扉 官方题解
Gioush OJ · P1034 五轮启幽扉
五轮启幽扉
【题意简述】
一个密钥包含五个模 意义下的数字。一次试拨可以修改一个转轮,也可以将两个相邻转轮沿相同方向转动相同格数。给出 个试拨后的状态,求可能的原始密钥数量。
【Hint】
【提示】
五个转轮一共只有 种状态。枚举原始密钥后,只需判断它与每条记录之间的模差是否对应一次合法试拨。
【数据点 1∼51\sim 51∼5】
特殊性质 A 保证全部记录两两不同,并且只有同一个转轮上的数字发生变化。
若原始密钥还在其他转轮上与记录不同,那么每次试拨都必须同时修改这个转轮和发生变化的转轮。此时两个转轮上的模差应当始终相同,与记录中只有一位变化矛盾。因此,原始密钥的其余四位已经被记录唯一确定。
变化转轮的原始数字可以是 中任意一个没有在记录中出现的数字。若该数字已经出现,则某条记录会与原始密钥相同,不再对应一次试拨。因此答案为 。
【数据点 6∼106\sim 106∼10】
特殊性质 B 中,全部记录只在一个固定的相邻转轮对上变化,并且两位的变化量始终相同。这些状态位于模 意义下的一条长度为 的轨道上。
原始密钥也必须位于这条轨道上,并且不能等于已经给出的状态。因为记录两两不同,所以答案仍为 。
【数据点 11∼1511\sim 1511∼15】
这一档不再给出满足的是特殊性质 A 还是特殊性质 B。
比较第一条记录与其余记录。若所有非零模差都集中在同一位,则使用单轮轨道;否则寻找唯一的相邻轮对,并检查两个位置的模差是否始终相同,再使用双轮轨道。
一般数据可能同时包含由不同单轮或不同相邻轮对得到的记录,因此无法再把候选限制在同一条轨道上。不过,上述模差判定仍然可以直接用于检查一个给定候选。
【正解】
枚举五个转轮上的数字,得到一个候选原始密钥。对于第 条记录,定义五个模差
其中 为候选密钥的第 位, 为记录的第 位。
令非零模差的数量为 :
- 时,候选密钥与记录相同,没有进行试拨,不合法;
- 时,恰好修改一个转轮,合法;
- 时,两个非零模差必须出现在相邻位置,并且数值相同;
- 时,不可能由一次试拨得到。
一个候选通过全部 条记录的检查时才计入答案。
【正确性证明】
若上述检查通过,则对于每条记录,候选密钥与记录之间恰有一个非零模差,或恰有两个位于相邻位置且相等的非零模差。前者对应一次单轮试拨,后者对应一次相邻双轮同步试拨,因此候选能够产生全部记录。
反过来,若一个原始密钥能够通过一次试拨产生某条记录,那么这次试拨只可能修改一个转轮,或修改两个相邻转轮且变化量相同。它与该记录之间的模差必然满足上述判定。枚举覆盖了全部 个密钥,因此算法不会遗漏,也不会重复计算。
特别地,当 时,单轮试拨共有 种反向候选,相邻双轮试拨共有 种反向候选,答案为 。
【复杂度分析】
共有 个候选,每个候选检查 条记录。时间复杂度为 ,空间复杂度为 。
【参考代码】
/*- @Author: Ehundategh- @Date: 2023-10-23 11:49:08- @FilePath: \Code\CSP-S\lock.cpp- @Description: You Steal,I kill */#include <cstdio>#include <cstring>#define MAXN 10using namespace std;int n,Ans=0;int Condition[MAXN][7];bool Try(int a,int b,int c,int d,int e){ for(int i=1;i<=n;i++){ int Da=0,Db=0,Dc=0,Dd=0,De=0,Times=0; Da=(a-Condition[i][1]+10)%10; if(Da) Times++; Db=(b-Condition[i][2]+10)%10; if(Db) Times++; Dc=(c-Condition[i][3]+10)%10; if(Dc) Times++; Dd=(d-Condition[i][4]+10)%10; if(Dd) Times++; De=(e-Condition[i][5]+10)%10; if(De) Times++; if(!Times) return false; if(Times==1) continue; if(Times==2){ if((Da==Db&&Da!=0)||(Db==Dc&&Db!=0)||(Dc==Dd&&Dc!=0)||(Dd==De&&Dd!=0)) continue; else return false; } if(Times>2) return false; } return true;}void Solve(){ scanf("%d",&n); Ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=5;j++){ scanf("%d",&Condition[i][j]); } } if(n==1) printf("81\n"); else{ for(int i=0;i<=9;i++){ for(int j=0;j<=9;j++){ for(int k=0;k<=9;k++){ for(int r=0;r<=9;r++){ for(int o=0;o<=9;o++){ if(Try(i,j,k,r,o)){ Ans++; } } } } } } printf("%d\n",Ans); }}int main(){ int c,T; scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}