B. Count Subrectangles
time limit per test1 second
memory limit per test512 megabytes
inputstandard input
outputstandard output
You are given an array a of length n and array b of length m both consisting of only integers 0 and 1. Consider a matrix c of size n×m formed by following rule: ci,j=ai⋅bj (i.e. ai multiplied by bj). It’s easy to see that c consists of only zeroes and ones too.
How many subrectangles of size (area) k consisting only of ones are there in c?
A subrectangle is an intersection of a consecutive (subsequent) segment of rows and a consecutive (subsequent) segment of columns. I.e. consider four integers x1,x2,y1,y2 (1≤x1≤x2≤n, 1≤y1≤y2≤m) a subrectangle c[x1…x2][y1…y2] is an intersection of the rows x1,x1+1,x1+2,…,x2 and the columns y1,y1+1,y1+2,…,y2.
The size (area) of a subrectangle is the total number of cells in it.
Input
The first line contains three integers n, m and k (1≤n,m≤40000,1≤k≤n⋅m), length of array a, length of array b and required size of subrectangles.
The second line contains n integers a1,a2,…,an (0≤ai≤1), elements of a.
The third line contains m integers b1,b2,…,bm (0≤bi≤1), elements of b.
Output
Output single integer — the number of subrectangles of c with size (area) k consisting only of ones.
Examples
input
3 3 2
1 0 1
1 1 1
output
4
input
3 5 4
1 1 1
1 1 1 1 1
output
14
题意:
输入n,m,k,第二行输入一个n1的列向量a,第二行输入一个1m的行向量b,得到矩阵C = a·b(n x m),求C中全为1且面积为k的矩形有多少个。
思路:
(1≤n,m≤40000,1≤k≤n⋅m), 因为范围比较大,所以不能直接c矩阵,再统计。对于一个全为1的行列构成的矩形,要想面积为k,设k = a * b,则需要有连续a个b列都为1。我们可以把a的连续1的子串和b的连续1的子串分别统计出来。
比如样例1:
a = [1,0,1]T
b = [1,1,1]
a中
连续1个1的个数为2,
b中
连续1个1的个数为3,
连续2个1的个数为2,
连续3个1的个数为1,
要想面积为2,可以a提供1行为1的,b提供2列连续为1的,而a中能提供2个这样的1,b中能提供2个这样的2,所以2*2=4就是答案。

为了降低复杂度。我们可以对k进行分解因数,令k = x * y,a提供x行连续为1的,b提供y列连续为1的。
统计连续为1的长度的技巧类似最长上升子串
比如对于a
memset(dp, 0, sizeof(dp));
for(int i = 1; i<= n; i++) {
if(a[i] == 1) dp[i] = dp[i-1] + 1;
else dp[i] = 0;
}
然后统计每一个长度连续子串出现的次数,并标记某个长度是否出现过
for(int i = 1; i <= n; i++) cnt[dp[i]]++, vis[dp[i]] = 1;
dp[i]就存了到第i位a的连续为1子串的长度。
但是对于类似 1 1 1,3个连续为1,也有2个连续为1的,2个连续为1的已经在dp[2]处记录过一次了,但记录的是1-2的,而2-3也有一段2个连续为1的子串。如果有3个连续为1的子串x个,那也一定包含x个2个连续为1的子串,所以对于每一个cnt[i]其实都少记录了cnt[i+1]次。因此从最大值开始回滚。
for(int i = maxn - 5; i >= 0; i--) cnt[i] += cnt[i + 1];
这样cnt[i]记录的就是长度为i的连续1子串的个数。
AC代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
ll a[maxn], b[maxn];
ll cnta[maxn], cntb[maxn];
bool visa[maxn], visb[maxn];
ll dp[maxn];
void getsub(ll *arr, int n, ll *cnt, bool *vis) {
memset(dp, 0, sizeof(dp));
for(int i = 1; i<= n; i++) {
if(arr[i] == 1) dp[i] = dp[i-1] + 1;
else dp[i] = 0;
}
for(int i = 1; i <= n; i++) cnt[dp[i]]++, vis[dp[i]] = 1;
for(int i = maxn - 5; i >= 0; i--) cnt[i] += cnt[i + 1];
}
int main() {
int n, m, k;
while(scanf("%d%d%d", &n, &m, &k) == 3) {
memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
memset(cnta, 0, sizeof(cnta)); memset(cntb, 0, sizeof(cntb));
memset(visa, 0, sizeof(visa)); memset(visb, 0, sizeof(visb));
vector<ll> fc;
for(int i = 1; i * i <= k; i++)
if(!(k % i)) {
if(i * i != k) {
fc.push_back(i);
fc.push_back(k / i);
} else {
fc.push_back(i);
}
}
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int i = 1; i <= m; i++) scanf("%lld", &b[i]);
getsub(a, n, cnta, visa);
getsub(b, m, cntb, visb);
ll ans = 0;
for(int i = 0; i < fc.size(); i++) {
if(fc[i] > maxn -2 || k / fc[i] > maxn - 2) continue; //这个地方是个坑,容易RE
if(visa[fc[i]] && visb[k/fc[i]])
ans += cnta[fc[i]] * cntb[k/fc[i]];
}
printf("%lld\n", ans);
}
return 0;
}
赛后总结
这一场总体而言不难,但被这个B在一些细节上卡住了,写掉了i*i,T了3发,RE了4发。其余题目有空再更,因为对这个题目印象深刻所以先放上去了。
以后补题和更题主要在个人博客 baolar.com //虽然也就是写给自己看