P1896-DP【蓝】
状压dp的本质在于通过一个用位压缩表示了的复杂状态指标来实现对于极细状态的刻画,从而解决子问题所需描述过细的问题,同时利用位运算的快速性实现快速运算。
这是我写的第一道状压dp,刚开始没思路,看了两眼题解瞬间领悟到上面那句话,即状压dp的意义,然后就尝试复现,思路懂起来很快,但写起来写了半天,遇到很多小坑,比如位运算的优先级、部分longlong等等,总之写了2h,很蛋疼,好在写完了
然后洛谷上AC了,但VJ上TLE了,说明还是不够快,试着直接把S改成内联函数,速度直接翻倍,再试着直接删去S,完全用位运算,哪怕重复计算,结果竟然速度又提升了,要知道删去S那么就会有重复计算了,但这样竟然又提速了,可见位运算快到不可思议。
然而还是没能AC,对于大数据如“9 50”而言仍然慢的不可思议,但是,仅仅是改成了递推式求解,就tmd一下子秒过了...太离谱了,我这个递推式求解明明求解了更多没用的状态,我这个用啥算啥的比全算出来的更慢,而且我的基于记忆化现用现预处理的方法竟然也慢于无脑全部预处理的方法更慢...可见递归本身是有多慢,我以前只知道递归慢,但没想到这么慢,为了使用递归而多耗的算力竟然这么大以至于完全多过了无脑递推会导致的算力浪费...懂了,以后尽量多递推少记搜
Code-1
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <stack>
#include <queue>
#include <map>
#include <unordered_map>
#include <cmath>
using namespace std;
//dp[n][s][j]表示在边界情况为j的前提下在n*n的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^19~=600000,n<9,s<9,dp.size<60000000太大了
//若dp[n][s][j]表示在边界情况为j的前提下在n*N的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^9==512,n<9,s<9,dp.size<100000小不少
//要求dp[N][K][0];
//dp[n][s][0]=sum(dp[n-1][s-correspondingS][any])
//dp[n][s][j]=sum(dp[n-1][s-correspondingS][anyallowedbyj])
//how to commpute what status is allowed by j?
//if( S(k,j)==1 )then k is allowed by j, V.V.
long long dp[10][82][512];
int n1[512],s[512][512],N,K,maxs;
int N1(int k)
{
if(n1[k]!=-1)return n1[k];
int ans=0,kk=k;
while(k!=0)
{
if(k%2==1)
ans++;
k>>=1;
}
return n1[kk]=ans;
}
int S(int a,int b)
{
if(s[a][b]!=-1)return s[a][b];
if( (((a>>1)|(a<<1))&a)||(((b>>1)|(b<<1))&b) )return s[a][b]=s[b][a]=0;
if( ((a|(a>>1)|(a<<1))&b)!=0 ) return s[a][b]=s[b][a]=0;
return s[a][b]=s[b][a]=1;
}
long long dfs(int n,int s,int j)
{
//if(n<0||j<0||j>maxs) { cout<<"error"<<endl; exit(0); }
if(s==0)return 1;
if(n==0&&s>0)return 0;
if(s<0)return 0;
if(dp[n][s][j]!=0)return dp[n][s][j];
long long dsum=0;
for(int k=0;k<=maxs;k++)
if(S(k,j)==1)
dsum+=dfs(n-1,s-N1(k),k);
return dp[n][s][j]=dsum;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>N>>K;
memset(s,-1,sizeof(s));
memset(n1,-1,sizeof(n1));
maxs=(1<<N)-1;
cout<<dfs(N,K,0)<<endl;
return 0;
}
Code-2
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <stack>
#include <queue>
#include <map>
#include <unordered_map>
#include <cmath>
using namespace std;
//dp[n][s][j]表示在边界情况为j的前提下在n*n的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^19~=600000,n<9,s<9,dp.size<60000000太大了
//若dp[n][s][j]表示在边界情况为j的前提下在n*N的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^9==512,n<9,s<9,dp.size<100000小不少
//要求dp[N][K][0];
//dp[n][s][0]=sum(dp[n-1][s-correspondingS][any])
//dp[n][s][j]=sum(dp[n-1][s-correspondingS][anyallowedbyj])
//how to commpute what status is allowed by j?
//if( S(k,j)==1 )then k is allowed by j, V.V.
long long dp[10][82][512];
int n1[512],s[512][512],ss[512],N,K,maxs;
int N1(int k)
{
if(n1[k]!=-1)return n1[k];
int ans=0,kk=k;
while(k!=0)
{
if(k%2==1)
ans++;
k>>=1;
}
return n1[kk]=ans;
}
long long dfs(int n,int s,int j)
{
//if(n<0||j<0||j>maxs) { cout<<"error"<<endl; exit(0); }
if(s==0)return 1;
if(n==0&&s>0)return 0;
if(s<0)return 0;
if(dp[n][s][j]!=0)return dp[n][s][j];
long long dsum=0;
for(int k=0;k<=maxs;k++)
if( (((k>>1)|(k<<1))&k)==0&&(((j>>1)|(j<<1))&j)==0&&((k|(k>>1)|(k<<1))&j)==0 )
dsum+=dfs(n-1,s-N1(k),k);
return dp[n][s][j]=dsum;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>N>>K;
memset(s,-1,sizeof(s));
memset(n1,-1,sizeof(n1));
memset(ss,-1,sizeof(ss));
maxs=(1<<N)-1;
cout<<dfs(N,K,0)<<endl;
return 0;
}
Final-AC-Code
#include <iostream>
#include <cstring>
using namespace std;
//若dp[n][s][j]表示在边界情况为j的前提下在n*n的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^19~=600000,n<=9,s<=81,dp.size<600000000太大了
//若dp[n][s][j]表示在边界情况为j的前提下在n*N的棋盘中摆放s个棋子的摆放方案数
//此时j<=2^9==512,n<=9,s<=81,dp.size<1000000小不少
//要求dp[N][K][0];
//dp[n][s][0]=sum(dp[n-1][s-correspondingS][any])
//dp[n][s][j]=sum(dp[n-1][s-correspondingS][anyallowedbyj])
//how to commpute what status is allowed by j?
//if( S(k,j)==1 )then k is allowed by j, V.V.
long long dp[10][82][512];
int n1[512],N,K,maxs;
bool legal[512],S[512][512];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>N>>K;
maxs=(1<<N)-1;
for(int i=0;i<=maxs;i++)
{
for(int k=i;k!=0;k>>=1)
if(k%2==1)
n1[i]++;
legal[i]=((((i>>1)|(i<<1))&i)==0);
}
for(int i=0;i<=maxs;i++)
for(int j=i;j<=maxs;j++)
S[j][i]=S[i][j]=(legal[i]&&legal[j]&&((i|(i>>1)|(i<<1))&j)==0);
//deal with condtion of s=0
for(int i=0;i<=N;i++)
for(int k=0;k<=maxs;k++)
dp[i][0][k]=1;
for(int i=1;i<=N;i++)
{
for(int s=1;s<=K;s++)
{
for(int j=0;j<=maxs;j++)
{
long long dsum=0;
if(legal[j])
for(int k=0;k<=maxs;k++)
if(S[j][k]&&s-n1[k]>=0)
dsum+=dp[i-1][s-n1[k]][k];
dp[i][s][j]=dsum;
}
}
}
cout<<dp[N][K][0]<<endl;
return 0;
}