首先将题目化为一维进行思考。则题目化简为修改后,查询区间内与修改的数不同的数的个数。
该题有多种做法,先讲最简单的一种:
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;
}