专栏文章
11.18 第4关 O((2^n)*n^2)题解
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mir3aft7
- 此快照首次捕获于
- 2025/12/04 15:03 3 个月前
- 此快照最后确认于
- 2025/12/04 15:03 3 个月前
考虑到问题有一个重要特点:操作与顺序无关:如先摁(0,0)再摁(2,2)与先摁(2,2)再摁(0,0)等价。故排列问题简化为组合问题。最naive的想法就是枚举所有种情况,但显然有很多多余情况无需枚举。
既然已经是组合问题,那么一个很好的性质就是摁灯的顺序(即排列)可任意,不妨考虑从第一排开始摁。
更进一步地,可以发现第一排的摁法一旦确定,第二排的摁法同样也确定了,因为第一排摁完之后还没亮的只能通过摁第二排来摁(这里也是很好地利用了边界的性质)。那么以此类推,3,4,5排的摁法都确定了,最后只要验证一下第5排是否全亮就行了。如果全亮,更新答案即可。
总之,以上方法的本质就是在等价转化问题后进行剪枝。
另外,在枚举第一排摁法时,为了避免多层循环,代码运用了状态压缩的小trick,它将一种摁法跟一个二进制数进行了一一映射(如:10010代表摁了第1和第4个灯)。(感觉有些类似机器学习做分类问题时用的one-hot预处理)
对了,总时间复杂度。
CPP#include<bits/stdc++.h>
using namespace std;
int c[7][7],ori[7][7],cnt;//cnt是步骤数计数器
void swch(int i,int j){//一次摁灯操作;^是异或操作,1^1=0,0^1=1
c[i-1][j]^=1;
c[i+1][j]^=1;
c[i][j-1]^=1;
c[i][j+1]^=1;
c[i][j]^=1;
}
void ini(){//初始化,每次在c数组上操作
cnt=0;
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
c[i][j]=ori[i][j];
}
}
}
int main(){
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
ori[i][j]=getchar()-'0';//getchar就是读入一个字符
}
getchar();//吞掉换行符
}
int mn=INT_MAX;
for(int i=0;i<(1<<5);i++){//状态压缩,解释见上文,(1<<i)的含义是2的i次方(将二进制1左移i位)
ini();
for(int j=0;j<5;j++){
if((1<<j)&i){//&是二进制按位与,这里的含义就是摁法i的第j位有1就会返回1,否则返回0
cnt++;
swch(1,j+1);
}
}
for(int j=2;j<=5;j++){
for(int k=1;k<=5;k++){
if(!c[j-1][k]){//如果上一行的灯没亮,摁它下面这个灯
swch(j,k);
cnt++;
}
}
}
//下面在验证最后一行是否全是1
bool flag=1;
for(int j=1;j<=5;j++){
if(!c[5][j]){
flag=0;
break;
}
}
if(flag) mn=min(mn,cnt);
}
if(mn==INT_MAX) puts("-1");
else cout<<mn;
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...