牛客网暑期ACM多校训练营(第二场)J题题解(随机化算法/平方构造法过数据/bit操作)

首先将题目化为一维进行思考。则题目化简为修改后,查询区间内与修改的数不同的数的个数。

该题有多种做法,先讲最简单的一种:

1.先用两个前缀和保存区间修改值和区间修改次数,则若是 该点修改值 = 该点修改次数 * 该点初始值,则很有可能施的是多次相同的肥料(即 8 = 4 + 4),但出题人肯定会出数据卡掉这一正确性并非绝对的算法(如 8 = 2 + 6),但如果将对应的值随机化, 如将 4 -> 6, 2 -> 5, 6 -> 3 ,则上面的等式化为 :12 = 6 + 6, 12 != 5 + 3,出题人的数据就很有可能卡不到你 。

附注:虽然本题只要随机化就能过,但最好映射为素数,这样出错的概率更低。

思想大概就这样,接下来只要用这个思想,把一维前缀和转化为二维的就行了。

以下是代码(配合注释)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 3e6 + 10;
int mp[maxn];
ll  vis[maxn] = {0}, dict[maxn] = {0},change[maxn] = {0};

int main()
{
     int n,m,t;
     cin >> n >> m >> t;
     for(int i = 1; i <= n * m; i++)    mp[i] = i; //初始化
     random_shuffle(mp + 1, mp + 1 + n * m); //随机映射
     m += 2; // 考虑原本[x][m + 1] 及[x][0] 两个序列,故加2
     for(int i = 1; i <= n; i++)
     {
        for(int j = 1; j <= m - 2; j++)
        {
            scanf("%lld",&dict[i * m + j]);
            dict[i * m + j] = mp[dict[i * m + j]]; //映射
        }
     }
     for(int i = 0; i < t; i++)
     {
        int x1,y1,x2,y2,k;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&k);
        k = mp[k];
        change[x1 * m + y1] += k; change[(x2 + 1) * m + y2 + 1] += k; //二维前缀和操作
        change[x1 * m + y2 + 1] -= k; change[(x2 + 1) * m + y1] -= k;
        vis[x1 * m + y1]++; vis[(x2 + 1) * m + y2 + 1]++;
        vis[x1 * m + y2 + 1]--; vis[(x2 + 1) * m + y1]--;
     }

     int tot = 0;
     for(int i = 1; i <= n; i++)
     {
        for(int j = 1; j <= m - 2; j++)
        {
            change[i * m + j] += change[(i - 1) * m + j] + change[i * m + j - 1] - change[(i - 1) * m + j - 1]; 二维前缀和操作
            vis[i * m + j] += vis[(i - 1) * m + j] + vis[i * m + j - 1] - vis[(i - 1) * m + j - 1];
            if( change[i * m + j] != vis[i * m + j] * dict[i * m + j] ) //该点修改值 != 该点修改次数 * 该点初始值,则施的肥料不同
                tot++;
        }
     }
     cout << tot << endl;
     return 0;

}

2.另一个与该解法类似的是添加一个区间修改值的平方和,通过双重判断,同样可以规避掉出题人的特殊数据。

依据:2c = a + b, 2c^2 = a^2 + b^2,当且仅当a=b=c 时成立,三维同理

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn = 3e6 + 10;
ll  dict[maxn] = {0},change1[maxn] = {0},change2[maxn] = {0};
int vis[maxn] = {0};

