Codeforces Round #394 (Div. 2) F. Dasha and Photos(二位前缀和+线段树/二维前缀和,好题)

F. Dasha and Photos
time limit per test
4 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

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.

Input

The first line contains three integers nmk (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. 

Output

In the only line print the minimum total distance from the found special photo to all other special photos.

Examples
input
3 3 2
aaa
aaa
aaa
1 1 2 2 b
2 2 3 3 c
output
10
input
5 5 3
abcde
eabcd
deabc
cdeab
bcdea
1 1 3 4 f
1 2 3 3 e
1 3 3 4 i
output
59
Note

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码的差值绝对值之和

要找一个特殊矩阵,使其和剩余 k1 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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值