BZOJ 2241 SDOI2011 打地鼠 线性筛+二阶差分

首先声明:此题不满足二分条件,一切写二分的题解均为误解 请注意辨明!

题目大意:给定一个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)查询的二阶差分

向左向上进行两次差分 然后每次修改如图

BZOJ 2241 SDOI2011 打地鼠 线性筛+二阶差分_第1张图片

击打后整个区域都为零则可以完成击打

此外这题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;
}


你可能感兴趣的:(差分,bzoj,线性筛,二阶差分,BZOJ2241)