前言
DP题,我当然不会啦。
题目大意
请你找到m个n位二进制数,对于相邻两个,i+1的前j位的和不小于i的前j位的和。
此外还有对于某个二进制数某位必须是几的若干个限制。
DP
很容易想到设dp[i,s]表示做到第i个二进制数第i个是s,每次枚举前一个,复杂度很大。
转移复杂度太大了,我们来尝试优化。
如果可以不用枚举前一个就好了。
那么不如让前一个对着当前的改变吧!
设dp[i,j,s]表示做到第i个二进制数,已经确定了它的前j位和s的前j位一致,其中你只要保证了这个新的二进制数任意前缀和和s满足,就能满足限制。
这个s就是前一个变化而来的。
变化规则也很简单。
如果你确定当前位为1,s这位也是1,当然没问题。
0与0同理。
而0与1非法。
然后1与0,则为了满足s的前j位与当前二进制数前j位一致,要对s做改变。
你可以找到在这之后第一个有1的位置,让s这一位-1,第j位+1。
当然之后可能已经没有1了,这说明当前二进制数可以为所欲为,然后我们直接把第j位+1即可。
这样dp就简单了。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=20+10,mo=1000000007;
int dp[2][maxn][(1<<21)+10],nxt[(1<<21)+10][maxn];
int bz[maxn][maxn];
int i,j,k,l,r,s,t,n,m,p,ans,now;
int main(){
scanf("%d%d%d",&n,&m,&p);
n--;
fo(i,0,(1<<n)-1){
t=-1;
fd(j,n-1,0){
if ((i&(1<<j))) t=j;
nxt[i][j]=t;
}
}
fo(i,1,m)
fo(j,0,n-1)
bz[i][j]=-1;
fo(i,1,p){
scanf("%d%d%d",&j,&k,&l);
bz[j][k-1]=l;
}
dp[0][n][0]=1;
now=1;
fo(i,1,m){
fo(s,0,(1<<n)-1) dp[now][0][s]=dp[1-now][n][s];
fo(j,0,n)
fo(s,0,(1<<n)-1)
dp[1-now][j][s]=0;
fo(j,0,n-1)
fo(s,0,(1<<n)-1)
if (dp[now][j][s]){
fo(k,0,1){
if (bz[i][j]!=-1&&bz[i][j]!=k) continue;
t=(s&(1<<j));
if (t) t=1;
if (k==0&&t==1) continue;
if (k==t) (dp[now][j+1][s]+=dp[now][j][s])%=mo;
else{
r=nxt[s][j];
if (r==-1) (dp[now][j+1][s+(1<<j)]+=dp[now][j][s])%=mo;
else (dp[now][j+1][s+(1<<j)-(1<<r)]+=dp[now][j][s])%=mo;
}
}
}
now=1-now;
}
fo(s,0,(1<<n)-1) (ans+=dp[1-now][n][s])%=mo;
(ans+=mo)%=mo;
printf("%d\n",ans);
}