题目链接。
解析
后边的 dp 方式似乎和已有题解有一些差异。
首先考虑从第
- 最后局面该纸片占两格,显然只有
种放法。 - 最后局面该纸片占一格,那么可能的放法有
种,去除其中会覆盖最终局面的放法即可。 - 最后局面不存在该纸片,那么可能的放法为所有未被覆盖的位置的放法之和,这个可以在过程中维护。具体地,初始放法数为
,然后每次放纸片之后减去那些不能放的放法即可。
容易发现,所有在最终局面出现过的纸片都是必须要选择的,假设这些纸片数为
我们又发现, 所有最终局面不存在的纸片是独立的,即某一张纸片是否选择并不会影响其他纸片的方案数。那么问题转化为求在剩余纸片中分别选
我们考虑选
举个例子,加入剩余的纸片对应的方案数为
可能说的还不是很明白,可以参考下面的代码进行理解。
代码
#include<bits/stdc++.h> #define pi pair<int,int> #define fi first #define se second #define mk make_pair #define int long long #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) using namespace std; char buf[1<<21],*p1=buf,*p2=buf; template <typename T> inline void read(T& r) { r=0;bool w=0; char ch=getchar(); while(ch<'0'||ch>'9') w=ch=='-'?1:0,ch=getchar(); while(ch>='0'&&ch<='9') r=r*10+(ch^48), ch=getchar(); r=w?-r:r; } const int N=1005; const int mod=1e9+7; int T,n,m,k,l,r,a[N][N],ans[N]; int cnt,tot,px[4]={0,0,1,-1},py[4]={1,-1,0,0}; bool vis[N][N]; pi pos[N]; inline pi check(int x,int y){//找旁边有没有相同的编号 for(int i=0,xx,yy;i<4;i++){ xx=x+px[i],yy=y+py[i]; if(xx<1||yy<1||xx>n||yy>m) continue; if(a[xx][yy]==a[x][y]) return mk(xx,yy); } return mk(0,0); } inline void del(int x,int y){//更新还有多少位置可以放 for(int i=0,xx,yy;i<4;i++){ xx=x+px[i],yy=y+py[i]; if(xx<1||yy<1||xx>n||yy>m) continue; if(!vis[xx][yy]) cnt--; } } int b[N],c[N],d[N]; signed main(){ read(T); while(T--){ read(n);read(m);read(k);read(l);read(r); cnt=2*(n-1)*(m-1)+n-1+m-1;tot=0; for(int i=1;i<=k;i++) pos[i]=mk(0,0),ans[i]=0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ vis[i][j]=0,read(a[i][j]); if(!pos[a[i][j]].fi) pos[a[i][j]]=mk(i,j); } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i][j]==0){//没有被覆盖的点先除去 tot++;del(i,j); vis[i][j]=1; } } } for(int i=1;i<=k&&tot!=n*m;i++){ if(!pos[i].fi) ans[i]=cnt;//没有出现的 else{ int x=pos[i].fi,y=pos[i].se; pi t=check(x,y); if(t.fi){//出现了两个 int xx=t.fi,yy=t.se; del(x,y);vis[x][y]=1; del(xx,yy);vis[xx][yy]=1; ans[i]=1;tot+=2; } else{//出现了一个 for(int j=0,xx,yy;j<4;j++){ xx=x+px[j],yy=y+py[j]; if(xx<1||yy<1||xx>n||yy>m) continue; if(!vis[xx][yy]) ans[i]++; } del(x,y);tot++; vis[x][y]=1; } } } int ANS=1,sp=0,Ans=0; for(int i=1;i<=k;i++){ if(pos[i].fi) ANS=ANS*ans[i]%mod,l--,r--;//一定要选中的 else b[++sp]=ans[i]; } if(l<=0) Ans=ANS; c[sp+1]=0;//此处开始为 dp,准确说是求一次答案,算一次前缀和 for(int i=1;i<=r;i++){ if(i!=1){ for(int j=1;j<=sp;j++){ c[j]=b[j]*c[j+1]; } } else{ for(int j=1;j<=sp;j++) c[j]=b[j]; } for(int j=sp-1;j>=1;j--) c[j]=(c[j]+c[j+1])%mod; if(i>=l) Ans=(Ans+c[1]*ANS%mod)%mod;//更新答案时需要呈上必须选中的贡献 } printf("%lld\n",Ans); } return 0; }