2018牛客多校赛第二场 J-farm(二进制位运算思想+二维差分数组)

这个位运算+二维差分数组对于我这种菜鸡来说可以说是神思维了。。即使想到了用差分数组搞一搞,但是根本想不到位运算啊orz。。事后看了好多题解,终于理解了位运算的思维,赶快记录下来

题目链接https://www.nowcoder.com/acm/contest/140/J

题解思路
先考虑只有0和1两种数的情况,如果这一位植物是0,撒完农药后这一位有1,说明必死,同理对于1也一样。至于处理的方法,对于本题的数据暴力是绝对不可能的(而且这是多校赛啊orz,怎么可能这么简单),于是稍微了解差分数组的很容易想到用二维差分数组处理前缀和搞一搞,其实也很简单。
再考虑本题情况:如果这一位植物是i,撒完农药后这一位有不是i的,说明必死。
那么问题来了,这玩意怎么用差分数组搞?毕竟差分数组处理0和1的情况是相当于记录了次数,但是这里如果直接对每次撒的农药的权值运用差分数组处理,处理出来的结果只能是每株植物最后被撒了农药的总权值,对于答案似乎并没有什么帮助(大概,因为我还没了解别的解法),于是我们需要转变思路。
我们知道对于两个不一样的数,他们的二进制位至少有一位不同,这里就是突破口。我们发现农药的权值最多不超过1e6,化成二进制之后只有20位,于是我们可以对每一位进行枚举。我们开两个二维差分数组,一个计算对于权值k的农药的第i位是有多少1,另一个计算对于权值k的农药的第i位有多少0,然后处理权值为a的植物的第i为,如果这一位是1,我们只需看记录0的差分数组的这一位是不是0,如果是0,则说明对于这一位来说,植物的权值和农药的权值是一样的,如果不是0,则说明对于这一位农药的权值和植物的权值不一样,那么这株植物就死了。然后我们只需开一个数组,记录死掉的植物,最后统计一下就ok了。

顺便贴一个我个人觉得讲的很好理解的的差分数组和前缀和的讲解,不懂差分和前缀和的可以看看:https://www.cnblogs.com/OIerShawnZhou/p/7348088.html

菜鸡真的菜。。看了一下感觉几乎是全场题了,但是我事后补题都看了好久QAQ。。还需努力还需努力!

补题ac代码

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1000005;
int n, m, t;
int a[maxn], bn[2][maxn];// bn[0]和bn[1]记录每一位分别为0和1时候的差分数组,每运算一次清一次零
//看到很多题解是先用差分数组记录洒农药的次数,然后处理每一位的时候只记录该位为1的时候的全覆盖,看了我好久。。个人感觉分别记录1和0比较容易理解,就这么写了一发
bool vis[maxn];//判断哪些位置是死掉的

struct p {
    int x1, x2, y1, y2, k;
} b[maxn];

int id(int x, int y) {//将二维坐标转化成一维
    return (x - 1) * m + y - 1;
}

void add(int i, int *s) {//二维差分数初始处理
    s[id(b[i].x1, b[i].y1)]++;
    if(b[i].y2 < m)//超出矩阵边界的不操作
        s[id(b[i].x1, b[i].y2 + 1)]--;
    if(b[i].x2 < n)//同上
        s[id(b[i].x2 + 1, b[i].y1)]--;
    if(b[i].x2 < n && b[i].y2 < m)//同上
        s[id(b[i].x2 + 1, b[i].y2 + 1)]++;
}

void work(int *s) {//二维差分数组求前缀和
    for(int i = 0; i < n * m; i++) {
        if(i % m != 0)
          s[i] += s[i - 1];
        if(i >= m)
          s[i] += s[i - m];
        if(i % m != 0 && i >= m)
          s[i] -= s[i - m - 1];
    }
}

int main() {
    while(~scanf("%d%d%d", &n, &m, &t)) {
        memset(vis, 0, sizeof(vis));
        for(int i = 0; i < n * m; i++)
            scanf("%d", &a[i]);
        for(int i = 0; i < t; i++)
            scanf("%d%d%d%d%d", &b[i].x1, &b[i].y1, &b[i].x2, &b[i].y2, &b[i].k);
        for(int i = 0; i < 20; i++) {
            for(int j = 0; j < n * m; j++)
                bn[0][j] = bn[1][j] = 0;//每次枚举每一位需清零
            for(int j = 0; j < t; j++)
                add(j, bn[b[j].k >> i & 1]);//分别统计0和1的覆盖次数
            work(bn[0]);//用差分数组前缀和完成这一位0的全覆盖
            work(bn[1]);//用差分数组前缀和完成这一位1的全覆盖
            for(int j = 0; j < n * m; j++) {
                if((a[j] >> i & 1) == 0 && bn[1][j] != 0) //如果这个植物的这一位是0且全覆盖(撒完农药)后这一位有1,则这一位死掉
                    vis[j] = true;
                else if((a[j] >> i & 1) == 1 && bn[0][j] != 0)//如果这个植物的这一位是1且全覆盖(撒完农药)后这一位有0,则这一位死掉
                    vis[j] = true;
            }
        }
        int ans = 0;
        for(int i = 0; i < n * m; i++) {
            if(vis[i])
                ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值