更多 CSP 认证考试题目题解可以前往:CSP-CCF 认证考试真题题解
原题链接: 202309-4 阴阳龙
时间限制: 2.0s
内存限制: 1.0GB
问题描述
西西艾弗岛的下方是一个庞大的遗迹群,神兽“阴阳龙”栖居在这个遗迹群中。
为了得到这件宝物,西西艾弗遗迹探索有限公司(以下简称“公司”)派遣了 p p p 名员工前往遗迹群,这些员工依次编号为 1 1 1 到 p p p。
遗迹可以视为一个大小为 n × m n\times m n×m 的网格,左下角坐标 ( 1 , 1 ) (1,1) (1,1),右上角坐标 ( n , m ) (n,m) (n,m)。初始时,第 i i i 名员工所在的位置是 ( x i , y i ) (x_i,y_i) (xi,yi)。保证所有员工初始所在的位置两两不同。
作为神兽,阴阳龙有着特殊之处。当其在 p = ( u , v ) \mathbf p=(u,v) p=(u,v) 位置以强度 t ∈ [ 1 , 7 ] t\in[1,7] t∈[1,7] 现身时,会导致遗迹群的环境发生阴和阳的变转,从而导致在遗迹中的人的位置发生变化。
具体来说,阴阳龙首先观察右、右上、上、左上、左、左下、下和右下这八个方向,并在这八个方向找到和阴阳龙“距离”最近的员工(不包括
p
\mathbf p
p)的“距离”。
其中,垂直和水平方向的“距离”是指员工和阴阳龙连线的长度;斜线方向的“距离”是指员工和阴阳龙连线在水平方向上投影的长度。设想从阴阳龙的位置同时出发,
分别向这 8 个方向前进,每一单位时间运动 1 个“距离”。如果在某一时刻,在某一方向刚好遇到一位员工,则此时前进的距离即被记为
k
k
k;否则,如果在某一时刻,
在某一方向上刚好到达遗迹的边界,但是在此之前任何方向上都没有遇到员工,则令
k
=
0
k=0
k=0。形式化描述上述确定
k
k
k 的方法是:
记 d 0 \mathbf d_0 d0 到 d 7 \mathbf d_7 d7 依次为向量 ( 1 , 0 ) , ( 1 , 1 ) , ( 0 , 1 ) , ( − 1 , 1 ) , ( − 1 , 0 ) , ( − 1 , − 1 ) , ( 0 , − 1 ) , ( 1 , − 1 ) (1,0),(1,1),(0,1),(-1,1),(-1,0),(-1,-1),(0,-1),(1,-1) (1,0),(1,1),(0,1),(−1,1),(−1,0),(−1,−1),(0,−1),(1,−1),令: K 1 = { k ∈ N + ∣ ∃ i ∈ [ 0 , 7 ] , j ∈ [ 1 , p ] , s.t. ( x j , y j ) = p + k d i } K_1=\left\{k\in\mathbb N^+ \mid \exists i\in[0,7],j\in[1,p],\text{s.t.}(x_j,y_j)=\mathbf p+k\mathbf d_i\right\} K1={k∈N+∣∃i∈[0,7],j∈[1,p],s.t.(xj,yj)=p+kdi} K 2 = { k ∈ N + ∣ ∀ i ∈ [ 0 , 7 ] , ( p + k d i ) ∈ [ 1 , n ] × [ 1 , m ] } K_2=\left\{k\in\mathbb N^+\mid\forall i\in[0,7],\left(\mathbf p+k\mathbf d_i\right)\in[1,n]\times[1,m]\right\} K2={k∈N+∣∀i∈[0,7],(p+kdi)∈[1,n]×[1,m]}
其中:
- ( x i , y i ) (x_i,y_i) (xi,yi) 为第 i i i 名员工在此次阴阳龙现身前的位置(这个位置可能和其初始位置不同,但为了方便起见,我们使用同一个记号);
- K 1 K_1 K1 为所有员工到阴阳龙距离组成的集合;
- K 2 K_2 K2 为从阴阳龙出发直至在某一方向抵达边界所包括全部的距离组成的集合。
若 K = K 1 ⋂ K 2 = ∅ K=K_1\bigcap K_2=\emptyset K=K1⋂K2=∅,则令 k = 0 k=0 k=0;否则令 k = min K > 0 k=\min K>0 k=minK>0。
例如,参考下图中的例子,其中左下角为 ( 1 , 1 ) (1,1) (1,1),右上角为 ( 7 , 7 ) (7,7) (7,7),共有 8 8 8 名员工,位置如图。
若 p = ( 4 , 4 ) \mathbf p=(4,4) p=(4,4),那么员工 1 刚好在阴阳龙所在位置,不计入;员工 3 不在阴阳龙的 8 个方向上,不计入;员工 2、4、5、6 与阴阳龙“距离”是 2;员工 7、8、9 与阴阳龙“距离”是 3,因此有 K 1 = 2 , 3 K_1 = {2, 3} K1=2,3。由于与阴阳龙“距离”为 3 就到达了遗迹的边界,所以有 K 2 = 1 , 2 , 3 K_2 = {1, 2, 3} K2=1,2,3。因此 k = 2 k=2 k=2。
若 p = ( 2 , 2 ) \mathbf p=(2,2) p=(2,2),那么员工 2、3、7、8、9都不在阴阳龙的 8 个方向上,不计入;员工 1、6 与阴阳龙的“距离”是 2;员工 4、5 与阴阳龙的“距离”是 4,因此有 K 1 = 2 , 4 K_1 = {2,4} K1=2,4。由于与阴阳龙“距离”为 1 时,就在向下、向左、向左下三个方向上到达了遗迹的边界,所以有 K 2 = 1 K_2 = {1} K2=1。因此 k = 0 k=0 k=0。
变化前各员工位置
如果 k > 0 k > 0 k>0,则将八个方向上的距离为 k k k 的位置上的员工以 p \mathbf p p 为中心逆时针旋转 t t t 倍的八分之一个圆周的角度。形式化地:
- 若 k = 0 k=0 k=0,则什么也不会发生。
- 否则, ∀ i ∈ [ 0 , 7 ] \forall i\in[0,7] ∀i∈[0,7],若 p + k d i \mathbf p+k\mathbf d_i p+kdi 位置上有员工,那么其该员工会被移动到 p + k d ( i + t ) m o d 8 \mathbf p+k\mathbf d_{(i+t)\bmod 8} p+kd(i+t)mod8。
易知在所有员工移动结束后,每个位置上仍至多有一个员工。例如,在上图所示的例子中取 p = ( 4 , 4 ) , t = 1 \mathbf p=(4,4),t=1 p=(4,4),t=1,则变化后各员工所在位置如下图所示。
变化后各员工位置
在全部员工进入遗迹群后,西西艾弗遗迹探索有限公司总共探测到 q q q 次阴阳龙的现身。很不幸的是,由于来自东方神秘力量的干扰,这 q q q 次阴阳龙的现身后,西西艾弗遗迹探索有限公司失去了所有员工的位置信息,因此他希望你帮他计算出所有员工的位置。
输入格式
从标准输入读入数据。
第一行四个正整数 n , m , p , q n,m,p,q n,m,p,q;
接下来 p p p 行,第 i i i 行两个正整数 ( x i , y i ) (x_i,y_i) (xi,yi) 表示第 i i i 名员工的初始位置。
保证所有员工初始所在的位置两两不同。
接下来 q q q 行,第 i i i 行三个正整数 u i , v i , t i u_i,v_i,t_i ui,vi,ti 表示西西艾弗遗迹探索有限公司探测到的第 i i i 次阴阳龙现身的位置和强度。
输出格式
输出到标准输出中。
为了减少输出量,设 q q q 次阴阳龙的现身后所有员工的位置为 ( x 1 , y 1 ) , … , ( x p , y p ) (x_1,y_1),\dots,(x_p,y_p) (x1,y1),…,(xp,yp),则你只需要输出: ⨁ i = 1 p i × x i + y i \bigoplus_{i=1}^p i\times x_i+y_i i=1⨁pi×xi+yi其中 ⨁ \bigoplus ⨁ 表示按位异或,即 C/C++ 中的 ^ 运算符。
样例输入
3 3 9 1
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
2 2 1
样例输出
20
样例说明
阴阳龙现身前,每个员工所在的位置如下:
3 6 9
2 5 8
1 4 7
阴阳龙现身一次后,每个员工所在位置如下:
6 9 8
3 5 7
2 1 4
评测用例规模与约定
子任务编号 | n ≤ n\le n≤ | m ≤ m\le m≤ | p ≤ p\le p≤ | q ≤ q\le q≤ | 子任务分值 |
---|---|---|---|---|---|
1 | 1000 1000 1000 | 1000 1000 1000 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 40 40 40 |
2 | 1 0 9 10^9 109 | 1 0 9 10^9 109 | 1000 1000 1000 | 1000 1000 1000 | 15 15 15 |
3 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 25 25 25 |
4 | 1 0 9 10^9 109 | 1 0 9 10^9 109 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 20 20 20 |
对于全部数据:
1 ≤ n , m ≤ 1 0 9 , 1 ≤ p , q ≤ 1 × 1 0 5 , 1 ≤ x i , u ≤ n , 1 ≤ y i , v ≤ m , 1 ≤ t i ≤ 7 1\le n,m\le 10^9,1\le p,q\le 1\times 10^5,1\le x_i,u\le n,1\le y_i,v\le m, 1 \le t_i \le 7 1≤n,m≤109,1≤p,q≤1×105,1≤xi,u≤n,1≤yi,v≤m,1≤ti≤7。
保证所有员工初始所在的位置两两不同。
题解
对于每次阴阳龙时,需要快速得确定 k k k 值。对于选中的一个点,向 8 8 8 个方向同时前进,可以分为 4 4 4 组:水平、竖直、主对角线方向、副对角线方向,分组后有可能涉及到的坐标之间有一定的关系:
- 水平方向的那一组具有相同的 y y y 值
- 竖直方向的那一组具有相同的 x x x 值
- 主对角线方向的那一组具有相同的 x + y x+y x+y 值
- 副对角线发现的那一组具有相同的 x − y x-y x−y 值
将每个点存
4
4
4 次,分别存到对应值的一个 set
中,对应值可以使用 map
、unordered_map
的键值来表示(不能直接开数组,太大了)。
每次现身时可以通过 lower_bound
和 upper_bound
快速求出按照
8
8
8 个方向前进时可能遇到的最近的点(可能不存在),然后和边界一起考虑得出这次现身的
k
k
k 值,并找出影响到的点,根据题目要求进行旋转即可。找到点后,要从
4
4
4 个 set
中把对应的点删掉,然后将修改后的点重新加入到 set
中去。旋转时可以根据题目中的公式
p
+
k
d
(
i
+
t
)
m
o
d
8
\mathbf p+k\mathbf d_{(i+t)\bmod 8}
p+kd(i+t)mod8 先确定
i
i
i,然后直接求出旋转后的结果即可。
最后再求异或的时候,别忘了开 long long
。
时间复杂度: O ( ( p + q ) log p ) \mathcal{O}((p+q)\log p) O((p+q)logp)。
参考代码(1.0s,77.95MB)
/*
Created by Pujx on 2024/3/7.
*/
#pragma GCC optimize(2, 3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
//#define double long double
using i64 = long long;
using ui64 = unsigned long long;
using i128 = __int128;
#define inf (int)0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define yn(x) cout << (x ? "yes" : "no") << endl
#define Yn(x) cout << (x ? "Yes" : "No") << endl
#define YN(x) cout << (x ? "YES" : "NO") << endl
#define mem(x, i) memset(x, i, sizeof(x))
#define cinarr(a, n) for (int i = 1; i <= n; i++) cin >> a[i]
#define cinstl(a) for (auto& x : a) cin >> x;
#define coutarr(a, n) for (int i = 1; i <= n; i++) cout << a[i] << " \n"[i == n]
#define coutstl(a) for (const auto& x : a) cout << x << ' '; cout << endl
#define all(x) (x).begin(), (x).end()
#define md(x) (((x) % mod + mod) % mod)
#define ls (s << 1)
#define rs (s << 1 | 1)
#define ft first
#define se second
#define pii pair<int, int>
#ifdef DEBUG
#include "debug.h"
#else
#define dbg(...) void(0)
#endif
const int N = 2e5 + 5;
//const int M = 1e5 + 5;
const int mod = 998244353;
//const int mod = 1e9 + 7;
//template <typename T> T ksm(T a, i64 b) { T ans = 1; for (; b; a = 1ll * a * a, b >>= 1) if (b & 1) ans = 1ll * ans * a; return ans; }
//template <typename T> T ksm(T a, i64 b, T m = mod) { T ans = 1; for (; b; a = 1ll * a * a % m, b >>= 1) if (b & 1) ans = 1ll * ans * a % m; return ans; }
int a[N];
int n, m, t, k, p, q;
pii pt[N], cur;
unordered_map<int, set<pii>> heng, shu, zhu, fu;
int dx[8] = {1, 1, 0, -1, -1, -1, 0, 1};
int dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
void work() {
auto insert = [&] (int id) {
int x = pt[id].ft, y = pt[id].se;
heng[y].insert({x, id});
shu[x].insert({y, id});
zhu[x + y].insert({x, id});
fu[x - y].insert({x, id});
};
auto erase = [&] (int id) {
int x = pt[id].ft, y = pt[id].se;
heng[y].erase({x, id});
shu[x].erase({y, id});
zhu[x + y].erase({x, id});
fu[x - y].erase({x, id});
};
auto dis = [&] (int id) {
return max(abs(pt[id].ft - cur.ft), abs(pt[id].se - cur.se));
};
cin >> n >> m >> p >> q;
for (int i = 1; i <= p; i++) cin >> pt[i].ft >> pt[i].se, insert(i);
while (q--) {
cin >> cur.ft >> cur.se >> t;
vector<int> d(8, 0);
auto it = heng[cur.se].upper_bound({cur.ft, inf}); d[0] = it != heng[cur.se].end() ? it->second : 0;
it = fu[cur.ft - cur.se].upper_bound({cur.ft, inf}); d[1] = it != fu[cur.ft - cur.se].end() ? it->second : 0;
it = shu[cur.ft].upper_bound({cur.se, inf}); d[2] = it != shu[cur.ft].end() ? it->second : 0;
it = zhu[cur.ft + cur.se].lower_bound({cur.ft, 0}); d[3] = it != zhu[cur.ft + cur.se].begin() ? (--it)->second : 0;
it = heng[cur.se].lower_bound({cur.ft, 0}); d[4] = it != heng[cur.se].begin() ? (--it)->second : 0;
it = fu[cur.ft - cur.se].lower_bound({cur.ft, 0}); d[5] = it != fu[cur.ft - cur.se].begin() ? (--it)->second : 0;
it = shu[cur.ft].lower_bound({cur.se, 0}); d[6] = it != shu[cur.ft].begin() ? (--it)->second : 0;
it = zhu[cur.ft + cur.se].upper_bound({cur.ft, inf}); d[7] = it != zhu[cur.ft + cur.se].end() ? it->second : 0;
k = min({cur.ft - 1, cur.se - 1, n - cur.ft, m - cur.se});
for (auto x : d) if (x) k = min(k, dis(x));
for (auto& x : d) if (x && dis(x) > k) x = 0;
for (auto x : d) if (x) erase(x);
for (int i = 0; i < 8; i++) {
if (!d[i]) continue;
pt[d[i]].ft = cur.ft + k * dx[(i + t) % 8];
pt[d[i]].se = cur.se + k * dy[(i + t) % 8];
insert(d[i]);
}
}
i64 ans = 0;
for (int i = 1; i <= p; i++) ans ^= 1ll * i * pt[i].ft + pt[i].se;
cout << ans << endl;
}
signed main() {
#ifdef LOCAL
freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.in", "r", stdin);
freopen("C:\\Users\\admin\\CLionProjects\\Practice\\data.out", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int Case = 1;
//cin >> Case;
while (Case--) work();
return 0;
}
/*
_____ _ _ _ __ __
| _ \ | | | | | | \ \ / /
| |_| | | | | | | | \ \/ /
| ___/ | | | | _ | | } {
| | | |_| | | |_| | / /\ \
|_| \_____/ \_____/ /_/ \_\
*/
关于代码的亿点点说明:
- 代码的主体部分位于
void work()
函数中,另外会有部分变量申明、结构体定义、函数定义在上方。#pragma ...
是用来开启 O2、O3 等优化加快代码速度。- 中间一大堆
#define ...
是我习惯上的一些宏定义,用来加快代码编写的速度。"debug.h"
头文件是我用于调试输出的代码,没有这个头文件也可以正常运行(前提是没定义DEBUG
宏),在程序中如果看到dbg(...)
是我中途调试的输出的语句,可能没删干净,但是没有提交上去没有任何影响。ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
这三句话是用于解除流同步,加快输入cin
输出cout
速度(这个输入输出流的速度很慢)。在小数据量无所谓,但是在比较大的读入时建议加这句话,避免读入输出超时。如果记不下来可以换用scanf
和printf
,但使用了这句话后,cin
和scanf
、cout
和printf
不能混用。- 将
main
函数和work
函数分开写纯属个人习惯,主要是为了多组数据。