#include
#include
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
char orilights[5] ;
char lights[5] ;
char result[5] ;
int getBit(char x,int i){//获取x的第i个比特 (从低位到高位),x为char类型,刚好8比特
return (x>>i)&1;
}
void setBit(char& x,int i,int v){//设置x的第i个比特设置成v,要改变x的实参
if(v==1) x=x|(1<<i);//1|1==1,1|0=1
else x&=~(1<<i) ;
}
void FlipBit(char& x,int i){
x^=(1<<i);//0/1与1求异或就是取反
}
int main(int argc, char** argv) {
//step1 先存起来灯的初始状态
int s;
for(int i=0;i<5;i++){
for(int j=0;j<6;j++){
cin>>s;
setBit(orilights[i],j,s);
}
}
//5行6列 ,每一行的开关选取状态对应一个整数
//要输出的result开关选取数组result[5] 原来灯的状态数组 orilights[5]
int switchs;
// for(int i=0;i<5;i++){//考虑每一行
//}
for(int n=0;n<64;n++){//第一行的开关选取组合
memcpy(lights,orilights,sizeof(lights));
switchs=n;
for(int j=0;j<5;j++){//假设第j行的开关状态已知
result[j]=switchs;//选好了第0行开关组合 改变原来灯的状态
for(int i=0;i<6;i++){//第i行的开关一个个看过去
if(getBit(switchs,i)==1){//开关组合选择中按下的是1才发生变化
if(i>=1) FlipBit(lights[j],i-1);
if(i<=4) FlipBit(lights[j],i+1);
FlipBit(lights[j],i);
}//同一行的灯的处理,左边 右边 自己
}
//第j行的开关不仅影响第j行,还影响第j+1行,
//对j-1的影响就是把他全部熄灭 ,就是以此为条件决定第j行开关情况的
if(j<5)lights[j+1]^=switchs;//首先可以确定第1行的状态,再次能确定第1行的开关选择
switchs=lights[j];
}
if(lights[4]==0){
for(int i=0;i<5;i++){
for(int j=0;j<6;j++){
printf("%d ",getBit(result[i],j));
}
printf("\n");
}
break;
}
}
return 0;
}
Mondriaan’s Dream
有一个NM(N<=5,M<=1000)的棋盘,现在有12及2*1的小木块无数个,要盖满整个棋盘,有多少种方式?答案只需要mod1,000,000,007即可。
int dp[10005][40];
//状压换的是状态的列举方式,不变的是递推思想,
表示第i列的为state状态的 前i-1列铺木块方式
因为 第i列的state状态 是由前i-1的状态 推过来的
对 第i列(state状态下,考虑第i-1列的木块对它的影响)的状态进行 列举
所有可行的状态都会对应产生一个next状态(i+1列状态列举的预状态 )
每一个next状态得到的方法数就是dp[i+1][nex]=dp[i][state]
当然了前i列的状态不同组合下(第i列状态在这些组合下不一样),也有可能
得到相同的第i+1列的预状态,
所以得到的dp[i+1][nex]=dp[i+1][nex]+ dp[i][state]
至于对第i列在预状态下进行状态列举,就得从从1行到j行
(0,1的组合序列类似dfs求全排列),预状态决定了附近能否放牧快
类似于vis数组。对第j个位置能决定(到达)j+1,j+2个位置,再从j+1,j+2位置开始向下dfs,直到j==n,这一列就状态就结束了,一种状态(01组合)列举完了(一条路走完了),就可以为它推出的下一列nex状态的方法数
dp[i+1][nex] 加上它的贡献值
#include
#include
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
typedef long long ll;//全换ll,一了百了
long long int dp[2007][2007];//再空间允许范围内开大
//后面是状态数,不能根据 5行为一列状态数开40,又输入6行(64种) 8列
const int mod=1000000007;
int n,m;//n<=5,m<=1000,至多5行,至多1000列
//状压换的是状态的列举方式,不变的是递推思想,
void dfs(ll i,ll j,ll state,ll nex){
if(j==n){
dp[i+1][nex]=dp[i+1][nex]+dp[i][state];
//之前前i列的某种组合(其中第i列不是state状态,也能得到第i+1列nex这种状态
dp[i+1][nex]%=mod;
return ;
}
if((state&(1<<j))>0)dfs(i,j+1,state,nex);
if((state&(1<<j))==0){//第j行是否放了木块,
//注意了将一个数右数第j位设置为1,k&1<<(j-1)),可这里j是下标对应j+1位
dfs(i,j+1,state,nex|(1<<j));
//横着放一块,产生一种i+1行预状态,下次从j+1行出发啦
}
if((j+1<n)&&(state&(1<<j))==0&&(state&(1<<(j+1)))==0){
dfs(i,j+2,state,nex);//竖着放一块
}
return;
} //遇到位运算,尽可能多地打括号 ,别用!判断是否非零
//(state&(1<
int main(int argc, char** argv) {
// int n,m;//n<=5,m<=1000,至多5行,至多1000列
//一列一列的铺,因为一列普下来的状态较小,可以状压用二进制表示
//因此循环外层遍历的是列
cin>>n>>m;
memset(dp,0,sizeof(dp));
dp[1][0]=1;//第一列0状态就一种01组合
for(int i=1;i<=m;i++){//列
for(int s=0;s<(1<<n);s++){
if(dp[i][s]){//前i-1列得到的第i列预状态的方法数
dfs(i,0,s,0);//列从i列举(1~m),行从0列举0~n-1
}
}
}
cout<<dp[m+1][0];//前m列摆满了,对第m+1列的预状态就是0
return 0;
}
题目链接
和在棋盘上铺木块很像,要求解的方法数等于到达最终状态的总方法数,用dp数组来累加到达某行某状态的方法数,一旦前面i-1行的若干组合中出现这种到达i行的state状态,就加到dp【i】【state】
if( dp【i前】【state前】)表示的就是之前i-1行种达到state前这种状态的方法数,只有这种组合存在(至少有一种方法能达到这种状态),才能由这种状态推出第i行的各种state状态
#include
#include
#include
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
typedef long long ll;//全换ll,一了百了
//int a[15][15];
ll init[15];
ll legal[1<<12];//记录legal[s]是否为1
//bitset<1<<12>legal;
ll m,n;
ll dp[15][1<<12];//前i行,第i行为j状态时的最大方法数
const ll mod=100000000;
int main(int argc, char** argv) {
cin>>m>>n;
int d;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
cin>>d;
init[i]=(init[i]<<1)+d;//一行压缩成一个二进制数init[i]
}
}
for(int s=0;s<(1<<n);s++){
//首先判断该状态相邻不种草,到具体某一行再去判断状态与草地肥沃是否吻合
//先用legal数组索引合法状态,不用每次都去判断
// if((s&(s<<1)==0)&&(s&(s>>1)==0))legal[s]=1;
if(((s&(s<<1))==0)&&((s&(s>>1))==0))legal[s]=1;
}
dp[0][0]=1;
for(int i=1;i<=m;i++){
for(int s=0;s<(1<<n);s++){
//种草的总方法数就是最后一行达到的所有合法状态的数量之和
//达到//先判断这种状态是否可能存在,一是能否种草,二是种草的左右不能有草
//一种状态,就加在dp[i][state](所有到第i行为止达到state的方法数总合)上
if(legal[s]&&((s&init[i])==s)){//合法匹配是1 1,0 1,0 0
for(int sl=0;sl<(1<<n);sl++)//枚举上一行状态
if(dp[i-1][sl]&&((s&sl)==0))dp[i][s]=(dp[i][s]+dp[i-1][sl])%mod;
// dp[i][s]=(dp[i][s]+dp[i-1][all])%mod;
}
}
}
ll cnt=0;
for(int s=0;s<(1<<n);s++){//最后一行以所有状态结束的总方法
if(dp[m][s])cnt=(cnt+dp[m][s])%mod;
}
cout<<cnt;
return 0;
}
if((s&(s<<1)==0)&&(s&(s>>1)==0))legal[s]=1; ❌
if(((s&(s<<1))==0)&&((s&(s>>1))==0))legal[s]=1; ✔
先判断这种状态是否可能存在,一是能否种草,二是种草的左右不能有草
int getBit(int s,int j){//1110101
return s&(1<<(j-1);
}
bool legal(int i,int s){//在肥沃之地种草,不能出现左右相邻的情况
for(int j=1;j<=n;j++){
if(getBit(s,j)>a[i][n-j]){//从右数第j个数下标是n-j
return false;
}
}
//可以不需要a数组,将初始草地肥沃与否的状态也压缩成一个二进制数,
//与s相与不为0
if(s&(s<<1)!=0||s&(s>>1)!=0)return false;
}
到了某一行的某个状态,就去判断,这样对状态是否合法的判断重复了很多
不同的行会重复判断同样的状态是否合法
状态范围已知,不如先用数组记录合法的状态,到了具体某一行再具体和这一行的init[i]状态比较
记录合法的状态,可以用判断数组 legal【state]=1
也可以记录在数组种,legal【len】=state
还有一种容器 bitset
bitset用法
bitset大概就是类似于bool数组一样的东西
但是它的每个位置只占1bit(特别特别小)
bitset的原理大概是将很多数压成一个,从而节省空间和时间(暴力出奇迹)
一般来说bitset会让你的算法复杂度 /32(具体是什么要看计算机)
使用bitset类型需#include < bitset >
bitset类型在定义时就需要指定所占的空间,例如
1)、判断两个1不能相邻
s&(s<<1)==0&&s&(s>>1)==0
!!!你看看,上面又没打括号
(s&(s<<1))==0&&(s&(s>>1))==0
2)、init[x] 0 1 1
state 0 1 0
满足这种对应就是init[x]&state == state
3)、两个数中1的位置不能相同
x1&x2==0
n个二进制数的状态状压之后范围是 0 ~ (1< 所以列举状态时 状态从0开始遍历,行啊列啊,从1开始列举比较好,因为一开始要初始化, 第1行才有意义
for(int s=0;s<(1<5、状态从0开始遍历,行啊列啊,从1开始列举