int main()
{
     int n,m,t;
     cin >> n >> m >> t;
     m += 2; // 考虑原本[x][m + 1] 及[x][0] 两个序列,故加2
     for(int i = 1; i <= n; i++)
     {
        for(int j = 1; j <= m - 2; j++)
        {
            scanf("%lld",&dict[i * m + j]);
        }
     }
     for(int i = 0; i < t; i++)
     {
        int x1,y1,x2,y2;
        ll k;
        scanf("%d%d%d%d%lld",&x1,&y1,&x2,&y2,&k);
        change1[x1 * m + y1] += k; change1[(x2 + 1) * m + y2 + 1] += k; //二维前缀和操作
        change1[x1 * m + y2 + 1] -= k; change1[(x2 + 1) * m + y1] -= k;
        k = k * k;
        change2[x1 * m + y1] += k; change2[(x2 + 1) * m + y2 + 1] += k;//修改值的平方
        change2[x1 * m + y2 + 1] -= k; change2[(x2 + 1) * m + y1] -= k;
        vis[x1 * m + y1]++; vis[(x2 + 1) * m + y2 + 1]++;
        vis[x1 * m + y2 + 1]--; vis[(x2 + 1) * m + y1]--;

     }

     int tot = 0;
     for(int i = 1; i <= n; i++)
     {
        for(int j = 1; j <= m - 2; j++)
        {
            change1[i * m + j] += change1[(i - 1) * m + j] + change1[i * m + j - 1] - change1[(i - 1) * m + j - 1]; 二维前缀和操作
            change2[i * m + j] += change2[(i - 1) * m + j] + change2[i * m + j - 1] - change2[(i - 1) * m + j - 1];
            vis[i * m + j] += vis[(i - 1) * m + j] + vis[i * m + j - 1] - vis[(i - 1) * m + j - 1];
            if( change1[i * m + j] != dict[i * m + j] * vis[i * m + j] ||
                change2[i * m + j] != dict[i * m + j] * dict[i * m + j] * vis[i * m + j]  ) //双重判断
                tot++;
        }
     }
     cout << tot << endl;
     return 0;

}

1、2小结:对于正确率较高,但并非绝对正确的算法,可以通过类似与哈希的思想来绕过出题人的特殊数据,从而AC。

 

3.题解bit做法,主要思想与题解一样,在这里我把标称的代码修改后,再结合代码解释。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
inline int read() //加快读入速度
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}

const int maxn = 3e4 + 10;

int n,m,t;
int dict[maxn] = {0},check[maxn] = {0},change[maxn] = {0};//dict存储原始数据
                                 //change存储区间修改的次数,check用作bit判断
#define y1 y_1  //y1貌似是关键字,不define编译会出错              
int x1[maxn],y1[maxn],x2[maxn],y2[maxn],tp[maxn]; //把修改的数据存下来,减少传值
bool die[maxn] = {0}; //死亡植物结果统计

void add(int i,int *p) //二维前缀和修改
{
    p[x1[i] * m + y1[i]]++; p[(x2[i] + 1) * m + y2[i] + 1]++;
    p[x1[i] * m + y2[i] + 1]--; p[(x2[i] + 1) * m + y1[i]]--;
}

void work(int *p) //二维前缀和统计
{
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m - 2; j++)
        {
            p[i * m + j] += p[(i - 1) * m + j] + p[i * m + j - 1]
                            - p[(i - 1) * m + j - 1];
        }
    }
}


int main()
{
    cin >> n >> m >> t;
    m += 2;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m - 2; j++)
            dict[i * m + j] = read();
    for(int i = 0; i < t; i++)
    {
        x1[i] = read(),y1[i] = read(), x2[i] = read();
        y2[i] = read(),tp[i] = read();
        add(i,change);//对区间修改次数进行修改
    }
    work(change);//统计结果
    for(int v = 0; v < 20; v++)//按位bit操作
    {
        for(int i = 1; i <= n; i++)//首先要赋初值
            for(int j = 1; j <= m - 2; j++)
                check[i * m + j] = 0;
        for(int i = 0; i < t; i++) // 如果修改的值在该位为1,则进行区间修改
            if(tp[i] >> v & 1)
                add(i,check);
        work(check);//统计
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m - 2; j++)
            {
                if(dict[i * m + j] >> v & 1) //如果原本区间的值在该位的值为1
                {
                    if(check[i * m + j] != change[i * m + j])//不等表明必定浇了
                      die[i * m + j] = 1;                    //两种以上肥料
                }
                else
                {
                    if(check[i * m + j]) //若成立则表明 i!=j
                        die[i * m + j] = 1;
                }
            }
        }
    }
    int tot = 0;
    for(int i = 1; i <= n; i++)//统计结果
        for(int j = 1; j <= m - 2; j++)
            tot += die[i * m + j];
    cout << tot << endl;
    return 0;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值