首先声明:此题不满足二分条件,一切写二分的题解均为误解 请注意辨明!
题目大意:给定一个m*n的洞穴矩阵,每个洞穴里面有若干地鼠,我们需要选定一个r*c的锤子进行击打,每次击打必须保证r*c的范围内所有洞穴均有地鼠,且每次击打只会打掉每个洞穴恰好一只地鼠,求最小击打次数
m,n<=100
考虑一个1*8的洞穴,当我们把锤子设作1*4时可以完成击打,而1*3不能 故不满足单调性,二分不正确
但是一个性质是确定的:假设我们选定一个3*4的锤子可以完成击打,那么我们选定一个3*2的锤子也一定能完成击打
那么反过来,当我们选择一个3*2的锤子无法完成击打时,那么3*4的锤子一定无法完成击打
说白了这题是一个偏是积性函数
于是这题我们选择线性筛进行筛选 若r相同时c1无法完成击打,那么c1*p也无法完成击打
验证的话 首先选定锤子时打的方案是一定的,于是我们贪心,从左上至右下依次击打,每个位置击打的次数等于击打区域左上角洞穴的当前地鼠数量,当击打后任意洞穴地鼠数量不为零
然后验证的时候强行模拟是O( (mn)^2 )树套树可以变成O( mn*logm*logn )
但是这道题有修改而没有查询(每次查询节点时前面必须为零) 故我们选择O(1)修改O(n^2)查询的二阶差分
向左向上进行两次差分 然后每次修改如图
击打后整个区域都为零则可以完成击打
此外这题O(n^4)暴力都能过 而且只比正解慢了四毫秒 就连O(n^6)的暴力加点剪枝都过了0.0 什么情况究竟
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 110 using namespace std; int m,n,cnt,a[M][M],map[M][M],sum,ans=0x7f7f7f7f; const int prime[]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97},tot=25; int not_able[2][M]; bool Judge(int x,int y) { int i,j; if( sum%(x*y) ) return 0; memcpy(map,a,sizeof a); for(i=1;i<=m;i++) { for(j=1;j<=n;j++) { if(map[i][j]<0) return 0; if(i<=m-x+1&&j<=n-y+1) { int num=map[i][j]; map[i][j]-=num; map[i+x][j]+=num; map[i][j+y]+=num; map[i+x][j+y]-=num; } if(map[i][j]>0) return 0; } } ans=min(ans,sum/x/y); return 1; } bool Linear_Shaker(int num,bool flag,int Time) { int i,j; bool re=0; for(i=1;i<=(flag?n:m);i++) { if(not_able[flag][i]!=Time) { if(flag) not_able[flag][i]=(Judge(num,i)?0:Time); else not_able[flag][i]=(Linear_Shaker(i,1,++cnt)?0:Time); } if(not_able[flag][i]!=Time) re=1; if(i^1) for(j=1;j<=tot&&i*prime[j]<=(flag?n:m);j++) { if(not_able[flag][i]==Time||not_able[flag][prime[j]]==Time) not_able[flag][i*prime[j]]=Time; if(i%prime[j]==0) break; } } return re; } int main() { int i,j; cin>>m>>n; for(i=1;i<=m;i++) for(j=1;j<=n;j++) scanf("%d",&a[i][j]),sum+=a[i][j]; for(i=m;i;i--) for(j=n;j;j--) a[i][j]-=a[i-1][j]; for(i=m;i;i--) for(j=n;j;j--) a[i][j]-=a[i][j-1]; Linear_Shaker(0,0,++cnt); cout<<ans<<endl; }