简述题意
给定一个 n × m n \times m n×m 的方格,每个格子里有一个小写英文字母。
现在你有 k k k 个 n × m n \times m n×m 的方格,这些方格都是给定方格的基础上将左上角为 ( a i , b i ) (a_i,b_i) (ai,bi),右下角为 ( c i , d i ) (c_i,d_i) (ci,di) 的矩形区域的方格中的字母替换成字符 e i e_i ei。
定义两个方格的距离为所有格子中字母在 Ascll \text{Ascll} Ascll 码的差累和。你要找到 k k k 个方格中的一个方格,满足它到其他 k − 1 k-1 k−1 个矩阵的距离之和最小,并输出这个最小值。
- 1 ≤ n , m ≤ 1000 , 1 ≤ k ≤ 3 × 1 0 5 1 \le n,m \le 1000,1 \le k \le 3 \times 10^5 1≤n,m≤1000,1≤k≤3×105
- 1 ≤ a i , b i ≤ n , 1 ≤ c i , d i ≤ m 1 \le a_i,b_i \le n,1 \le c_i,d_i\le m 1≤ai,bi≤n,1≤ci,di≤m
思路
以下简记:
- X ( a i , b i , c i , d i ) X(a_i,b_i,c_i,d_i) X(ai,bi,ci,di) 表示 X X X 矩阵的左上角为 ( a i , b i ) (a_i,b_i) (ai,bi),右下角为 ( c i , d i ) (c_i,d_i) (ci,di) 的子矩阵的元素之和。
- A A A 表示原矩阵。
首先不难想到枚举每一个矩阵,求出其到其他 k − 1 k-1 k−1 个矩阵的距离和,取最小值即可,然而我们难以直接维护当前枚举到的矩阵与其他矩阵的距离和。
注意到,每一个新矩阵与原矩阵唯一的差别便是一个子矩阵不一样,套路地想到维护原矩阵。
不妨令 w i , j , k w_{i,j,k} wi,j,k 为 k k k 个新矩阵中, ( i , j ) (i,j) (i,j) 这一位的字母为 k k k 的数量, c n t i , j cnt_{i,j} cnti,j 表示 k k k 个新矩阵中有多少个矩阵与原矩阵不同的位置包含了 ( i , j ) (i,j) (i,j)。
考虑如何求出 w w w 数组。不妨先让 ∀ x ∈ [ 1 , n ] , y ∈ [ 1 , m ] , w x , y , A x , y = k \forall x \in [1,n],y \in [1,m],w_{x,y,A_{x,y}}=k ∀x∈[1,n],y∈[1,m],wx,y,Ax,y=k,后面减去掉原矩阵贡献的这一部分即可。而后,每生成一个矩阵,就相当于把 ∀ x ∈ [ a i , b i ] , y ∈ [ c i , d i ] , w x , y , e i ← w x , y , e i + 1 , c n t x , y ← c n t x , y + 1 \forall x \in [a_i,b_i],y \in[c_i,d_i],w_{x,y,e_i} \leftarrow w_{x,y,e_i}+1,cnt_{x,y} \leftarrow cnt_{x,y}+1 ∀x∈[ai,bi],y∈[ci,di],wx,y,ei←wx,y,ei+1,cntx,y←cntx,y+1,最后统一把 x ∈ [ 1 , n ] , y ∈ [ 1 , m ] , w x , y , A x , y ← w x , y , A x , y − c n t x , y x \in [1,n],y \in [1,m],w_{x,y,A_{x,y}} \leftarrow w_{x,y,A_{x,y}} - cnt_{x,y} x∈[1,n],y∈[1,m],wx,y,Ax,y←wx,y,Ax,y−cntx,y 即可。
有了 w w w 数组后,就可以求出原矩阵与 k k k 个新矩阵的距离和。具体地,枚举 ( i , j ) (i,j) (i,j),那么当前位置与 k k k 个矩阵对应位置的差值和即为: r e s i , j = ∑ o ∈ S w i , j , o × ∣ A i , j − o ∣ res_{i,j}=\sum_{o \in S}w_{i,j,o} \times |A_{i,j}-o| resi,j=∑o∈Swi,j,o×∣Ai,j−o∣,其中 o o o 为字符, S S S 为字符集。
有了 r e s res res 数组之后就大功告成了!
而后,枚举每一个新矩阵,先继承一下原矩阵的贡献,就是 r e s ( 1 , 1 , n , m ) − r e s ( a i , b i , c i , d i ) res(1,1,n,m)-res(a_i,b_i,c_i,d_i) res(1,1,n,m)−res(ai,bi,ci,di),紧接着考虑 ( a i , b i , c i , d i ) (a_i,b_i,c_i,d_i) (ai,bi,ci,di) 这个子矩阵对答案的贡献,显然就是 ∑ o ∈ S w o ( a i , b i , c i , d i ) × ∣ e i − o ∣ \sum_{o \in S}w_o(a_i,b_i,c_i,d_i) \times |e_i-o| ∑o∈Swo(ai,bi,ci,di)×∣ei−o∣。(说人话 w o ( a i , b i , c i , d i ) w_o(a_i,b_i,c_i,d_i) wo(ai,bi,ci,di) 表示的就是 k k k 个矩阵每一个 ( a i , b i , c i , d i ) (a_i,b_i,c_i,d_i) (ai,bi,ci,di) 子矩阵中有多少个字符 o o o)
总结一下,一个矩阵到其他 k − 1 k-1 k−1 个矩阵的距离和即为:
r e s ( 1 , 1 , n , m ) − r e s ( a i , b i , c i , d i ) + ∑ o ∈ S w o ( a i , b i , c i , d i ) × ∣ e i − o ∣ res(1,1,n,m)-res(a_i,b_i,c_i,d_i)+\sum_{o \in S}w_o(a_i,b_i,c_i,d_i) \times |e_i-o| res(1,1,n,m)−res(ai,bi,ci,di)+o∈S∑wo(ai,bi,ci,di)×∣ei−o∣
空间优化
如果你使用的是 区间修改,区间查询 的二维树状数组求解 w , r e s w,res w,res,那么恭喜你喜提 Memory limit exceeded on test 1 \text{Memory limit exceeded on test 1} Memory limit exceeded on test 1,因为每一个树状数组需要 4 4 4 个 b i t bit bit 数组,且需要 26 26 26 个这样的树状数组,空间炸飞了。
再仔细思考,发现此题的 区间修改,区间查询 不是同时进行的,所以不妨先差分,再前缀和求出每一位,再前缀和维护子矩阵的和,这样普通数组就可以解决。
代码
请自行忽略树状数组,当成差分看就行。
主要是最开始超空间限制,改了半截发现空间开得下了就懒得改了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 1000 + 5 , MAXM = 3e5 + 5;
int n , m , k , a[MAXM] , b[MAXM] , c[MAXM] , d[MAXM];
ll Pre_T[MAXN][MAXN][26] , Pre_res[MAXN][MAXN] , res[MAXN][MAXN];
char mp[MAXN][MAXN] , opt[MAXM];
struct BIT{
#define lowbit(x) (x & -x)
int bit[MAXN][MAXN];
void update(int k , int k2 , int x) {
for (int i = k ; i <= n ; i += lowbit(i)) {
for (int j = k2 ; j <= m ; j += lowbit(j)) {
bit[i][j] += x;
}
}
}
ll Sum(int k , int k2) {
ll sum = 0;
for (int i = k ; i ; i -= lowbit(i)) {
for (int j = k2 ; j ; j -= lowbit(j)) {
sum += bit[i][j];
}
}
return sum;
}
void add(int a , int b , int c , int d , int x) {update(a , b , x) , update(a , d + 1 , -x) , update(c + 1 , b , -x) , update(c + 1 , d + 1 , x);}
}T[27];
ll query1(int a , int b , int c , int d , int opt) {return Pre_T[c][d][opt] - Pre_T[a - 1][d][opt] - Pre_T[c][b - 1][opt] + Pre_T[a - 1][b - 1][opt];}
ll query2(int a , int b , int c , int d) {return Pre_res[c][d] - Pre_res[a - 1][d] - Pre_res[c][b - 1] + Pre_res[a - 1][b - 1];}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
cin >> n >> m >> k;
for (int i = 1 ; i <= n ; i ++) {
for (int j = 1 ; j <= m ; j ++) {
cin >> mp[i][j];
}
}
for (int i = 1 ; i <= k ; i ++) {
cin >> a[i] >> b[i] >> c[i] >> d[i] >> opt[i];
T[opt[i] - 'a'].add(a[i] , b[i] , c[i] , d[i] , 1);
T[26].add(a[i] , b[i] , c[i] , d[i] , 1);
}
for (int i = 1 ; i <= n ; i ++) {
for (int j = 1 ; j <= m ; j ++) {
int sel = T[26].Sum(i , j);
T[mp[i][j] - 'a'].add(i , j , i , j , k - sel);
}
}
for (int i = 1 ; i <= n ; i ++) {
for (int j = 1 ; j <= m ; j ++) {
int now = mp[i][j] - 'a';
for (int o = 0 ; o < 26 ; o ++) res[i][j] += 1ll * abs(now - o) * T[o].Sum(i , j);
}
}
for (int i = 1 ; i <= n ; i ++) {
for (int j = 1 ; j <= m ; j ++) {
Pre_res[i][j] = Pre_res[i - 1][j] + Pre_res[i][j - 1] - Pre_res[i - 1][j - 1] + res[i][j];
for (int o = 0 ; o < 26 ; o ++) {
Pre_T[i][j][o] = Pre_T[i - 1][j][o] + Pre_T[i][j - 1][o] - Pre_T[i - 1][j - 1][o] + T[o].Sum(i , j);
}
}
}
ll ans = 1e16;
for (int i = 1 ; i <= k ; i ++) {
int now = opt[i] - 'a';
ll w = query2(1 , 1 , n , m) - query2(a[i] , b[i] , c[i] , d[i]);
for (int o = 0 ; o < 26 ; o ++) w += 1ll * abs(now - o) * query1(a[i] , b[i] , c[i] , d[i] , o);
ans = min(ans , w);
}
cout << ans;
return 0;
}