CCF-CSP认证考试 202309-4 阴阳龙 100分题解

本文围绕 CSP 202309 - 4 阴阳龙题目展开,介绍了问题描述、输入输出格式、样例等内容。题中遗迹为网格,员工在其中,阴阳龙现身会使员工位置变化。题解提出分组确定 k 值,根据公式旋转点,时间复杂度为 O((p + q)logp),还给出参考代码及相关说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更多 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={kN+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={kN+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=K1K2=,则令 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

fig1.png
变化前各员工位置

如果 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,则变化后各员工所在位置如下图所示。

fig2.png
变化后各员工位置

在全部员工进入遗迹群后,西西艾弗遗迹探索有限公司总共探测到 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=1pi×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 1n,m109,1p,q1×105,1xi,un,1yi,vm,1ti7

保证所有员工初始所在的位置两两不同。


题解

对于每次阴阳龙时,需要快速得确定 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 xy

将每个点存 4 4 4 次,分别存到对应值的一个 set 中,对应值可以使用 mapunordered_map 的键值来表示(不能直接开数组,太大了)。

每次现身时可以通过 lower_boundupper_bound 快速求出按照 8 8 8 个方向前进时可能遇到的最近的点(可能不存在),然后和边界一起考虑得出这次现身的 k k k 值,并找出影响到的点,根据题目要求进行旋转即可。找到点后,要从 4 4 4set 中把对应的点删掉,然后将修改后的点重新加入到 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;
}
/*
     _____   _   _       _  __    __
    |  _  \ | | | |     | | \ \  / /
    | |_| | | | | |     | |  \ \/ /
    |  ___/ | | | |  _  | |   }  {
    | |     | |_| | | |_| |  / /\ \
    |_|     \_____/ \_____/ /_/  \_\
*/

关于代码的亿点点说明:

  1. 代码的主体部分位于 void work() 函数中,另外会有部分变量申明、结构体定义、函数定义在上方。
  2. #pragma ... 是用来开启 O2、O3 等优化加快代码速度。
  3. 中间一大堆 #define ... 是我习惯上的一些宏定义,用来加快代码编写的速度。
  4. "debug.h" 头文件是我用于调试输出的代码,没有这个头文件也可以正常运行(前提是没定义 DEBUG 宏),在程序中如果看到 dbg(...) 是我中途调试的输出的语句,可能没删干净,但是没有提交上去没有任何影响。
  5. ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); 这三句话是用于解除流同步,加快输入 cin 输出 cout 速度(这个输入输出流的速度很慢)。在小数据量无所谓,但是在比较大的读入时建议加这句话,避免读入输出超时。如果记不下来可以换用 scanfprintf,但使用了这句话后,cinscanfcoutprintf 不能混用。
  6. main 函数和 work 函数分开写纯属个人习惯,主要是为了多组数据。
### MyBatis Plus 源码详解 #### 1. MyBatis Plus 的核心概念 MyBatis Plus 是基于 MyBatis 进行二次封装的一个简化操作数据库的增强工具[^2]。它不仅保留了 MyBatis 原有的灵活性,还提供了更多便捷的功能来提高开发效率。 #### 2. 主要功能模块解析 ##### 2.1 `SqlParser` 解析器 为了支持更多的 SQL 特性和优化查询性能,MyBatis Plus 提供了一个强大的 SQL 解析机制。通过自定义 SQL 注解和内置模板引擎的支持,开发者能够更加灵活地编写复杂的业务逻辑所需的 SQL 语句[^1]。 ##### 2.2 `MetaObject` 元对象处理 继承并扩展了 MyBatis 中用于管理实体类属性映射的核心组件——`MetaObject`,使得在不侵入原有代码结构的情况下实现了对 JavaBean 属性更细粒度的操作控制。 ```java // MetaObject 使用示例 public class User { private Long id; private String name; public static void main(String[] args) { User user = new User(); MetaObject metaObject = SystemMetaObject.forObject(user); metaObject.setValue("name", "test"); System.out.println(metaObject.getValue("name")); } } ``` ##### 2.3 表名与字段自动识别 借助于注解方式实现表名以及列名到实体类成员变量之间的双向绑定关系维护工作自动化。例如,在实体类上添加 `@TableName` 来指定对应的数据库表格名称[^3]: ```java @TableId(type = IdType.AUTO) private Integer id; // 自增主键 @ApiModelProperty(value = "批次详情ID") @Schema(description = "批次详情ID") @TableField(fill = FieldFill.INSERT_UPDATE) private Long batchDetailId; ``` #### 3. 插件体系架构设计 插件化的设计理念贯穿整个框架之中,允许第三方开发者轻松集成各种实用特性而无需修改内核代码。比如页插件、乐观锁插件等都是利用这一特点构建而成。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值