牛客网暑期ACM多校训练营(第七场) - J - Sudoku Subrectangles
题意:
已知一个n*m充满字符的矩阵,求出有多少个子矩阵,其每一行每一列都没有相同的字母(不同行不同列的位置字母可以相同)
枚举以每一个点作为子矩阵的左上角,求出有多少个,然后最后求和即为答案。
然后问题就是如何快速计算每个点作为左上角有多少个满足条件的子矩阵。
先考虑每行不同,不考虑列不同,其子矩阵种数,可以从上往下推。
就在每次右边界减小时 答案加上 当前长度*下来层数,因为接下来右边界减小,所以当前长度的子矩阵接下来不会在出现,可以直接加入答案,设当前长度为 L1,接下来长度为L2,开始横坐标为x1,现在横坐标为x2
则加入答案的值为 (L1-L2) * (x2-x1)
整个分块的过程如上
然后再同样的方式加入纵向的限制,有所不同的是行的是从上到下限制,而列的则是从左到右限制
就逐步分块求和。
可以知道,矩阵越大,约束越强。
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
typedef long long int LL;
const int N = 1e3 + 10;
int n, m, vis[N], r[N][N], d[N][N], mi[N];
char mp[N][N];
int main()
{
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) scanf("%s", mp[i]+1);
for(int i=1;i<=n;i++) {
for(int j=1,ed=1;j<=m;j++) {
while(ed <= m && !vis[mp[i][ed]]) vis[mp[i][ed]] = 1, ed ++;
vis[mp[i][j]] = 0;
r[i][j] = ed - j;
}
}
for(int j=1;j<=m;j++) {
for(int i=1,ed=1;i<=n;i++) {
while(ed <= n && !vis[mp[ed][j]]) vis[mp[ed][j]] = 1, ed ++;
vis[mp[i][j]] = 0;
d[i][j] = ed - i;
}
}
LL ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
LL len = r[i][j]; mi[j] = d[i][j];
for(int k=1;k<r[i][j];k++) {
mi[j+k] = min(mi[j+k-1], d[i][j+k]);
}
for(int k=1;k<d[i][j];k++){
while(len >= 1 && k >= mi[j+len-1]){
ans += k;
len --;
}
if(len > r[i+k][j]) {
ans += (len - r[i+k][j]) * k;
len = r[i+k][j];
}
}
ans += len * d[i][j];
}
}
printf("%lld\n", ans);
return 0;
}