CF1519B The Cake Is a Lie \color{green}{\texttt{CF1519B The Cake Is a Lie}} CF1519B The Cake Is a Lie
[Problem] \color{blue}{\texttt{[Problem]}} [Problem]
[Solution] \color{blue}{\texttt{[Solution]}} [Solution]
看到题目,应该可以想到一个比较经典的递推套路:记 f i , j , k f_{i,j,k} fi,j,k 表示从 ( 1 , 1 ) (1,1) (1,1) 走到 ( i , j ) (i,j) (i,j),是否可以刚好获得 k k k 点分数。
显然,如果 f i , j , k f_{i,j,k} fi,j,k 的值为真(即可以刚好获得 k k k 点分数),则 f i , j + 1 , k + i f_{i,j+1,k+i} fi,j+1,k+i 和 f i + 1 , j , k + j f_{i+1,j,k+j} fi+1,j,k+j 都应该是真(分别题目中的转移 1 , 2 1,2 1,2)。
但是,每次求 f n , m , k f_{n,m,k} fn,m,k 的时间复杂度是 O ( n × m × k ) \mathcal{O}(n \times m \times k) O(n×m×k) 的,再乘上数据组数 T T T,很有机会会 TLE。
不过,由于对于确定的 n , m , k n,m,k n,m,k 而言, f n , m , k f_{n,m,k} fn,m,k 的值是确定的,所以我们可以提前计算出所有的 f n , m , k f_{n,m,k} fn,m,k,然后就可以 O ( 1 ) \mathcal{O}(1) O(1) 回答每个询问了。
最后的总时间复杂度为 O ( max { n m k } + T ) \mathcal{O}(\max \{nmk \}+T) O(max{nmk}+T)。
[Code] \color{blue}{\texttt{[Code]}} [Code]
int T,n,m,k,Q;
bool f[110][110][10100];
int main(){
memset(f,false,sizeof(f));
f[1][1][0]=true;//初始化
for(int i=1;i<=100;i++)
for(int j=1;j<=100;j++)
for(int k=0;k<=10000;k++)
if (f[i][j][k]){
if (k+i<=10000) f[i][j+1][k+i]=true;
if (k+j<=10000) f[i+1][j][k+j]=true;
}
scanf("%d",&Q);
for(T=1;T<=Q;T++){
scanf("%d%d%d",&n,&m,&k);
if (f[n][m][k]) printf("YES\n");
else printf("NO\n");
}
return 0;
}
[Afterword] \color{blue}{\texttt{[Afterword]}} [Afterword]
其实这个递推的方法对于这一类题目是很有效的。
但是,本题也有另一种思考方法,即用一个递归函数 f ( n , m , k ) f(n,m,k) f(n,m,k) 判断从 ( 1 , 1 ) (1,1) (1,1) 走到 ( n , m ) (n,m) (n,m) 可否刚好获得 k k k 点分数。
要计算 f ( n , m , k ) f(n,m,k) f(n,m,k),你显然会去求 f ( n , m − 1 , k − n ) f(n,m-1,k-n) f(n,m−1,k−n) 和 f ( n − 1 , m , k − m ) f(n-1,m,k-m) f(n−1,m,k−m) 的值。然后你会发现,这个递归函数是可以记忆化的,也可以在 O ( max { n m k } ) \mathcal{O}(\max \{ nmk \}) O(max{nmk}) 的时间内求出所有的答案。而且不需要预处理,记忆化可以保证不会炸。
再把递归改为递推就可以得到本题的填表法型转移方程式。