题目链接:https://codeforces.com/contest/364/problem/E
题意:给一个n*m的01矩阵,问有多少个子矩阵满足其和为K
题解:
考虑solve(x1,y1,x2,y2)solve(x1,y1,x2,y2)solve(x1,y1,x2,y2)表示[x1,y1]到[x2,y2]的答案,显然最后的答案就是solve(1,1,n,m)solve(1,1,n,m)solve(1,1,n,m)
套个前缀和就变成了O(1)O(1)O(1)查询,和为k可以看成面积为k
考虑按照水平划分中线(竖直同理),枚举左右边界,p1[k]p1[k]p1[k]表示中线向上和为k最多能延伸到哪,p2[k]p2[k]p2[k]同理,只不过表示向下,这个显然可以O(k)O(k)O(k)预处理,然后考虑和为k满足什么条件
(p1[k−1]−p1[k])∗(p2[K−k]∗p2[K−k−1])(p1[k-1]-p1[k])*(p2[K-k]*p2[K-k-1])(p1[k−1]−p1[k])∗(p2[K−k]∗p2[K−k−1])
因为限定了左右边界,所以只需要考虑该面积的有多少个
p1[k−1]−p1[k]p1[k-1]-p1[k]p1[k−1]−p1[k]表示面积为kkk的限定了边界的矩形有多少个(因为可能上面有好几排0)
p2同理,k+(K−k)=Kk+(K-k)=Kk+(K−k)=K,所以这样恰好面积就是K了,注意边界
最后为了保证复杂度,需要水平竖直间隔着切
代码:
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9;
int n,m,K;
LL ans=0;
char s[2505][2505];
int sum[2505][2505],p1[20005],p2[20005];
int calc(int x1,int y1,int x2,int y2){
return sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1];
}
void solve(int x1,int y1,int x2,int y2,int horver){
// printf("%d %d %d %d\n",x1,y1,x2,y2);getchar();
if(x1>x2 || y1>y2)return ;
if(x1 == x2 && y1 == y2){
ans += (calc(x1,y1,x2,y2) == K);
return ;
}
if(horver == 1){
int mid=x1+x2>>1;
solve(x1,y1,mid,y2,horver ^ 1);
solve(mid+1,y1,x2,y2,horver ^ 1);
for(int i=y1;i<=y2;i++){
for(int k=0;k<=K;k++){
p1[k]=mid, p2[k]=mid+1;
}
for(int j=y2;j>=i;j--){
for(int k=0;k<=K;k++){
while(p1[k] >= x1 && calc(p1[k],i,mid,j) <= k)-- p1[k];
while(p2[k] <= x2 && calc(mid+1,i,p2[k],j) <= k)++ p2[k];
}
for(int k=1;k<K;k++){
ans += (p1[k-1]-p1[k]) * (p2[K-k]-p2[K-k-1]);
}
if(K > 0){
ans += (mid-p1[0]) * (p2[K]-p2[K-1]);
ans += (p2[0]-mid-1) * (p1[K-1]-p1[K]);
}else if(K == 0){
ans += (mid-p1[0]) * (p2[0] - mid - 1);
}
}
}
}else{
int mid=y1+y2>>1;
solve(x1,y1,x2,mid,horver ^ 1);
solve(x1,mid+1,x2,y2,horver ^ 1);
for(int i=x1;i<=x2;i++){
for(int k=0;k<=K;k++){
p1[k]=mid, p2[k]=mid+1;
}
for(int j=x2;j>=i;j--){
for(int k=0;k<=K;k++){
while(p1[k] >= y1 && calc(i,p1[k],j,mid) <= k)-- p1[k];
while(p2[k] <= y2 && calc(i,mid+1,j,p2[k]) <= k)++ p2[k];
}
for(int k=1;k<K;k++){
ans += (p1[k-1]-p1[k]) * (p2[K-k]-p2[K-k-1]);
}
if(K > 0){
ans += (mid-p1[0]) * (p2[K]-p2[K-1]);
ans += (p2[0]-mid-1) * (p1[K-1]-p1[K]);
}else if(K == 0){
ans += (mid-p1[0]) * (p2[0] - mid - 1);
}
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1] + (s[i][j] == '1');
solve(1,1,n,m,0);
printf("%I64d\n",ans);
return 0;
}

本文解析了CodeForces竞赛中一道题目E的解决方案,该题要求找出一个n*m的01矩阵中所有子矩阵的和等于K的数量。通过使用前缀和优化查询效率,并采用水平和竖直方向交替划分的方法,实现O(k)预处理,最终得出答案。
799

被折叠的 条评论
为什么被折叠?



