P6557 Yesterday Once More 题解
__Silvefish__ · · 题解
P6557 Yesterday Once More 题解
原题链接
题意简述
给定一个
首先需要输出放置长条的方案数。然后给定
对于每次修改还需要输出修改过后的方格表的放置方案数。
注意边界情况的特判。这是处理横向修改的方法。处理纵向修改时,只需要将原方格表旋转
深挖数据范围
这样,我们似乎就得到了一个
代码
最后,Talk is cheap, show me the code.
总用时:4.37s
峰值时间:300ms
峰值内存:292.07MB
#include <bits/stdc++.h>
#define int long long
#ifdef zhr_debug
#define debug printf
#else
void debug(const char* s,...){}
#endif
using namespace std;
const int mod=998244353;
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
void freopen(string file){freopen((file+".in").c_str(),"r",stdin);freopen((file+".out").c_str(),"w",stdout);}
int n,m,qq;
int a[25][25];
struct query
{
int op;
int x,y;
int v;
};
query q[1020];
int ans[1020];
int dpl[17][17][33000][2];
int dpr[17][17][33000][2];
void solve()//进行插头 DP
{
memset(dpl,0,sizeof(dpl));
dpl[0][m][0][0]=1;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
{
auto now=dpl[i][j],pre=(j==1 ? dpl[i-1][m] : dpl[i][j-1]);
for(int s=0;s<(1<<m);s++) for(int t:{0,1})
{
if(!pre[s][t]) continue;
if(t && (s>>j-1&1)) continue;//(i,j) 上方和左方同时需要延伸,矛盾
if(!a[i][j])
{
if(!(t || (s>>j-1&1))) add(now[s][t],pre[s][t]);//当前格为障碍,只有上方和左方同时不要延伸才合法
continue;
}
if(t)
{
if(j<m && !(s>>j&1) && a[i][j+1]) add(now[s][t],pre[s][t]);//横向插头向 (i,j+1) 延伸
add(now[s][0],pre[s][t]);//横向插头不继续延伸
}
else if(s>>j-1&1)
{
if(i<n && !t && a[i+1][j]) add(now[s][t],pre[s][t]);//纵向插头向 (i+1,j) 延伸
add(now[s^(1<<j-1)][t],pre[s][t]);//纵向插头不继续延伸
}
else
{
add(now[s][t],pre[s][t]);//这一格什么也不填
if(i<n && a[i+1][j]) add(now[s|(1<<j-1)][t],pre[s][t]);//创建一个向 (i+1,j) 延伸的插头
if(j<m && !(s>>j&1) && a[i][j+1]) add(now[s][1],pre[s][t]);//创建一个向 (i,j+1) 延伸的插头
}
}
}
memset(dpr,0,sizeof(dpr));
dpr[n+1][1][0][0]=1;
for(int i=n;i>=1;i--) for(int j=m;j>=1;j--)
{
auto now=dpr[i][j],pre=(j==m ? dpr[i+1][1] : dpr[i][j+1]);
for(int s=0;s<(1<<m);s++) for(int t:{0,1})
{
if(!pre[s][t]) continue;
if(t && (s>>j-1&1)) continue;
if(!a[i][j])
{
if(!(t || (s>>j-1&1))) add(now[s][t],pre[s][t]);
continue;
}
if(t)
{
if(j>1 && !(s>>j-2&1) && a[i][j-1]) add(now[s][t],pre[s][t]);
add(now[s][0],pre[s][t]);
}
else if(s>>j-1&1)
{
if(i>1 && !t && a[i-1][j]) add(now[s][t],pre[s][t]);
add(now[s^(1<<j-1)][t],pre[s][t]);
}
else
{
add(now[s][t],pre[s][t]);
if(i>1 && a[i-1][j]) add(now[s|(1<<j-1)][t],pre[s][t]);
if(j>1 && !(s>>j-2&1) && a[i][j-1]) add(now[s][1],pre[s][t]);
}
}
}
}
int query(int x,int y,int v)
{
static int ret;ret=0;
static int yl,yr;yl=y;yr=y+v;
auto L=(yl==1 ? dpl[x-1][m] : dpl[x][yl-1]),R=(yr==m ? dpr[x+1][1] : dpr[x][yr+1]);
for(int s=((1<<m)-1)^((1<<yr)-(1<<yl-1));;s=s-1&(((1<<m)-1)^((1<<yr)-(1<<yl-1))))//枚举可能延伸的插头集合
{
add(ret,L[s][0]*R[s][0]%mod);
if(!s) break;
}
return ret;
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
cin>>qq;
for(int i=1;i<=qq;i++)//将询问离线,因为要横竖各做一遍
{
cin>>q[i].op;
if(q[i].op==2) cin>>q[i].x;
if(q[i].op==3) cin>>q[i].x;
if(q[i].op==4) cin>>q[i].x>>q[i].y>>q[i].v;
if(q[i].op==5) cin>>q[i].x>>q[i].y>>q[i].v;
if(q[i].op==1) cin>>q[i].x>>q[i].y,q[i].op=4,q[i].v=0;//类型 1 的询问可以转化为类型 4
}
solve();
ans[0]=dpr[1][1][0][0];
for(int i=1;i<=qq;i++)
{
if(q[i].op==2) ans[i]=query(q[i].x,1,m-1);
if(q[i].op==4) ans[i]=query(q[i].x,q[i].y,q[i].v);
}
for(int i=1;i<=max(n,m);i++) for(int j=1;j<i;j++) swap(a[i][j],a[j][i]);
swap(n,m);solve();//交换横竖再做一遍
for(int i=1;i<=qq;i++)
{
if(q[i].op==3) ans[i]=query(q[i].x,1,m-1);
if(q[i].op==5) ans[i]=query(q[i].y,q[i].x,q[i].v);
}
for(int i=0;i<=qq;i++) printf("%lld\n",ans[i]);
}