[CSP-S模拟测试]:physics(二维前缀和+二分+剪枝)
题目传送门(内部题26)
输入格式
第一行有$3$个整数$n,m,q$。
然后有$n$行,每行有一个长度为$m$的字符串,$+$表示正电粒子,$-$表示负电粒子。
然后有$q$行,每行$2$个整数$x,y$,表示将第$x$行第$y$列的正电粒子修改为负电粒子,保证修改前第$x$行第$y$列的粒子带正电。
输出格式
有$q$行,每行一个整数此次修改后所有正电粒子能形成的最大的电场强度。
样例
样例输入:
5 5 5
+-+++
+++++
+++++
+++++
++++-
1 5
2 2
5 3
2 3
1 1
样例输出:
4
3
3
2
2
数据范围与提示
对于所有数据,$1\leqslant n,m\leqslant {10}^3,1\leqslant q\leqslant {10}^3$。
题解
$40\%$算法:
纯暴力,暴力枚举端点,暴力枚举边长,暴力统计。
时间复杂度:$\Theta(n^5)$。
期望得分:$40$分。
实际得分:$40$分。
$70\%$算法:
如果你会二维前缀和,然后你以为你可以拿到$70$分,然后……依然是$40$分。
但是我们发现,假设当前枚举的端点是$(i,j)$,边长为$k$,那么如果当前的正方形不行,那边长更大的肯定也不行;如果行,边长更小的肯定也行,那么我们要找的答案就在这行与不行的分界点上,而这个分界点你可以通过二分来找。
时间复杂度:$\Theta(n^2\log n)$。
期望得分:$40$分。
实际得分:$40$分。
$70\%pro$算法:
题目中只会把正电子变为负电子,所以最大的电场强度一定是单调不递增的。
进而,如果我们现在改变的这个电荷不在最大的正方形内,也就是它的改变对答案并没有影响,那么我们就可以标记它改变了,然后输出上一次的答案。
如果它在最大正方形里,那么暴力再来一遍好啦。
在二分的时候,如果已经不能出现比现在已经计算出来的答案更大的答案,就直接$continue$掉好啦。
由于强大的剪枝,你就可以用这个算法$A$掉这道题了,但是仍能被极端数据卡掉。
时间复杂度:$\Theta(n^2\log n)$。
期望得分:$70$分。
实际得分:$100$分。
代码时刻
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
char ch[1001];
int Map[1001][1001],sum[1001][1001];
int maxn,ans=1001;
pair<int,int> pre;
int main()
{
scanf("%d%d%d",&n,&m,&q);
maxn=min(n,m);
for(int i=1;i<=n;i++)
{
scanf("%s",ch+1);
for(int j=1;j<=m;j++)
{
if(ch[j]=='+')Map[i][j]=1;
sum[i][j]=Map[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
}
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
Map[x][y]=0;
if(x<pre.first||y<pre.second||x>pre.first+ans-1||y>pre.second+ans-1)
{
printf("%d\n",ans);
continue;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j]=Map[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int lft=1,rht=min(maxn,min(n-i+1,m-j+1)),res=0;
if(rht<=ans)break;
while(lft<=rht)
{
int mid=(lft+rht)>>1;
if(sum[i+mid-1][j+mid-1]-sum[i+mid-1][j-1]-sum[i-1][j+mid-1]+sum[i-1][j-1]==mid*mid){lft=mid+1,res=mid;}
else rht=mid-1;
if(rht<=ans)break;
}
if(res>ans)
{
ans=res;
pre=make_pair(i,j);
}
}
printf("%d\n",ans);
maxn=ans;
}
return 0;
}
rp++