今天模拟又考了一个SPFA+状压dp,巧了,我又不会,华丽丽爆零。(实际上我啥都不会)
状压dp即用二进制暴力枚举然后状态转移。用f[i][j]表示第i行在状态j的时候的方案数,其中j用一个二进制数来表示。转移的时候只要判断与当前行和上一行(或是上几行)是否冲突即可,如果不冲突,f[i][j]=∑f[i−1][q]其中q为不冲突的状态。∑1≤i≤cntf[n][i] 就是最后的答案,cnt为总状态数。状态转移前要先用dfs预处理一下方案的是否可行,是否冲突。
状压dp题的一个鲜明特征就是题目中的列数或者行数往往非常小,只有几或者十几,否则压缩不下。
1.luoguP3943星空
利用差分思想,设b[i]=a[i]^a[i-1],则b数组中最多有2k个1。对a的某一个区间进行取反时,b只有头尾被取反。将此问题转化为最短路,即有2k个起点,步长有m种,用spfa跑一遍最短路,使2k个1全部消去即可。为了避免重复计算,每次转移时可以先固定选择最小的位置,然后再枚举另一个位置。
#include<bits/stdc++.h>
#define maxn 40005
#define maxx 1000000009
using namespace std;
int n,k,m,x,y,d[maxn],have[maxn],w[23],val[maxn],f[500010],dis[23][23];
bool inq[maxn];
int read(){
int s=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s;
}
queue<pair<int,int> > q;
int main(){
n=read();k=read();m=read();
for(int i=0;i<=n+1;i++) val[i]=1;
for(int i=1;i<=k;i++) x=read(),val[x]=0;
k=0;
for(int i=1;i<=n+1;i++)
if(val[i]^val[i-1]) w[++k]=i,have[i]=k;
for(int i=1;i<=m;i++) d[i]=read(),d[i]++;
int to;
for(int i=1;i<=k;i++)
for(int j=1;j<=k;j++)
if(i!=j) dis[i][j]=maxx;
for(int i=1;i<=k;i++){
for(int j=0;j<=n+1;j++) inq[j]=false;
q.push(make_pair(w[i],0)); inq[w[i]]=true;
while(!q.empty()){
x=q.front().first;y=q.front().second;q.pop();
for(int j=1;j<=m;j++){
to=x+d[j]-1;
if(to>n+1||inq[to]) continue;
q.push(make_pair(to,y+1));
inq[to]=true;
if(have[to]) dis[i][have[to]]=y+1;
}
for(int j=1;j<=m;j++){
to=x-d[j]+1;
if(to<0||inq[to]) continue;
q.push(make_pair(to,y+1));
inq[to]=true;
if(have[to]) dis[i][have[to]]=y+1;
}
}
}
for(int i=1;i<(1<<k);i++) f[i]=maxx;
for(int i=0;i<(1<<k);i++){
int j=0;
while(i&(1<<j)) j++;
for(int p=j+1;p<k;p++){
if(!(i&(1<<p)))
f[i|(1<<p)|(1<<j)]=min(f[i|(1<<p)|(1<<j)],f[i]+dis[j+1][p+1]);
}
}
// if(f[(1<<k)-1]==maxx) printf("-1\n");
printf("%d",f[(1<<k)-1]);
return 0;
}
2.luoguP1879玉米田
跟上一题比起来这一题就简单多了,直接预处理然后表示状态好了。
#include<bits/stdc++.h>
using namespace std;
int dp[13][1<<12+5],f[15],n,m,a[15][15];
bool g[1<<12+5];
int read(){
int s=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s;
}
int main(){
m=read();n=read();
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
a[i][j]=read(),f[i]=(f[i]<<1)+a[i][j];
for(int i=0;i<(1<<n);i++)
g[i]=((!(i&(i<<1)))&&(!(i&(i>>1))));
dp[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<(1<<n);j++){
if(g[j]&&((j&f[i])==j)){
for(int k=0;k<(1<<n);k++){
if(!(j&k)) dp[i][j]=(dp[i-1][k]+dp[i][j])%100000000;
}
}
}
}
int ans=0;
for(int i=0;i<(1<<n);i++) ans=(ans+dp[m][i])%100000000;
printf("%d\n",ans);
}
3.luoguP2915奶牛混合起来
f[i][j]表示以i结尾,状态为j的方案数。(如,j=12,即1100,表示队中有3,4两头牛)
注意:要判断i是否在队中。
#include<bits/stdc++.h>
using namespace std;
long long n,k,a[18],y;
long long f[18][1<<18],ans;
long long read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1; ch=getchar();}
while(ch<='9'&&ch>='0') {x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int main(){
n=read();y=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++)
f[i][(1<<(i-1))]=1;//仅有i在队中,是混乱的,方案数为1
for(int i=1;i<(1<<n);i++)
for(int k=1;k<=n;k++)
for(int j=1;j<=n;j++)
if(abs(a[k]-a[j])>y&&(i&(1<<(k-1)))&&(i&(1<<(j-1)))==0)
f[j][i|(1<<(j-1))]+=f[k][i];
for(int i=1;i<=n;i++)
ans+=f[i][(1<<n)-1];
printf("%lld",ans);
return 0;
}
4.luoguP2704炮兵阵地
真恶心,,竟然上面两行对其都有影响……而且为了这题,我又不得不去学了下滚动数组……
f[i][j][k]表示当前考虑第k行,状态为j,上一行状态为i。
#include<bits/stdc++.h>
using namespace std;
int n,m,mapp[105],f[1<<10][1<<10][2],sum[1<<10],ans=0;
bool g[1<<10];
char ch;
int read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1; ch=getchar();}
while(ch<='9'&&ch>='0') {x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int getsum(int x){
int tot=0;
while(x){if(x&1) tot++; x=x>>1;}
return tot;
}
int main(){
n=read();m=read();
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
ch=getchar();
while(ch!='P'&&ch!='H') ch=getchar();
if(ch=='H') mapp[i]=mapp[i]<<1|1;
else mapp[i]=mapp[i]<<1;
}
}//地图
for(int i=0;i<(1<<m);i++) sum[i]=getsum(i);//预处理每种状态可以放几个
for(int i=0;i<(1<<m);i++)
for(int j=0;j<(1<<m);j++)
if(!(i&j||i&mapp[0]||j&mapp[1]||(i&(i<<1))||(j&(j<<1))||(i&(i<<2))||(j&(j<<2))))
f[i][j][1]=sum[i]+sum[j];//预处理第一行
for(int i=2;i<n;i++){
for(int j=0;j<(1<<m);j++){
if((j&mapp[i-1])||(j&(j<<1))||(j&(j<<2))) continue;//上一行
for(int k=0;k<(1<<m);k++){
if((k&(k<<1))||(k&(k<<2))||(k&mapp[i])||(k&j)) continue;//本行
for(int v=0;v<(1<<m);v++){
if((v&(v<<1))||(v&(v<<2))||(v&j)||(v&k)||(v&mapp[i-2])) continue;//上上行
f[j][k][i%2]=max(f[j][k][i%2],f[v][j][(i-1)%2]+sum[k]);
}
}
}
}
for(int i=0;i<(1<<m);i++)
for(int j=0;j<(1<<m);j++)
ans=max(ans,f[i][j][(n-1)%2]);//咋都可以,只要在最后一行就行,取最大值。
printf("%d",ans);
return 0;
}
下面还会有的吧。。