今天模拟又考了一个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