Dasha decided to have a rest after solving the problem D and began to look photos from previous competitions.
Let's call photos as the matrix with the size n × m, which consists of lowercase English letters.
Some k photos especially interested her, because they can be received from photo-template by painting a rectangular area in a certain color. Let's call such photos special.
More formally the i-th special photo is received from the photo-template by replacing all characters on some rectangle with upper left corner of the cell with coordinates (ai, bi) and lower right corner in the cell with coordinates (ci, di) to the symbol ei.
Dasha asks you to find the special photo so that the total distance from it to all other special photos is minimum. And calculate this distance.
Determine the distance between two photos as the sum of distances between all corresponding letters. The distance between two letters is the difference module of their positions in the alphabet. For example, the distance between letters 'h' and 'm' equals |8 - 13| = 5, because the letter 'h' is the 8-th in the alphabet, the letter 'm' is the 13-th.
The first line contains three integers n, m, k (1 ≤ n, m ≤ 103, 1 ≤ k ≤ 3·105) — the number of strings in the photo-template, the number of columns and the number of special photos which are interesting for Dasha.
The next n lines contains the string with m length which consists of little Latin characters — the description of the photo-template.
Each of the next k lines contains the description of the special photo in the following format, "ai bi ci di ei" (1 ≤ ai ≤ ci ≤ n, 1 ≤ bi ≤ di ≤ m), where (ai, bi) — is the coordinate of the upper left corner of the rectangle, (ci, di) — is the description of the lower right corner, and ei — is the little Latin letter which replaces the photo-template in the described rectangle.
In the only line print the minimum total distance from the found special photo to all other special photos.
3 3 2 aaa aaa aaa 1 1 2 2 b 2 2 3 3 c
10
5 5 3 abcde eabcd deabc cdeab bcdea 1 1 3 4 f 1 2 3 3 e 1 3 3 4 i
59
In the first example the photos are following:
bba aaa bba acc aaa acc
The distance between them is 10.
题意:
给你一个 n×m 的字母矩阵,然后给你 k 次独立的操作,每次操作是对原矩阵的某一子矩形用给定字符覆盖
并称操作完的新矩阵为特殊矩阵
定义两个矩阵的差别值是对应位置的字母的 ASCII ASCII码的差值绝对值之和
要找一个特殊矩阵,使其和剩余 k−1 k−1个特殊矩阵的差别值之和最小
输出最小值
n*m不大,所以可以尝试对每一位都预处理出填每个字符的答案。
首先把k个矩阵在行a,c处打插入、删除标记。由于覆盖的部分是个矩形,列对应的是一个区间。
方法1:
开26个线段树对应每个小写字母,对于每一行,列[1,m]就是一个区间,对该行进行插入、删除就是区间加减。计算每个点的答案时,先在26个线段树里进行单点查询,查询出在这k次操作里面这个点上每个字符出现次数,取出每种字母对应的答案(注意有些矩阵中不覆盖掉当前点,就要把原矩阵的字母算上)。然后枚举填的字母,直接计算即可。
接下来是统计答案:对填每种字母、原矩阵已经求出来每一位的答案,做一个二维前缀和,然后枚举k个矩阵,在原矩阵的答案上减去覆盖掉矩形的答案,再把填上对应字母的答案加上,就得到了该矩阵的答案了。
时间复杂度 O(26nmlogm) ,可以通过所有数据。
注意此处有个线段树的特殊应用,由于此题中线段树是区间更新,单点查询,所以不需要lazy标记了,只要在查询时从根结点走一条到这个查询点的路径,加上路经过路径上结点的权值就行了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
#define mp make_pair
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll inf=0x3fffffffffffff;
const ll mod=1000000007;
const int maxn=1e3+100;
struct node
{
int x1,y1,x2,y2,kind;
}a[maxn*300];
int seg[30][maxn*4]; //26个线段树,记录26种字符的情况
char map[maxn][maxn];
VI in[maxn],out[maxn];
int cnt[30];
ll p[maxn][maxn],s[30][maxn][maxn]; //两个二位前缀和
void update(int i,int l,int r,int L,int R,int *t,int v) //区间更新
{
if(L==l&&R==r)
{
t[i]+=v;
return;
}
int m=(L+R)/2;
if(r<=m) update(i*2,l,r,L,m,t,v);
else if(l>m) update(i*2+1,l,r,m+1,R,t,v);
else
{
update(i*2,l,m,L,m,t,v);
update(i*2+1,m+1,r,m+1,R,t,v);
}
}
int query(int i,int pos,int L,int R,int *t)
{
if(L==R)
return t[i];
int m=(L+R)/2;
if(pos<=m) return t[i]+query(i*2,pos,L,m,t); //区间更新后单点查询时不需要用lazy标记,只要把在线段树从根到这个点的路径沿路所有点权值和加上就可以了
else return t[i]+query(i*2+1,pos,m+1,R,t);
}
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
rep(i,1,n+1) scanf("%s",map[i]+1);
rep(i,0,k)
{
scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
in[a[i].x1].pb(i),out[a[i].x2].pb(i);
char str[10];
scanf("%s",str);
a[i].kind=str[0]-'a';
}
rep(i,1,n+1)
{
vector<int>::iterator it;
for(it=in[i].begin();it!=in[i].end();it++)
{
update(1,a[*it].y1,a[*it].y2,1,m,seg[a[*it].kind],1);
}
rep(j,1,m+1)
{
int num=0;
ll sum=0;
memset(cnt,0,sizeof(cnt));
rep(x,0,26)
{
cnt[x]+=query(1,j,1,m,seg[x]);
num+=cnt[x];
sum+=cnt[x]*x;
}
cnt[map[i][j]-'a']+=k-num;
sum+=(map[i][j]-'a')*(k-num);
rep(x,0,26)
{
if(x==map[i][j]-'a')
p[i][j]=p[i-1][j]+p[i][j-1]-p[i-1][j-1]+sum; //p[i][j]记录子矩阵(1,1)-(i,j)其他k个的差值之和
s[x][i][j]=s[x][i-1][j]+s[x][i][j-1]-s[x][i-1][j-1]+sum; //s[x][i][j]表示子矩阵(1,1)-(i,j)都填上字符'a'+x后与其他k个的差值之和
if(x>0) cnt[x]+=cnt[x-1];
sum+=cnt[x]-(k-cnt[x]); //sum为点(i,j)填上字符'a'+x时与其他k个差值之和,sum随着x的改变而更新
}
}
for(it=out[i].begin();it!=out[i].end();it++)
{
update(1,a[*it].y1,a[*it].y2,1,m,seg[a[*it].kind],-1);
}
}
ll ans=inf;
rep(i,0,k)
{
ll res=p[n][m]-p[a[i].x2][a[i].y2]+p[a[i].x1-1][a[i].y2]+p[a[i].x2][a[i].y1-1]-p[a[i].x1-1][a[i].y1-1]; //先除去要填的部分算其他部分差值和
res+=s[a[i].kind][a[i].x2][a[i].y2]-s[a[i].kind][a[i].x1-1][a[i].y2]-s[a[i].kind][a[i].x2][a[i].y1-1]+s[a[i].kind][a[i].x1-1][a[i].y1-1];//加上填的这部分的差值和
ans=min(ans,res);
}
printf("%lld\n",ans);
return 0;
}
其实有个更快的做法
log其实可以去掉。
对于每个矩形的参数a,b,c,d,对应的字符中,在(a,b)、(c+1,d+1)处打+1标记,(c+1,b)、(a,d+1)处打-1标记。然后求前缀和。可以发现,对于任意一个矩形的前缀和,如果在矩形内部,它正好+1,否则为0。(画一画就知道)
接下来就是枚举每个位置的字母,然后二维前缀和。和前面的一样做
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
#define mp make_pair
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll inf=0x3fffffffffffff;
const ll mod=1000000007;
const int maxn=1e3+100;
struct node
{
int x1,y1,x2,y2,kind;
}a[maxn*300];
char map[maxn][maxn];
int cnt[30];
ll p[maxn][maxn],s[30][maxn][maxn]; //两个二位前缀和
int g[30][maxn][maxn];
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
rep(i,1,n+1) scanf("%s",map[i]+1);
rep(i,0,k)
{
scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
char str[10];
scanf("%s",str);
a[i].kind=str[0]-'a';
g[a[i].kind][a[i].x1][a[i].y1]++,g[a[i].kind][a[i].x2+1][a[i].y2+1]++;
g[a[i].kind][a[i].x1][a[i].y2+1]--,g[a[i].kind][a[i].x2+1][a[i].y1]--;
}
rep(i,1,n+1)
{
rep(j,1,m+1)
{
int num=0;
ll sum=0;
memset(cnt,0,sizeof(cnt));
rep(x,0,26)
{
g[x][i][j]+=g[x][i-1][j]+g[x][i][j-1]-g[x][i-1][j-1]; //二维前缀和更新
cnt[x]=g[x][i][j];
num+=cnt[x];
sum+=cnt[x]*x;
}
cnt[map[i][j]-'a']+=k-num;
sum+=(map[i][j]-'a')*(k-num);
rep(x,0,26)
{
if(x==map[i][j]-'a')
p[i][j]=p[i-1][j]+p[i][j-1]-p[i-1][j-1]+sum; //p[i][j]记录子矩阵(1,1)-(i,j)其他k个的差值之和
s[x][i][j]=s[x][i-1][j]+s[x][i][j-1]-s[x][i-1][j-1]+sum; //s[x][i][j]表示子矩阵(1,1)-(i,j)都填上字符'a'+x后与其他k个的差值之和
if(x>0) cnt[x]+=cnt[x-1];
sum+=cnt[x]-(k-cnt[x]); //sum为点(i,j)填上字符'a'+x时与其他k个差值之和,sum随着x的改变而更新
}
}
}
ll ans=inf;
rep(i,0,k)
{
ll res=p[n][m]-p[a[i].x2][a[i].y2]+p[a[i].x1-1][a[i].y2]+p[a[i].x2][a[i].y1-1]-p[a[i].x1-1][a[i].y1-1]; //先除去要填的部分算其他部分差值和
res+=s[a[i].kind][a[i].x2][a[i].y2]-s[a[i].kind][a[i].x1-1][a[i].y2]-s[a[i].kind][a[i].x2][a[i].y1-1]+s[a[i].kind][a[i].x1-1][a[i].y1-1];//加上填的这部分的差值和
ans=min(ans,res);
}
printf("%lld\n",ans);
return 0;
}