[关闭]
@XLM 2017-09-26T02:27:18.000000Z 字数 1853 阅读 326

【SDOI2011】打地鼠

题解 优雅的暴力


由于我常用的OJ炸了,我暂时先把Luogu上的题面放上来
LuoguP2484

看完题目回来了吧。
一开始看题的时候觉得这题很简单,但是某ghbdalao写完这题M掉了,我眉头一皱觉得这并不好玩。

那么先把题目简化为OIer友好的格式吧。
简单来说,给出一个矩阵,每次操作可以也只能将一个固定区间内的所有数减1,求最小操作的次数。

首先可以发现一个性质,就是一旦确定了区间的长和宽,我们操作的次序是无关紧要的。因为左上角的点能且仅能被左上角开始的操作覆盖,所以在枚举是否可行的时候,我们实际上只需要循环减去就可以了,代码其实可以像这样

  1. bool judge(int r,int c){
  2. int temp[maxn][maxn];memcpy(temp,val,sizeof(val));//现开一个数组便于判断
  3. for (int i=1;i<=n;i++){
  4. for (int j=1;j<=m;j++){
  5. if (temp[i][j]){//因为我从左上枚举,所以枚举到一个点时我必须打他
  6. if (i+r<=n+1&&j+c<=m+1){
  7. int num=temp[i][j];
  8. for (int row=0;row<r;row++){
  9. for (int col=0;col<c;col++){
  10. temp[i+row][j+col]-=num;//循环减去
  11. if (temp[i+row][j+col]<0) return 0;//倘若不合法就失败
  12. }
  13. }
  14. }
  15. }
  16. }
  17. }
  18. return 1;
  19. }

然后我们就可以枚举长和宽写代码了。
ghbdalao想出这个后写了个暴力判断 60

为什么是60呢?我们看,每一次判断是否可行我们复杂度是O(n^2*m^2),枚举长宽复杂度是O(nm)
乘起来之后复杂度变为O(n^3*m^3),对于n,m<=100的数据自然会过不了的。

过不了怎么办?
那就剪枝,优雅地爆搜
可行性剪枝
题目中明确的给出我必须一次打掉r*c,不妨记录总数,一旦我当前枚举的r*c不可以被sum整除,那么便一定不合法,可以直接跳过。
最优性剪枝
我在枚举过程中是要记录值的,倘若我当前的r*c小于等于我枚举过且更新成功值的r*c,那么一定没必要枚举并判断他。

经过这两个剪枝,在Luogu上便可以AC

AC代码

  1. #include<cstdio>
  2. #include<cmath>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<iostream>
  6. using namespace std;
  7. const int maxn=110;
  8. int val[maxn][maxn];
  9. int n,m,sum,ans;
  10. int r,c;
  11. void read(){
  12. scanf("%d%d",&n,&m);
  13. for (int i=1;i<=n;i++){
  14. for (int j=1;j<=m;j++){
  15. scanf("%d",&val[i][j]);
  16. sum+=val[i][j];
  17. }
  18. }
  19. }
  20. bool legal(int r,int c){
  21. if (r<0||r>n) return 0;
  22. if (c<0||c>n) return 0;
  23. return 1;
  24. }
  25. bool judge(int r,int c){
  26. int temp[maxn][maxn];memcpy(temp,val,sizeof(val));
  27. for (int i=1;i<=n;i++){
  28. for (int j=1;j<=m;j++){
  29. if (temp[i][j]){
  30. if (i+r<=n+1&&j+c<=m+1){
  31. int num=temp[i][j];
  32. for (int row=0;row<r;row++){
  33. for (int col=0;col<c;col++){
  34. temp[i+row][j+col]-=num;
  35. if (temp[i+row][j+col]<0) return 0;
  36. }
  37. }
  38. }
  39. }
  40. }
  41. }
  42. return 1;
  43. }
  44. int main(){
  45. read();
  46. for (int r=n;r>=1;r--){
  47. for (int c=m;c>=1;c--){
  48. if (sum%(r*c)||r*c<=ans) continue;
  49. if (judge(r,c)) ans=r*c;
  50. }
  51. }
  52. // printf("%d %d\n",sum,ans);
  53. printf("%d",sum/ans);
  54. }

几个简单却行之有效的优化

1.我们看到,在判断时枚举实在是太暴力了,因此可以用前缀和优化。
2.在最优性剪枝中,因为我们是倒序枚举的,所以我们可以把可行(即满足sum%r*c==0)的解全部保存,按面积排序在判断
加上这两个优化后ghb8ms水过这道题。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注