2024省选模拟 D4T3 题解
题意:
有一个 \(m \times m\) 的棋盘,上面有 \(n\) 个点 \((x_i, y_i)\),每个点有一个权值 \(a_i\)。
选取 \(3\) 个点获得的价值是这三个点的权值和。
\(x\) 如果是特殊数则 \(x\) 是质数且满足 \(x \bmod 20 = 3\) 或 \(x \le 10\)。
求所有选取三个点使得这三个点的三个曼哈顿距离的中位数是特殊数的所有方案的价值和。
\(n \le 8000, m \le 30000\)。
题意:
被我独立做出来了,信心倍增。
首先距离都在 \(1 \sim 2m\) 内,算一下发现总共只有 \(754\) 个特殊数。
但是暴力枚举无法做,考虑优化。这个数据范围很像 \(O(n^2)\) 跑不满的样子,所以考虑枚举两个点。
我们枚举两个点 \((i, j)\) 且曼哈顿距离为特殊数的点对,考虑所有这个点对的距离是中位数,然后尝试算出第三个点。
为了方便起见,我们将曼哈顿距离近似成欧几里得距离。假设距离为 \(r\),我们分别以 \(i\) 和 \(j\) 为圆心画半径为 \(r\) 的圆。为了让 \(r\) 是中位数,这说明第三个点和这两个点的距离一个要大于 \(r\) 一个要小于 \(r\),考虑平面上的区域,这刚好就是恰好被一个圆覆盖的区域!
所以我们枚举点对,然后尝试求出平面内某个区域内部的点的个数和权值和即可。
但我们还需要做一些转化,首先我们真正的距离还是曼哈顿距离,不是真正的圆,很难计算。
然后就是经典技巧:曼哈顿距离转切比雪夫距离。
转完之后不难发现,这时候曼哈顿距离下近似的圆形就变成了矩形,这样我们就可以通过算矩形覆盖,显然可以都存下来用扫描线。而且这个扫描线可以用树状数组,极其好写。
这就是大体思路,但是还有一个很重要的问题:这样算如果三个点有两个距离相同且都是特殊数或者三个距离都相同且都是特殊数都会被多算,我们来讨论一下这种情况如何处理:
先考虑两个距离相等,考虑两个矩形的交矩形。这个矩形内部所有点都被排除再以上两种情况之外,而边上的所有点(除了我们枚举的两个点)都是有两个距离相等的,需要特殊处理。
而三个距离都相等的我们可以断言第三个点恰好在两个矩形的交点上,,可以直接判断。
注意最后算的时候各种重复的情况要捋清楚。
还有就是点坐标的范围可能会到 \(7m\),所以数组要开够。
代码有非常多的细节。
点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 8005;
const int M = 30005;
//质数表
bool f[M * 2] = {false};
vector<int> good_num;
bool isGood[M * 2] = {false};
void init() {
memset(f, true, sizeof f);
f[0] = f[1] = false;
for (int i = 2; i <= 60000; i++)
for (int j = i + i; j <= 60000; j += i)
f[j] = false;
for (int i = 1; i <= 60000; i++)
if (f[i] && (i % 20 == 3 || i <= 10))
good_num.push_back(i), isGood[i] = true;
}
int n, m;
struct Pnt {
int x, y;
Pnt (int _x = 0, int _y = 0) :
x(_x), y(_y) {}
} p[N];
bool operator<(Pnt a, Pnt b) {
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
int val[N] = {0};
int dis1(Pnt a, Pnt b) {
return abs(a.x - b.x) + abs(a.y - b.y);
}
int dis2(Pnt a, Pnt b) {
return max(abs(a.x - b.x), abs(a.y - b.y));
}
Pnt dec(Pnt a) {
return Pnt(a.x + a.y + m * 3, a.x - a.y + m * 3);
}
struct Square {
int lx, rx;
int ly, ry;
Square (int _lx = 0, int _rx = 0, int _ly = 0, int _ry = 0) :
lx(_lx), rx(_rx), ly(_ly), ry(_ry) {}
};
vector<Square> sq;
Square build_up(Pnt a, int r) {
return Square(a.x - r, a.x + r, a.y - r, a.y + r);
}
Square intersect(Square a, Square b) {
return Square(max(a.lx, b.lx), min(a.rx, b.rx), max(a.ly, b.ly), min(a.ry, b.ry));
}
Square minus_one(Square a) {
return Square(a.lx + 1, a.rx - 1, a.ly + 1, a.ry - 1);
}
bool OnSquare(Square a, Pnt b) {
return ((b.x == a.lx || b.x == a.rx) && (a.ly <= b.y && b.y <= a.ry))
|| ((b.y == a.ly || b.y == a.ry) && (a.lx <= b.x && b.x <= a.rx));
}
bool chk(Square a) {
return a.lx <= a.rx && a.ly <= a.ry;
}
struct Node {
int x, y, z;
Node (int _x = 0, int _y = 0, int _z = 0) :
x(_x), y(_y), z(_z) {}
};
vector<Node> idx;
struct BIT {
vector<long long> t;
BIT () {}
BIT (int x) {
t = vector<long long>(x + 1, 0ll);
}
int lowbit(int x) {
return x & -x;
}
void mdf(int x, int v) {
while (x <= m * 7)
t[x] += v, x += lowbit(x);
}
long long pfx(int x) {
long long ans = 0ll;
while (x > 0)
ans += t[x], x -= lowbit(x);
return ans;
}
long long qry(int l, int r) {
return pfx(r) - pfx(l - 1);
}
} t1, t2;
struct Seg {
int l, r, f, id;
Seg (int _l = 0, int _r = 0, int _f = 0, int _id = 0) :
l(_l), r(_r), f(_f), id(_id) {}
};
vector<Seg> seg[M * 7];
vector<int> pnt[M * 7];
vector<long long> cnt;
vector<long long> sum;
void cal() {//对所有矩形扫描线,求出其包含的点和点权和,单点加和区间求和,可以用树状数组
for (int i = 0; i <= m * 7; i++)
seg[i].clear(), pnt[i].clear();
cnt = vector<long long>((int)sq.size(), 0ll);
sum = vector<long long>((int)sq.size(), 0ll);
for (int i = 0; i < (int)sq.size(); i++)
if (chk(sq[i])) {
seg[sq[i].lx - 1].push_back(Seg(sq[i].ly, sq[i].ry, -1, i));
seg[sq[i].rx].push_back(Seg(sq[i].ly, sq[i].ry, 1, i));
}
for (int i = 1; i <= n; i++)
pnt[p[i].x].push_back(i);
t1 = BIT(m * 7), t2 = BIT(m * 7);
for (int i = 1; i <= m * 7; i++) {
for (auto j: pnt[i]) {
t1.mdf(p[j].y, 1);
t2.mdf(p[j].y, val[j]);
}
for (auto j: seg[i]) {
cnt[j.id] += j.f * t1.qry(j.l, j.r);
sum[j.id] += j.f * t2.qry(j.l, j.r);
}
}
}
map<Pnt, int> mp;
long long num[M] = {0};
long long num_cnt[M] = {0};
long long upd() {
long long tot = 0ll;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
int d = dis2(p[i], p[j]);
if (isGood[d]) {
tot += num[d] + val[j] * num_cnt[d] + num_cnt[d] * val[i];
num[d] += val[j];
num_cnt[d]++;
}
}
for (int j = 1; j <= n; j++) {
int d = dis2(p[i], p[j]);
if (isGood[d]) {
num[d] -= val[j];
num_cnt[d]--;
}
}
}
return tot;
}
void slv() {
cin >> n >> m;
mp.clear();
for (int i = 1; i <= n; i++)
cin >> p[i].x >> p[i].y, p[i] = dec(p[i]);
for (int i = 1; i <= n; i++)
cin >> val[i], mp[p[i]] = i;
sq.clear();
idx.clear();
long long ans3 = 0ll;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
int d = dis2(p[i], p[j]);
if (isGood[d]) {
//尝试建立矩形
Square A = build_up(p[i], d);
Square B = build_up(p[j], d);
Square C = intersect(A, B);
Square D = minus_one(C);
idx.push_back(Node(i, j, (int)sq.size()));
sq.push_back(A), sq.push_back(B), sq.push_back(C), sq.push_back(D);
//特判交点上
if (chk(C)) {
Pnt a = Pnt(C.lx, C.ly);
Pnt b = Pnt(C.lx, C.ry);
Pnt c = Pnt(C.rx, C.ly);
Pnt dd = Pnt(C.rx, C.ry);
int x = mp[a];
if (x != 0 && x != i && x != j && OnSquare(A, a) && OnSquare(B, a))
ans3 += val[i] + val[j] + val[x];
x = mp[b];
if (x != 0 && x != i && x != j && OnSquare(A, b) && OnSquare(B, b))
ans3 += val[i] + val[j] + val[x];
x = mp[c];
if (x != 0 && x != i && x != j && OnSquare(A, c) && OnSquare(B, c))
ans3 += val[i] + val[j] + val[x];
x = mp[dd];
if (x != 0 && x != i && x != j && OnSquare(A, dd) && OnSquare(B, dd))
ans3 += val[i] + val[j] + val[x];
}
}
}
//特判两个相同,一个不同
ans3 /= 3;
long long ans2 = upd() - ans3 * 3;
//扫描线
cal();
//扫描线完成了!!!!
long long ans1 = 0ll;
for (auto i: idx) {
long long Cnt = 0ll, Sum = 0ll;
Cnt = cnt[i.z] + cnt[i.z + 1] - cnt[i.z + 2] - cnt[i.z + 3] - 2;
Sum = sum[i.z] + sum[i.z + 1] - sum[i.z + 2] - sum[i.z + 3] - (val[i.x] + val[i.y]);
ans1 += Cnt * (val[i.x] + val[i.y]) + Sum;
}
cout << ans1 - ans2 - 2 * ans3 << endl;
}
int main() {
init();
int T;
cin >> T;
while (T--)
slv();
return 0;
}