A. Mio visits ACGN Exhibition
分析:
-
D P + DP\ + DP + 降维优化
-
f [ i ] [ j ] [ x ] [ y ] f[i][j][x][y] f[i][j][x][y] 表示从 ( 1 , 1 ) (1,1) (1,1) 走到 ( i , j ) (i,j) (i,j),经过 x x x 个 0 0 0 和 y y y 个 1 1 1 的方案数
因为非 0 0 0 即 1 1 1 ( y = i + j − 1 − x ) (y=i+j-1-x) (y=i+j−1−x),故第四维可以直接去掉
然后就可以得到:
f [ i ] [ j ] [ x ] = { f [ i ] [ j − 1 ] [ x ] + f [ i − 1 ] [ j ] [ x ] ( a [ i ] [ j ] = 1 ) f [ i ] [ j − 1 ] [ x − 1 ] + f [ i − 1 ] [ j ] [ x − 1 ] ( a [ i ] [ j ] = 0 , x > 0 ) f[i][j][x]= \begin{cases} f[i][j-1][x]&+&f[i-1][j][x]&(a[i][j]=1)\\ f[i][j-1][x-1]&+&f[i-1][j][x-1]&(a[i][j]=0,x>0) \end{cases} f[i][j][x]={f[i][j−1][x]f[i][j−1][x−1]++f[i−1][j][x]f[i−1][j][x−1](a[i][j]=1)(a[i][j]=0,x>0) -
但是这是三维的,超空间了,还要再优化掉一维的空间,两种方法:
- 滚动数组优化
- 用 p r e pre pre 存取 f [ i − 1 ] f[i-1] f[i−1] 的所有状态
f [ j ] [ x ] = { f [ j − 1 ] [ x ] + p r [ j ] [ x ] ( a [ i ] [ j ] = 1 ) f [ j − 1 ] [ x − 1 ] + p r [ j ] [ x − 1 ] ( a [ i ] [ j ] = 0 , x > 0 ) f[j][x]= \begin{cases} f[j-1][x]&+&pr[j][x]&(a[i][j]=1)\\ f[j-1][x-1]&+&pr[j][x-1]&(a[i][j]=0,x>0) \end{cases} f[j][x]={f[j−1][x]f[j−1][x−1]++pr[j][x]pr[j][x−1](a[i][j]=1)(a[i][j]=0,x>0)
三个坑点:
- 预处理
- i = 1 a n d j = 1 i=1\ and\ j=1 i=1 and j=1 的情况要 c o n t i n u e continue continue
- f [ j ] [ 0 ] = 0 f[j][0]=0 f[j][0]=0 (详见代码)
p r e pre pre 写法:
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N=1005;
int a[N][N], f[N][N];
int pr[N][N];
signed main()
{
int n,m,p,q;
cin>>n>>m>>p>>q;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
if(!a[1][1]) f[1][1]=1;
else f[1][0]=1; // 这就是预处理,我和队友枚举出来的预处理
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<=i+j;k++)
{
pr[j][k]=f[j][k];
}
}
for(int j=1;j<=m;j++)
{
if(i==1 && j==1) continue; //坑点二
for(int k=0;k<=i+j-1;k++)
{
if(!a[i][j])
{
f[j][k+1]=(f[j-1][k]+pr[j][k])%mod;
}
else f[j][k]=(f[j-1][k]+pr[j][k])%mod;
}
if(!a[i][j]) f[j][0]=0; // 坑点三,赛后还想了半年
}
}
int ans=0;
for(int i=p;i<=n+m-q-1;i++)
{
ans+=f[m][i];
ans%=mod;
}
cout<<ans<<endl;
}
滚动数组写法:
#include <bits/stdc++.h>
#define mod 998244353
#define int long long
using namespace std;
const int N=1005;
int a[N][N], f[N][N];
signed main()
{
int n,m,p,q;
cin>>n>>m>>p>>q;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
if(!a[1][1]) f[1][1]=1;
else f[1][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++){
if(i==1 && j==1) continue;
for(int k=i+j-1;k>=0;k--)
{
if(a[i][j]) f[j][k]=(f[j-1][k]+f[j][k])%mod;
else
{
if(k) f[j][k]=(f[j-1][k-1]+f[j][k-1])%mod;
}
}
if(!a[i][j]) f[j][0]=0;
}
}
int ans=0;
for(int i=p;i<=n+m-q-1;i++)
{
ans+=f[m][i];
ans%=mod;
}
cout<<ans<<endl;
}
赛时复盘:
-
由于队友开题太快,我第一题就开了这题,等队友签完三道题
我也用 p r e pre pre 的方法,断断续续把这道题敲了个大概(趁队友放下键盘的间隙,抢过键盘~)
然后就没有然后了… …
我不会预处理!!?
然后就和队友一起开始枚举预处理:
先声明预处理的正解,以防你们被我们(我)带偏 if(!a[1][1]) f[1][1]=1; else f[1][0]=1; No.1 for(int i=0;i<=m;i++) f[i][0]=1; 一调,大了好多,没事,咱继续! No.2 for(int i=1;i<=m;i++) f[i][0]=1; 再调,还是大了,不过比刚才好一些,嗯,不错,有进步,继续!! No.3 f[0][0]=1; 再再调,还是大了,更接近答案了,胜利就在前方了!!! No.4 可能队友实在是看不下去我的沙雕操作了,让我跟他讲一下思路 然后他重新打了一遍,用的是滚动数组 然后也没有然后了 因为,他的答案小了... ... 不过,细心的我还是一眼看到了他的预处理 No.5 没错,队友的预处理是正解 然后,我就把队友的预处理偷了过来 然后,我过了样例!!! "交不?" "交!" 然后,就wa了第一发 就自闭了 No.6 队友的滚动数组,过了样例! "交了?" "交!" 然后,wa了第二发 No.7 10s later... "你这个n+m-q为什么没-1?" -1,再交(直接在牛客上改的) 然后,wa了第三发 队友成功自闭 No.8 我又从队友那里看到了一些不一样的东西,就是那句continue 偷过来,再交 然后,wa了第四发 No.9 队友突然喊了一声"我是傻**" 然后加了一条代码 f[j][0]=0 然后,wa了第五发 No.10 短暂的沉默 "这里还是没-1!"(指n+m-q) 再交,转啊转,"Accept!!!"
经验教训:
- 调 b u g bug bug 不要自己闷着头调,好歹也跟队友讲一下思路,让他们知道你在干啥
- 要耐心去读队友的代码,帮队友 d e b u g debug debug,很多时候真是旁观者清!
- 千万不要在在线编译器上直接改,否则 10 s 10s 10s 后就忘了
- 心态一定不能崩!!!