[ZSTUOJ-4438]曼哈顿距离

Description

在平面上,坐标(x1, y1)的点P1与坐标(x2, y2)的点P2的曼哈顿距离为:|x1-x2|+|y1-y2|。 现在有一个nXn (1<=n<=1000)的矩阵,一开始矩阵中每个元素的值都为零。 对这个矩阵进行m (1<=m<=100000)次操作,每次操作定义如下:
(x1, y1), (x2, y2): 对于矩阵中满足x1<=x<=x2, y1<=y<=y2的所有元素(x, y)加上(x, y)与(x1, y1)的曼哈顿距离|x1-x|+|y1-y|
例如,当n=5,m=2,两组操作分别为(1, 1), (4, 4)和(2, 2), (5, 5):
[ZSTUOJ-4438]曼哈顿距离_第1张图片
现在想让你输出m次操作后的矩阵,但是由于本OJ输出文件不能超过1M,所以输出矩阵所有非零元素的乘积即可 (结果对1000000007取余),没有非零项则输出0;

Input

第一行两个整数n, m,分别表示正方形网格的边长和操作次数;
接下来m行,每行4个整数x1 y1 x2 y2;

Output

输出一个整数代表答案。

Sample Input

5 2
1 1 4 4
2 2 5 5

Sample Output

853543927

 

思路:首先要会差分标记,然后会求前缀和,这里放2个链接

https://blog.csdn.net/k_r_forever/article/details/81775899
https://www.cnblogs.com/OIerShawnZhou/p/7348088.html

然后做的时候建立2个差分数组,一个记录每次要加的最小值,也就是1;一个记录要加的最大值,也就是加到多少为止,为x1+y1那么多。理由我代码里讲。

闲话:这道题可以说是被我和申屠dalao一起玩坏了,先放几张图片
[ZSTUOJ-4438]曼哈顿距离_第2张图片

看看这满满一页的提交,看看这内存占用的。。。就快爆128MB了。。。而且我们感觉占用110MB内存和116MB内存速度就是2个差别,所以要压力测试,使劲爆!(这样才会快点#手动滑稽)

代码
先放一份不那么反人类的吧。。。 

AC 8776kb 92ms C

#include
const int mod=1000000007;
int cf[1003][1003];//差分数组一号,记录最小差分值
int cf2[1003][1003];//差分数组二号,记录最大差分值
long long ans=1;
int flag=0;
int main()
{
    int n,m,a,b,c,d,dif,now;
    scanf("%d%d",&n,&m);
    while (m--)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        cf[a][b]+=1;
        cf[c+1][d+1]+=1;
        cf[a][d+1]-=1;
        cf[c+1][b]-=1;//二维差分的标记方法,使(a,b)到(c,d)这整块上的值都+1
        dif=a+b;
        cf2[a][b]+=dif;
        cf2[c+1][d+1]+=dif;
        cf2[a][d+1]-=dif;
        cf2[c+1][b]-=dif;//使(a,b)到(c,d)这整块上的值都+dif
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
        {//下面这2个式子其实可以合并到下面那2个for循环里
            cf[i][j]+=cf[i][j-1]+cf[i-1][j]-cf[i-1][j-1];
            cf2[i][j]+=cf2[i][j-1]+cf2[i-1][j]-cf2[i-1][j-1];//差分求前缀和的公式,a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1]+cf[i][j],这里因为a矩阵初始值都是0,而且前缀和只需最后的时候求一遍,所以这题可以直接用cf数组充当a数组来存储前缀和,就相当于cf[i][j]=cf[i][j-1]+cf[i-1][j]-cf[i-1][j-1]+cf[i][j],即上面的式子,省了一些内存。。。
        }
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
        {
            now=cf[i][j]*(i+j)-cf2[i][j];//下面将解释为什么上面的cf2是加上(x1+y1),这里为什么是这样乘起来计算
            if (now)
            {
                flag=1;
                ans=ans*now%mod;
            }
        }
    }
    if (flag) printf("%lld\n",ans);
    else printf("0\n");
    return 0;
}


原因:看下面这图,只需看tot2和sum2。假设他的左上角的点为(a,b),a=1,b=1,假设有一点(x,y),x=3,y=5,注意这里的坐标都是以绝对坐标为准(即以(0,0)点为原点),那么cf[i][j]=1,cf2[i][j]=a+b=2,则cf[i][j] * (i+j) - cf2[i][j] = 1 * (3+5) - (1+1) = 6 = (x-a) + (y-b),[x>a且y>b] ,分配一下就可以看出其实这个式子是会算出(x,y)离(a,b)点多远,相当于题目里的这个|x1-x|+|y1-y|。
好,如果是叠了2层,一样的,那假设有2个左上角的点分别为(a1,b1),(a2,b2),有一点(x,y),那就是cf[i][j] * (i+j) - cf2[i][j] = 2 *(x+y) - (a1+b1+a2+b2) = (x-a1) + (y-b1) + (x-a2) + (y-b2),所以是一样的,没什么区别。。。很easy的道理,而且可以保证的是,如果有cf[i][j]的值那(i,j)的点一定比左上角的(a,b)点大。。。
[ZSTUOJ-4438]曼哈顿距离_第3张图片

然后下面是我的反人类代码,使用了快读fread(),原理是把数据先全读到缓冲区,然后再通过字符读取数据


AC 127596kb 48ms C++

