总结:
状态压缩是一个很广的概念(包括hash也是状态压缩的一种?),用到dp上来就跟数位dp一样用二进制或n进制表示一个状态,方便表示状态,也方便转移。然后用万恶的位运算来判断是否符合题意,同时可以实现O(1)的转移。一般先预处理出所有单行的合法状态,可以通过dfs(n/二进制下)或直接枚举(二进制下)实现。状压dp要求的位数(棋盘宽度)不能太大,否则状态太多就凉了呀。
(忘了位运算请出门左转:https://baike.baidu.com/item/位运算/6888804?fr=aladdin)
loj#10170. 「一本通 5.4 例 1」骑士
https://loj.ac/problem/10170
原题是互不侵犯king
先暴力枚举出所有 1的左右都没有1 的状态
然后对于每一行 枚举上一行的合法状态 直接继承
其实比普通dp就多了一维状态把
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
int x,c;
node()
{
x=0;c=0;
}
}a[(1<<10)+10];
long long f[11][110][(1<<10)+10];//f[行数][放置数量][当前行状态]
int n,kk,len,bin[11];
void dp()
{
int mx=(1<<n)-1;
for (int x=0;x<=mx;x++)
{
if ((x&(x<<1))==0)
{
a[++len].x=x;
for (int i=1;i<=n;i++) if ((x&bin[i])!=0) a[len].c++;
f[1][a[len].c][x]=1;
}
}
for (int k=2;k<=n;k++)
{
for (int i=1;i<=len;i++)//当前行状态
{
for (int j=1;j<=len;j++)//上一行状态
{
if ((a[i].x&a[j].x)==0&&((a[i].x<<1)&a[j].x)==0&&(a[i].x&(a[j].x<<1))==0&&a[i].c+a[j].c<=kk)
{
for (int u=a[j].c;u<=kk-a[i].c;u++)
f[k][a[i].c+u][a[i].x]+=f[k-1][u][a[j].x];
}
}
}
}
}
int main()
{
memset(f,0,sizeof(f));
bin[1]=1;
for (int i=2;i<=10;i++) bin[i]=bin[i-1]*2;
scanf("%d%d",&n,&kk);
dp();
long long ans=0;
for (int i=1;i<=len;i++) ans+=f[n][kk][a[i].x];
printf("%lld\n",ans);
return 0;
}
loj#10171. 「一本通 5.4 例 2」牧场的安排
https://loj.ac/problem/10171
照样先找出所有可能的状态
只要在枚举状态的时候判断一下有没有在不能种草的地方种了草
注意考虑到底是第几位(做之前先定义好)
记得longlong
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
struct node
{
int x,c;
}a[(1<<12)+10];
LL f[13][(1<<12)+10],ans;
int v[13][13],bin[13],len,n,m;
void dp()
{
int mx=(1<<m)-1; len=0;
for (int x=0;x<=mx;x++)
{
if ((x&(x<<1))==0)
{
a[++len].x=x;
for (int i=1;i<=m;i++) if ((bin[i]&x)!=0) a[len].c++;
bool bk=0;
for (int i=1;i<=m;i++) if ((bin[m-i+1]&x)!=0&&v[1][i]==0) {bk=1;break;}
if (bk==0) f[1][x]=1;
}
}
for (int k=2;k<=n;k++)
{
for (int i=1;i<=len;i++)
{
bool bk=0;
for (int o=1;o<=m;o++)
if ((bin[m-o+1]&a[i].x)!=0&&v[k][o]==0) {bk=1;break;}
if (bk) continue;
for (int j=1;j<=len;j++)
{
bk=0;
for (int o=1;o<=m;o++)
if ((bin[m-o+1]&a[j].x)!=0&&v[k-1][o]==0) {bk=1;break;}
if (bk) continue;
if ((a[i].x&a[j].x)==0)
{
f[k][a[i].x]+=f[k-1][a[j].x];
}
}
}
}
}
int main()
{
bin[1]=1;
for (int i=2;i<=15;i++) bin[i]=bin[i-1]*2;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) scanf("%d",&v[i][j]);
dp();
for (int x=1;x<=len;x++) ans+=f[n][a[x].x];
printf("%lld\n",ans%100000000);
return 0;
}
loj#10172. 「一本通 5.4 练习 1」涂抹果酱
https://loj.ac/problem/10172
三进制状态,0红果酱、1绿果酱、2蓝果酱
用dfs找出所有可能状态
注意要处理一下已经涂了果酱的那一行不能有别的状态但可以继承状态啊
数组没开大又凉了
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
#define mod 1000000
int bin[11],p[110],len,n,m,a[10],ai;
LL f[11000][250];
void dfs(int l,int last,int s)
{
if (l==0) {p[++len]=s;return ;}
for (int i=0;i<=2;i++)
{
if (i==last) continue;
dfs(l-1,i,s*3+i);
}
}
bool check(int x,int y)
{
for (int i=1;i<=m;i++)
{
int xx=x%3,yy=y%3;
if (xx==yy) return 0;
x/=3;y/=3;
}
return 1;
}
void dp()
{
for (int k=2;k<=n;k++)
{
//if (k==ai) continue;
for (int i=1;i<=len;i++)
{
for (int j=1;j<=len;j++)
{
if (check(p[i],p[j])&&f[k][p[i]]!=-1&&f[k-1][p[j]]!=-1) f[k][p[i]]=(f[k][p[i]]+f[k-1][p[j]])%mod;
}
}
}
}
int main()
{
bin[1]=1;
for (int i=2;i<=10;i++) bin[i]=bin[i-1]*3;
scanf("%d%d%d",&n,&m,&ai);
int tt=0;
for (int i=1;i<=m;i++) {scanf("%d",&a[i]);tt=tt*3+a[i]-1;}
dfs(m,-1,0);
for (int i=1;i<=len;i++) f[ai][p[i]]=-1;
if (ai!=1)
{
for (int i=1;i<=len;i++) f[1][p[i]]=1;
f[ai][tt]=0;
}
else f[ai][tt]=1;
dp();
LL ans=0;
for (int i=1;i<=len;i++) if (f[n][p[i]]!=-1) ans=(f[n][p[i]]+ans)%mod;
printf("%lld\n",ans%mod);
return 0;
}
loj#10173. 「一本通 5.4 练习 2」炮兵阵地
不要害怕T掉! 实际上可行状态数是没有那么多的
枚举三行状态 直接继承
不必 i j k 都要check,check这一行就可以了,可以知道如果上一行的状态是不成立的话它的f是没有值的,否则你就相当于拥有的了一个M这么大的常数而T掉!
小心RE/MLE,开滚动数组
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
int x,c;
}p[1100]; int len;
long long f[5][1100][1100]; //滚动数组 f[][这一行状态][上一行状态]
int v[110][11],n,m,bin[11],now,pre;
char ch[11];
bool check(int x,int k)
{
if (k==0) return 1;
for (int i=1;i<=m;i++)
if ((x&bin[i])!=0&&v[k][m-i+1]==0) return 0;
return 1;
}
void dp()
{
int mx=(1<<m)-1;
for (int x=0;x<=mx;x++)
{
if ((x&(x<<1))==0&&(x&(x<<2))==0)
{
p[++len].x=x;
bool bk=0;
for (int i=1;i<=m;i++) if ((x&bin[i])!=0) {p[len].c++;if (v[1][m-i+1]==0) bk=1;}
if (bk==0) f[0][0][x]=p[len].c;
}
}
for (int i=1;i<=len;i++)
{
for (int j=1;j<=len;j++)
{
if ((p[i].x&p[j].x)==0&&check(p[j].x,1)&&check(p[i].x,2))
f[1][p[i].x][p[j].x]=p[i].c+p[j].c;
}
}
now=1,pre=0;
for (int h=3;h<=n;h++)
{
pre=now; now=(now+1)%3;
for (int i=1;i<=len;i++)
{
if (check(p[i].x,h)==0) continue;
for (int j=1;j<=len;j++)
{
//if (check(p[j].x,h-1)==0) continue;
for (int k=1;k<=len;k++)
{
//if (check(p[k].x,h-2)==0) continue;
if ((p[i].x&p[j].x)==0&&(p[j].x&p[k].x)==0&&(p[i].x&p[k].x)==0)
{
f[now][p[i].x][p[j].x]=max(f[pre][p[j].x][p[k].x]+p[i].c,f[now][p[i].x][p[j].x]);
}
}
}
}
}
}
int main()
{
bin[1]=1;
for (int i=2;i<=10;i++) bin[i]=bin[i-1]*2;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%s",ch+1);
for (int j=1;j<=m;j++)
{
if (ch[j]=='H') v[i][j]=0;
else v[i][j]=1;
}
}
dp();
long long ans=0;
for (int i=1;i<=len;i++)
for (int j=1;j<=len;j++)
ans=max(ans,f[now][p[i].x][p[j].x]);
printf("%lld\n",ans);
return 0;
}
loj#10174. 「一本通 5.4 练习 3」动物园
https://loj.ac/problem/10174
每次枚举位置1~5的状态,对于后面的i ~ i+4 前面的i ~ i+3一定是要和前面一样的,那就直接max一下最后是0/1的然后继承
枚举多一个f[0]的状态,从第1行开始做,保证f[1]是由f[0]推出来的,并让f[n]=f[0],使得1和n是相连的
这题反过来做方便一点,即:如果把环展开,1对应的是最低位,n对应的是最高位
万恶的位运算真的好方便
思路&题解: https://blog.youkuaiyun.com/qq_35914587/article/details/80228260
https://blog.youkuaiyun.com/lych_cys/article/details/51553437
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct kids
{
int x,f,l,fer,lov;
kids()
{
x=f=l=fer=lov=0;
}
}kid[51000];
int bin[10],n,c,f[11000][40];//f[第i个笼子][i~i+4存不存在的状态] 1:保留 0:移走
int s[11000][40];
int dp()
{
int mx=(1<<5)-1,ans=0;
for (int i=0;i<=mx;i++)
{
memset(f[0],-63,sizeof(f[0]));
f[0][i]=0; //保证首尾相同
for (int j=1;j<=n;j++)//保证首尾相同
{
for (int k=0;k<=mx;k++)
{
//int last=k; if ((last&bin[5])!=0) last-=bin[5]; last=(last<<1); 我之前用的什么sd取出后四位方式? 直接 &((1<<4)-1) 不就完了吗
//位运算大法好! (赶紧找个地方记一下笔记先) x&1==x%2, x|1==x+1
f[j][k]=max(f[j-1][((k&15)<<1)|1],f[j-1][(k&15)<<1])+s[j][k];
}
}
ans=max(ans,f[n][i]);//保证首尾相同
}
return ans;
}
int main()
{
//freopen("1.in","r",stdin);
bin[1]=1;
for (int i=2;i<=5;i++) bin[i]=bin[i-1]<<1;
scanf("%d%d",&n,&c);
int mx=(1<<5)-1;
for (int i=1;i<=c;i++)
{
scanf("%d%d%d",&kid[i].x,&kid[i].f,&kid[i].l);
for (int j=1;j<=kid[i].f;j++)
{
int x;
scanf("%d",&x); x=(x-kid[i].x+n)%n+1;
kid[i].fer=(kid[i].fer|bin[x]);
}
for (int j=1;j<=kid[i].l;j++)
{
int x;
scanf("%d",&x); x=(x-kid[i].x+n)%n+1;
kid[i].lov=(kid[i].lov|bin[x]);
}
for (int j=0;j<=mx;j++)
{
//~ (按位非):0变1 1变0
if (((~j)&kid[i].fer)!=0||(j&kid[i].lov)!=0) s[kid[i].x][j]++;
}
}
printf("%d\n",dp());
return 0;
}
完结撒花!
我的数学老师骗了我三年,什么人易我易我心更细,明明是人难我难,人易我难