1058 字
5 分钟

P10315 [SHUPC 2024] - 原神,启动! 题解

2024-09-18
2024-09-18
浏览量 加载中...
Done

您已获得最佳的阅读体验!

题目原型谜题解法浅究m=4m=4 的情况)

题目地址

本题考察模意义下的高斯消元。

基本思路是,让 1n{1\sim n} 号方块分别击打 x1xnx_1\sim x_n 次,使得最终每个方块都朝向位置 t1tnt_1\sim t_n


欲解决此题,首先需要为每个位置编号。定义 ab,ca\rightarrow b,c 为“击打 aa 方块后,a,b,ca,b,c 均会旋转一次”。那么样例输入 #1 如下图(编号方式不唯一),红线为目标方向、初始时均在 00 朝向:

假设三个方块需要击打 x,y,zx,y,z 次,那么可以列出以下方程组:

{x+y=0y+z=2x+y+z=1\begin{cases} x+y=0 \\y+z=2 \\x+y+z=1 \end{cases}

但是不够严谨,因为我们没考虑“转过头”这种情况。即朝向为 22 的方块旋转后会变为朝向 00。因此可以发现朝向存在模 mm 意义的同余关系。

上例方程组解得:

{x=1y=1z=1\begin{cases} x=-1\\y=1\\z=1 \end{cases}

出现了负数,怎么处理?

根据解的意义考虑,这相当于 xx 往回转一次,根据同余关系可以得到,往回转 11 次就相当于向前转 m1m-1 次。因此对于负数,采取 (x % m + m) % m 的方式即可转化为非负数。


于是题目就很明了了,让我们求出如下 nn 元一次同余方程组的解:

{x1+x2+x3++xnt1s1(modm)x1+x2+x3++xnt2s2(modm)x1+x2+x3++xntnsn(modm)\begin{cases} x_1+x_2+x_3+\dots+x_n\equiv t_1-s_1\pmod m \\x_1+x_2+x_3+\dots+x_n\equiv t_2-s_2\pmod m \\\vdots \\x_1+x_2+x_3+\dots+x_n\equiv t_n-s_n\pmod m \end{cases}

因而可以用高斯消元求解。那么如何处理无数组解的情况呢?

高斯消元时我们会跳过一些零行。此时零行对应的变量就是一个自由元,可以任意赋值。

因此使用一个 ans 数组来存储答案,如果出现无数组解的情况,那么不管它,ans 数组也会自动赋值它为 00

模意义下的除法显然需要用逆元解决,考虑到 mm 为质数,因此使用费马小定理计算。注意处处取模,答案不要忘了转化为 [0,m)[0,m) 内的数,还要开 long long

#include <bits/stdc++.h>
#define N 110
#define SOLVE_OK 200
#define SOLVE_NO 404
#define LUXURIOUS_CHEST 0;
#define F return
using namespace std;
typedef long long ll;
int n, m;
ll matrix[N][N];
ll ans[N];
ll qpow(ll a, ll b, int p) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res % p;
}
ll inv(ll x, int p) {
return qpow(x, p - 2, p);
}
int gauss() {
// 1-index 高斯消元
int rank = 0;
for (int c = 1, r = 1; c <= n; c++) {
int max_row = r;
for (int i = r; i <= n; i++) {
if (abs(matrix[i][c]) > abs(matrix[max_row][c])) {
max_row = i;
}
}
if (!matrix[max_row][c]) continue;
if (max_row != r) swap(matrix[max_row], matrix[r]);
for (int i = n + 1; i >= c; i--) {
matrix[r][i] *= inv(matrix[r][c], m);
matrix[r][i] = (matrix[r][i] % m + m) % m;
}
for (int i = r + 1; i <= n; i++) {
if (abs(matrix[i][c])) {
for (int j = n + 1; j >= c; j--) {
matrix[i][j] -= (matrix[r][j] * matrix[i][c]);
matrix[i][j] = (matrix[i][j] % m + m) % m;
}
}
}
r++;
rank++;
}
if (rank < n) {
// 无解判断
for (int i = rank + 1; i <= n; i++) {
for (int j = 1; j <= n + 1; j++) {
if (abs(matrix[i][j])) return SOLVE_NO;
}
}
// 无穷解处理,无穷解算作有解
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (abs(matrix[i][j])) {
ans[j] = matrix[i][n + 1];
break;
}
}
}
return SOLVE_OK;
}
// 有唯一解,回代
for (int i = n; i >= 1; i--) {
for (int j = i + 1; j <= n; j++) {
matrix[i][n + 1] -= (matrix[i][j] * matrix[j][n + 1]);
matrix[i][n + 1] = (matrix[i][n + 1] % m + m) % m;
}
}
for (int i = 1; i <= n; i++) ans[i] = matrix[i][n + 1];
return SOLVE_OK;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) matrix[i][i] = 1; // 每个方块被击打后自己也会转一下,因此主对角线全为 1
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
for (int j = 1; j <= k; j++) {
int x;
cin >> x;
matrix[x][i] = 1;
}
}
for (int i = 1; i <= n; i++) {
ll s;
cin >> s;
matrix[i][n + 1] -= s;
}
for (int i = 1; i <= n; i++) {
ll t;
cin >> t;
matrix[i][n + 1] += t;
}
int res = gauss();
if (res == SOLVE_OK) {
for (int i = 1; i <= n; i++) cout << (ans[i] % m + m) % m << ' ';
} else cout << "niuza" << endl;
F LUXURIOUS_CHEST
}

The End\texttt{The End}

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
P10315 [SHUPC 2024] - 原神,启动! 题解
https://justpureh2o.cn/articles/10315/
作者
JustPureH2O
发布于
2024-09-18
许可协议
CC BY-NC-SA 4.0
最后更新于 2024-09-18,距今已过 516 天

部分内容可能已过时

评论区

Profile Image of the Author
JustPureH2O
穷方圆平直之情,尽规矩准绳之用
公告
JustPureH2O 的博客现已正式迁移至 Astro!原 Hexo 网站将移至 https://hexo.justpureh2o.cn/
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
100
分类
12
标签
54
总字数
368,990
运行时长
0
最后活动
0 天前

目录