【洛谷5307】[COCI2019] Mobitel(动态规划)
- 给定一个\(n\times m\)的矩阵,从\((1,1)\)走到\((n,m)\),每次只能向右或向下。
- 每个格子中有一个正整数,求有多少条路径满足其上所有数乘积大于等于\(v\)。
- \(n,m\le300,v\le10^6\)
暴力动态规划
非常显然的暴力\(DP\),设\(f_{i,j,k}\)表示走到\((i,j)\),其中所有数乘积为\(k\)的方案数(特殊地,若\(k\ge v\)统一记作\(v\))。
转移就只需考虑向下走和向右走两种。
第三维状态的优化
前两维表示当前位置显然不太好优化。
我们考虑第三维,\(k\ge v\Leftrightarrow k>v-1\Leftrightarrow\lfloor\frac{v-1}k\rfloor=0\)。
而我们又知道整除的运算满足\(\lfloor\frac{\lfloor\frac xy\rfloor}z\rfloor=\lfloor\frac x{yz}\rfloor\),所以可以直接把\(\lfloor\frac{v-1}k\rfloor\)记作状态,每想给\(k\)乘上一个数,只要给\(\lfloor\frac{v-1}k\rfloor\)整除以这个数即可。
根据除法分块那套理论,\(\lfloor\frac{v-1}k\rfloor\)的种数只有\(2\sqrt v\)个,足以通过此题。
代码:\(O(n^2\sqrt v)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300
#define V 1000000
#define S 2000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,v,a[N+5][N+5],f[2][N+5][S+5],p[V+5],w[S+5];
int main()
{
RI i,j,k;for(scanf("%d%d%d",&n,&m,&v),i=1;i<=n;++i) for(j=1;j<=m;++j) scanf("%d",&a[i][j]);
RI t=0;for(i=1;i^v;++i) !p[(v-1)/i]&&(w[p[(v-1)/i]=++t]=(v-1)/i);//记录下所有可能的(v-1) div k
for(f[1][1][p[(v-1)/a[1][1]]]=i=1;i<=n;++i)
{
for(j=1;j<=m;++j) for(k=0;k<=t;++k) f[i&1^1][j][k]=0;//滚存,需事先清空下一行
for(j=1;j<=m;++j) for(k=0;k<=t;++k)
i^n&&Inc(f[i&1^1][j][p[w[k]/a[i+1][j]]],f[i&1][j][k]),//向下走
j^m&&Inc(f[i&1][j+1][p[w[k]/a[i][j+1]]],f[i&1][j][k]);//向右走
}
return printf("%d\n",f[n&1][m][0]),0;//k≥n等价于(v-1) div k=0
}
待到再迷茫时回头望,所有脚印会发出光芒