去掉最后一位 x>>1 把右起第一个1变成0 x&(x-1) (x&y)==x?1:0 两个式子应当是等价的,但是洛谷P2074用下面的语句过不了 判断x的第i位是不是1 (((1<<(i-1))&x)>0)?1:0 |
bitset<10> bit(8); |
数据量不超过20的方案统计问题,考虑用状压dp
二维状压dp,一般由行作为动态规划的阶段,枚举状态
洛谷https://www.luogu.org/problem/P1879
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 100000000
int mp[20];
bool sta[(1<<16)];//用来预处理出所有的有效行
int dp[20][(1<<16)];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int t;
scanf("%d",&t);
mp[i]=(mp[i]<<1)+t;
}
}
for(int i=0;i<(1<<m);i++){
if(!(i&(i<<1)))sta[i]=true;
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<m);j++){
if(sta[j]&&((j&mp[i])==j)){//枚举一个合法的行状态 且有效行状态为第i行实际状态的子集
for(int k=0;k<(1<<m);k++){
if((k&j)==0){//枚举上一行的状态 若无相邻 则进行转移
dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
}
}
}
}
}
ll ans=0;
for(int i=0;i<(1<<m);i++){
if(sta[i]){
ans+=dp[n][i];
ans%=mod;
}
}
printf("%lld\n",ans);
return 0;
}
洛谷https://www.luogu.org/problem/P1896
状压dp数组记得用long long虽然n只有9但累加起来值很大。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
bool sta[1<<9];
ll dp[10][1<<9][85];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<(1<<9);i++){
if(!(i&(i<<1)))sta[i]=true;
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<n);j++){
if(sta[j]){
for(int l=0;l<(1<<n);l++){
if(((j&l)==0)&&(((j<<1)&l)==0)&&((j&(l<<1))==0)){
bitset<(10)> bit(j);
int cnt=bit.count();
for(int m=cnt;m<=k;m++){
dp[i][j][m]=dp[i][j][m]+dp[i-1][l][m-cnt];
}
}
}
}
}
}
ll ans=0;
for(int i=0;i<(1<<n);i++){
if(sta[i])
ans+=(long long)dp[n][i][k];
}
printf("%lld\n",ans);
return 0;
}
洛谷https://www.luogu.org/problem/P2704
因为涉及到了前两行,所以要加维才能正确表示本行与上一行的状态
#include<bits/stdc++.h>
using namespace std;
#define ll long long
bool sta[(1<<10)];
char tmp[105][15];
int mp[105][15];
ll dp[2][(1<<10)][(1<<10)];
int state[105];
ll max(ll a,ll b){
if(a>b)return a;
return b;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<(1<<10);i++){
if(((i&(i<<1))==0)&&((i&(i<<2))==0)){
sta[i]=true;
}
}
for(int i=1;i<=n;i++){
scanf("%s",tmp[i]);
for(int j=0;j<m;j++){
if(tmp[i][j]=='P'){
mp[i][j+1]=1;
}
else mp[i][j+1]=0;
}
}
for(int i=1;i<=n;i++){
int cur=0;
for(int j=1;j<=m;j++){
cur=(cur<<1)+mp[i][j];
}
state[i]=cur;
for(int j=0;j<(1<<m);j++){
if(!sta[j])continue;
// if(!((cur&j)==j))continue;//如果枚举的当前行占用山地
if(((cur|j)!=cur))continue;
bitset<10> bit1(j);
int cnt1=bit1.count();
for(int k=0;k<(1<<m);k++){
if(!sta[k])continue;
if(j&k)continue;
//if(!((sta[i-1]&k)==k))continue;
if((k|state[i-1])!=state[i-1])continue;//k为state[i-1]的子集 用上面的语句会错 但是自认为两种是等价的 不知道是什么原因
bitset<10> bit2(k);
int cnt2=bit2.count();
if(i==1){
dp[i%2][j][k]=cnt1;
continue;
}
else if(i==2){
dp[i%2][j][k]=max(dp[i%2][j][k],cnt1+cnt2);
continue;
}
for(int l=0;l<(1<<m);l++){
if(!sta[l])continue;
if((j&l)||(l&k))continue;
//if((l&(~sta[i-2])))continue;
if(! ((sta[i-2]&l) ==l) )continue;
if((l|state[i-2])!=state[i-2])continue;
dp[i%2][j][k]=max(dp[i%2][j][k],dp[(i-1)%2][k][l]+cnt1);
}
}
}
}
ll ans=0;
for(int i=0;i<(1<<m);i++){
for(int j=0;j<(1<<m);j++){
ans=max(ans,dp[n%2][i][j]);
}
}
printf("%lld\n",ans);
return 0;
}
洛谷https://www.luogu.org/problem/P2915
一维状压dp,一般枚举所有可能的state,每个state作为一个阶段,通过点的关系进行转移
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[20][(1<<18)];
int num[20];
int main(){
int n,t;
scanf("%d%d",&n,&t);
for(int i=1;i<=n;i++){
scanf("%d",num+i);
dp[i][(1<<i-1)]=1;
}
for(int j=0;j<(1<<n);j++){//每个状态都是一个阶段
for(int i=1;i<=n;i++){//枚举尾部的编号
if(!((1<<i-1)&j))continue; //如果枚举的状态中不含枚举的尾部编号 直接跳过
for(int k=1;k<=n;k++){
//if(((1<<k-1)&j))continue;//如果枚举的状态中含有枚举的后一位的编号 直接跳过
if(((1<<k-1)|j)==j)continue;
if(abs(num[i]-num[k])>t){
dp[k][(j|(1<<k-1))]+=dp[i][j];
}
}
}
}
ll ans=0;
for(int i=1;i<=n;i++){
ans+=dp[i][(1<<n)-1];
}
printf("%lld\n",ans);
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int mp[17][17];
ll dp[1<<16][17];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int t;
scanf("%d",&mp[i][j]);
}
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
dp[1<<i-1][i]=0;
}
for(int i=0;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if(!(i&(1<<j-1)))continue;//前一个人
for(int k=1;k<=n;k++){
if((i&(1<<k-1)))continue;//当前即将要被传的那个人
dp[i|(1<<k-1)][k]=min(dp[i|(1<<k-1)][k],dp[i][j]+mp[j][k]);
}
}
}
ll ans=0x3f3f3f3f;
for(int i=1;i<=n;i++){
ans=min(ans,dp[(1<<n)-1][i]);
}
printf("%lld\n",ans);
return 0;
}
洛谷https://www.luogu.org/problem/P3052
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll num[20];
ll les[(1<<18)],dp[(1<<18)];
int main(){
int n,w;
scanf("%d%d",&n,&w);
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
}
memset(dp,0x3f,sizeof(dp));
dp[0]=1;
les[0]=w;
for(int i=0;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if((1<<j-1)&i)continue;
if(les[i]>=num[j]){//如果能装下的话
les[(i|(1<<j-1))]=max(les[(i|(1<<j-1))],les[i]-num[j]);
dp[(i|(1<<j-1))]=min(dp[(i|(1<<j-1))],dp[i]);
}
else {
les[(i|(1<<j-1))]=max(les[(i|(1<<j-1))],w-num[j]);
dp[(i|(1<<j-1))]=min(dp[(i|(1<<j-1))],dp[i]+1);
}
}
}
printf("%lld\n",dp[(1<<n)-1]);
return 0;
}
洛谷https://www.luogu.org/problem/P3092
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll c[100005],p[20],sum[100005];
ll dp[1<<16];
ll pre[1<<16];
ll S;
int main(){
int k,n;
scanf("%d%d",&k,&n);
for(int i=1;i<=k;i++){
scanf("%lld",&p[i]);
S+=p[i];
}
for(int i=1;i<=n;i++){
scanf("%lld",&c[i]);
sum[i]=sum[i-1]+c[i];//因为花费都大于1 所以c一定是递增的
}
for(int i=0;i<(1<<k);i++){
for(int j=1;j<=k;j++){
if(((1<<j-1)&i))continue;
int last=dp[i];
ll pos=upper_bound(sum+1,sum+n+1,sum[last]+p[j])-sum-1;
dp[(i|(1<<j-1))]=max(dp[(i|(1<<j-1))],pos);
}
}
for(int i=0;i<(1<<k);i++){
for(int j=0;i>>j;j++){
if((i>>j)&1)pre[i]+=p[j+1];
}
}
ll ans=999999999999;
for(int i=0;i<(1<<k);i++){
if(dp[i]>=n){
ans=min(ans,pre[i]);
}
}
if(ans==999999999999){
printf("-1\n");
}
else {
printf("%lld\n",S-ans);
}
return 0;
}
POJhttp://poj.org/problem?id=2411
每格方块只有三种情况:
1.上半方块,上方一定是0,本身一定是1
2.下半方块,上方一定是1,本身一定是0
3.左右方块,上方可能是0或1,本身一定是0
两个上半方块不能相接,也就是j&k一定为0才能转移
将本行的上半与下半方块都消去后,剩下的都是一块横着的方块,横着的方块一定是偶数个,也就是j|k的结果连续的0一定是偶数才能转移
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
ll dp[12][(1<<11)];
bool isEven[(1<<11)];
int main(){
int n,m;
while(scanf("%d%d",&n,&m),n&&m){
memset(isEven,0,sizeof(isEven));
for(int i=0;i<(1<<m);i++){//预处理所有连续0为偶数的情况
int is=0,flag=0;
for(int j=0;j<m;j++){
if(!(i&(1<<j))){//此位为0
is^=1;
}
else {
//is为1表示奇数 为0表示偶数
flag|=is;is=0;
}
}
flag|=is;//最后一次别忘了
if(!flag)isEven[i]=true;
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<m);j++){//当前行
dp[i][j]=0;
for(int k=0;k<(1<<m);k++){//上一行
if(j&k)continue;//不能上方块与上方块相接
if(!isEven[j|k])continue;
dp[i][j]+=dp[i-1][k];
}
}
}
printf("%lld\n",dp[n][0]);
}
return 0;
}