#include
const int mod=1000000007;
int cf[1002][1002];
int cf2[1002][1002];
long long ans=1;
int flag=0;
namespace IO {
    const int MT = 116 * 1024 * 1024;//这里是116MB
    char IO_BUF[MT];
    int IO_PTR, IO_SZ;
    void begin() {
        IO_PTR = 0;
        IO_SZ = fread (IO_BUF, 1, MT, stdin);
    }
    template
    inline bool scan_d (T & t) {
        while (IO_PTR < IO_SZ && IO_BUF[IO_PTR] != '-' && (IO_BUF[IO_PTR] < '0' || IO_BUF[IO_PTR] > '9'))
            IO_PTR ++;
        if (IO_PTR >= IO_SZ) return false;
        bool sgn = false;
        if (IO_BUF[IO_PTR] == '-') sgn = true, IO_PTR ++;
        for (t = 0; IO_PTR < IO_SZ && '0' <= IO_BUF[IO_PTR] && IO_BUF[IO_PTR] <= '9'; IO_PTR ++)
            t = t * 10 + IO_BUF[IO_PTR] - '0';
        if (sgn) t = -t;
        return true;
    }
    inline bool scan_s (char s[]) {
        while (IO_PTR < IO_SZ && (IO_BUF[IO_PTR] == ' ' || IO_BUF[IO_PTR] == '\n') ) IO_PTR ++;
        if (IO_PTR >= IO_SZ) return false;
        int len = 0;
        while (IO_PTR < IO_SZ && IO_BUF[IO_PTR] != ' ' && IO_BUF[IO_PTR] != '\n')
            s[len ++] = IO_BUF[IO_PTR], IO_PTR ++;
        s[len] = '\0';
        return true;
    }
    template
    void print(T x) {
        static char s[33], *s1; s1 = s;
        if (!x) *s1++ = '0';
        if (x < 0) putchar('-'), x = -x;
        while(x) *s1++ = (x % 10 + '0'), x /= 10;
        while(s1-- != s) putchar(*s1);
    }
    template
    void println(T x) {
        print(x); putchar('\n');
    }
};
using namespace IO;//要写上这个
int main()
{
    begin();//还有这个
    int n,m,a,b,c,d,dif,now;
    scan_d(n);
    scan_d(m);
    while (m--)
    {
        scan_d(a);
        scan_d(b);
        scan_d(c);
        scan_d(d);
        cf[a][b]+=1;
        cf[c+1][d+1]+=1;
        cf[a][d+1]-=1;
        cf[c+1][b]-=1;
        dif=a+b;
        cf2[a][b]+=dif;
        cf2[c+1][d+1]+=dif;
        cf2[a][d+1]-=dif;
        cf2[c+1][b]-=dif;
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
        {
            cf[i][j]+=cf[i][j-1]+cf[i-1][j]-cf[i-1][j-1];
            cf2[i][j]+=cf2[i][j-1]+cf2[i-1][j]-cf2[i-1][j-1];
        }
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
        {
            now=cf[i][j]*(i+j)-cf2[i][j];
            if (now)
            {
                flag=1;
                ans=ans*now%mod;
            }
        }
    }
    if (flag) print(ans);
    else print(0);
    return 0;
}

这里还有一份官方的另一份就不放了,x和y两个数组搞来搞去看都看不懂

#include
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
const int mod = 1e9 + 7;
char fre[10] = "data1.in";
char fot[20] = "data1.out";

int tot[N][N];
int sum[N][N];
void solve(){
    int n, m, u, l, d, r;
    scanf("%d%d", &n, &m);
    memset(tot, 0, sizeof tot);
    memset(sum, 0, sizeof sum);
    for(int i = 1; i <= m; ++i){
        scanf("%d%d%d%d", &u, &l, &d, &r);
        sum[u][l] += (u + l);
        sum[u][r + 1] -= (u + l);
        sum[d + 1][l] -= (u + l);
        sum[d + 1][r + 1] += (u + l);
        tot[u][l] ++;
        tot[u][r + 1] --;
        tot[d + 1][l] --;
        tot[d + 1][r + 1] ++;
    }
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            sum[i][j] += sum[i][j - 1];
            tot[i][j] += tot[i][j - 1];
            printf("%d ",sum[i][j]);
        }
        printf("\n");
    }
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            sum[i][j] += sum[i - 1][j];
            tot[i][j] += tot[i - 1][j];//他这里没有用二维前缀和的公式,而是一步一步求,具体过程可以看上面那张图,我也放在文末了
        }
    }
    LL ans = 1, now = 0;
    bool flag = false;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            now = tot[i][j] * (i + j) - sum[i][j];
            //printf("%d ",now);
//            sum[i][j] = 0, tot[i][j] = 0;
            if(now) ans = ans * now % mod, flag = true;
        }
        //printf("\n");
    }
    if(!flag)   ans = 0;
    printf("%I64d\n", ans);
}
int main()
{
    solve();
//    int T = 6;
//    for(int cas = 1; cas <= T; ++cas){
//        fre[4] = ('0' + cas);
//        fot[4] = ('0' + cas);
//        freopen(fre, "r", stdin);
//        freopen(fot, "w", stdout);
//        solve();
//    }
    return 0;
}
/*
419683495
775697741
996935516
391348459
0
797953655
*/


[ZSTUOJ-4438]曼哈顿距离_第4张图片
Excel是个好东西,Excel大法好!!!
 

 

你可能感兴趣的:(差分&前缀和,二维差分,二维前缀和)