状压DP入门小结
题目:https://loj.ac/p/10170
非常基础的状压dp题,转移不难
同行不相邻:
if(i&(i<<1)) continue;
相邻行不相邻:
if((s[u]&s[v])||(s[u]&(s[v]<<1))||((s[u]<<1)&s[v])) continue;
此题的转移中,枚举起始态较为方便
难点在于复杂度的把控(时限500ms)
非常重要的一点:状态数的压缩和预存
如果不预存所有合法的单行状态,那么转移时初状态和末状态都有2的n次=1024种,导致T掉(91分)
举例:https://loj.ac/s/826935
预处理后,实际合法的单行状态只有144种,程序效率提升约50倍
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define debug cout<<"****************"<<endl
using namespace std;
typedef long long ll;
inline void ju() {
freopen("***.in","r",stdin);
freopen("***.out","w",stdout);
}
inline ll rd() {
ll x=0,f=1; char c=getchar();
while((c<'0'||c>'9')&&(c!='-')) c=getchar();
if(c=='-') f=-1,c=getchar();
while(c>='0'&&c<='9') {
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return x*f;
}
const int N=250;
int n,k,cnt;
int s[N],num[N];
ll f[12][110][N];
void init() {
cnt=0;
for(int i=0;i<(1<<n);++i) {
if(i&(i<<1)) continue;
int t=i;
while(t) {
num[cnt]+=t&1;
t>>=1;
}
s[cnt++]=i;
}
//cout<<cnt<<endl;
//for(int i=0;i<cnt;++i) printf("%d %d\n",s[i],num[i]);
}
void solve() {
ll ans=0;
for(int i=0;i<cnt;++i) f[1][num[i]][i]=1;
for(int i=1;i<n;++i) {
for(int j=0;j<=k;++j)
for(int u=0;u<cnt;++u) {
//printf("%d %d %d:%lld\n",i,j,u,f[i][j][u]);
if(num[u]<=j)
for(int v=0;v<cnt;++v)
if(j+num[v]<=k) {
if((s[u]&s[v])||(s[u]&(s[v]<<1))||((s[u]<<1)&s[v])) continue;
f[i+1][j+num[v]][v]+=f[i][j][u];
//printf("%d %d %d:%lld\n",i+1,j+num[v],v,f[i+1][j+num[v]][v]);
}
}
}
for(int v=0;v<cnt;++v) ans+=f[n][k][v]/*,printf("%d %d %d:%lld\n",n,k,s[v],f[n][k][v])*/;
cout<<ans<<endl;
}
int main(){
n=rd(),k=rd();
init();
solve();
return 0;
}