题目分析:
如果题目放在序列上,也就是询问一个长度为 \(n\) 的序列有多少个子段满足其和为 \(k\),那么考虑应该怎么做。
显然就是使用分治,即左边的答案+右边的答案+跨过中间的答案。
跨过中点的答案就可以使用一个类似卷积的方式去求,因为只需要求 \(k\) 这一项所以就可以做到 \(O(k)\) 了。
放在二维上显然也可以使用类似的思路去思考,也就是用 K-D Tree 建树式的方法,每次按横坐标或纵坐标划分。
因为我们要求的是一个矩阵所以我们可以考虑枚举一下矩阵的左右端点,然后其实发现如果是去预处理矩阵和等于 \(k\) 的子矩阵是很难办的,所以就差分一下,求一下矩阵和小于 \(k\) 的子矩阵,就可以设 \(up_i\) 表示矩阵和小于 \(i\) 的子矩阵的最上(左)面对应的坐标,\(dw_i\) 就是最下(右)面对应的坐标,因为随着 \(r\) 的递增 \(up\) 和 \(dw\) 是满足单调性的,所以就可以直接扫一遍求出来。
那么最后的统计答案显然就是 \(\sum_{i=0}^k (up_i - up_{i+1}) \times (dw_{k - i + 1} - dw_{k - i})\)
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e3+5;
int sum[N][N],up[N],dw[N],res,n,m,k;
char s[N];
int get(int xl,int xr,int yl,int yr){
return sum[xr][yr] - sum[xl][yr] - sum[xr][yl] + sum[xl][yl];
}
void solve(int xl,int xr,int yl,int yr,int opt){
if(xl == xr || yl == yr) return;
if(xr == xl+1 && yr == yl + 1){
res += (get(xl,xr,yl,yr) == k);
return;
}
if(opt == 0){
int mid = (yl + yr)>>1;
for(int l=xl; l<=xr; l++){
up[0] = dw[0] = mid;
for(int i=1; i<=k+1; i++) up[i] = yl,dw[i] = yr;
for(int r = l + 1; r <= xr; r++){
for(int i=1; i<=k+1; i++){
while(get(l,r,up[i],mid) >= i) up[i]++;
while(get(l,r,mid,dw[i]) >= i) dw[i]--;
}
for(int i=0; i<=k; i++) res += (up[i] - up[i+1]) * (dw[k-i+1] - dw[k-i]);
}
}
solve(xl,xr,yl,mid,1);solve(xl,xr,mid,yr,1);
}
else{
int mid = (xl + xr)>>1;
for(int l=yl; l<=yr; l++){
up[0] = dw[0] = mid;
for(int i=1; i<=k+1; i++) up[i] = xl,dw[i] = xr;
for(int r = l + 1; r <= yr; r++){
for(int i=1; i<=k+1; i++){
while(get(up[i],mid,l,r) >= i) up[i]++;
while(get(mid,dw[i],l,r) >= i) dw[i]--;
}
for(int i=0; i<=k; i++) res += (up[i] - up[i+1]) * (dw[k-i+1] - dw[k-i]);
}
}
solve(xl,mid,yl,yr,0);solve(mid,xr,yl,yr,0);
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1; i<=n; i++){
scanf("%s",s+1);
for(int j=1; j<=m; j++){
sum[i][j] = sum[i][j-1] + sum[i-1][j] - sum[i-1][j-1] + (s[j] == '1');
}
}
solve(0,n,0,m,1);
printf("%lld\n",res);
return 0;
}