P10315 解题报告
题目传送门
题目大意:
有 \(n\) 个石碑,每个石碑有 \(0\sim m - 1\) 共 \(m\) 种状态,击打一个石碑会带动其他的石碑。若当前石碑的状态是 \(s\),则击打或被带动后的状态为 \((s + 1)\bmod m\)。
现给定这 \(n\) 个石碑的初始状态 \(s_i\)、每个石碑带动的石碑及末状态 \(t_i\),求每个石碑至少被击打几次。
思路:
首先把题面意思抽象出来,令 \(a_{i, j}\) 表示第 \(i\) 个石碑和第 \(j\) 个石碑的联系,若 \(a_{i, j} = 1\),则表示击打 \(j\) 会带动 \(i\),若 \(a_{i, j} = 0\) 表示无影响,特别的,因为击打自己就相当于带动自己,所以 \(a_{i, i} = 1\)。
再令 \(x_i\) 表示第 \(i\) 个石碑被击打的次数。
那么题面就变为了:
再移个项,得:
求出每个 \(x_i\) 的最小非负整数解即可。
同余只是纸老虎!直接转换成等号,高斯消元求解即可,只是需要把解映射到 \([0, m - 1]\)。
同时这道题还需要在无穷多组解时输出任意一组解,需要在消元时额外注意(其实应该只有我这种写法应该注意),不能直接回代求解。
当找到一个主行 \(r\) 时,不要只从 \(r + 1\) 消到 \(n\),而应该把 \(1\sim n\) 都消一遍,此时除每行的首变量(每个行向量中第一个系数非零的未知数)之外其他的都是自由元,直接将首变量赋值,自由元赋成 \(0\) 不管就行了。
这是我原来的高斯消元代码:
int gauss() {
int c, r;
for(c = 0, r = 0; c < n; c++) {
int t = r;
for(int i = r + 1; i < n; i++)
if(abs(a[i][c]) > abs(a[t][c]))
t = i;
if(!a[t][c]) continue;
if(t != r) for(int i = c; i <= n; i++) swap(a[t][i], a[r][i]);
for(int i = n; i >= c; i--) a[r][i] = (a[r][i] * (qpow(a[r][c], mod - 2) + mod) % mod) % mod;
for(int i = r + 1; i < n; i++) //原来是消第 r + 1 到 n 行
if(a[i][c])
for(int j = n; j >= c; j--)
a[i][j] = (mod + a[i][j] - a[i][c] * a[r][j] % mod) % mod;
++r;
}
if(r < n) {
for(int i = r; i < n; i++)
if(a[i][n] > 0)
return -1;
}
for(int i = n - 2; ~i; i--)
for(int j = i + 1; j < n; j++)
a[i][n] = (mod + a[i][n] - a[i][j] * a[j][n] % mod) % mod;
return 1;
}
它在这组数据时会出错:
3 3
2 2 3
2 1 3
1 1
0 0 0
2 1 2
Answer:
0 1 1
或:
1 0 1
My answer:
1 1 0
原矩阵:
这是因为用以上代码消出来的结果为:
而如果直接回代就会直接将 \(x_3\) 钦定为 \(0\),这是不对的,因为 \(x_2\) 才是自由元,而 \(x_3\) 有固定的解 \(1\)。
而采用全部重消一遍的方法就能保证所有首变量都只会在一个行向量中出现,这时候回代就完全不用考虑和其他首变量取值出现冲突的问题。
\(\texttt{Code:}\)
#include <cmath>
#include <iostream>
using namespace std;
const int N = 110;
typedef long long ll;
int n, mod;
ll a[N][N];
ll ans[N];
ll qpow(ll a, int b) {
ll ans = 1, base = a % mod;
while(b) {
if(b & 1) ans = ans * base % mod;
base = base * base % mod;
b >>= 1;
}
return ans;
}
void output() {
puts("---------");
for(int i = 0; i < n; i++) {
for(int j = 0; j <= n; j++)
printf("%d ", a[i][j]);
puts("");
}
puts("---------");
}
int gauss() {
int c, r;
for(c = 0, r = 0; c < n; c++) {
int t = r;
for(int i = r + 1; i < n; i++)
if(abs(a[i][c]) > abs(a[t][c]))
t = i;
if(!a[t][c]) continue;
if(t != r) for(int i = c; i <= n; i++) swap(a[t][i], a[r][i]);
for(int i = n; i >= c; i--) a[r][i] = (a[r][i] * (qpow(a[r][c], mod - 2) + mod) % mod) % mod;
for(int i = 0; i < n; i++) //全部重消一遍
if(a[i][c] && i != r)
for(int j = n; j >= c; j--)
a[i][j] = (mod + a[i][j] - a[i][c] * a[r][j] % mod) % mod; //注意取模时要加上模数以防负数
++r;
}
if(r < n) {
for(int i = r; i < n; i++)
if(a[i][n] > 0)
return -1;
//
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++)
if(a[i][j]) { //寻找首变量
ans[j] = a[i][n]; //直接将首变量赋成方程右侧的值,其他变量都是自由元,直接赋成 0
break;
}
}
return 0;
}
for(int i = n - 2; ~i; i--)
for(int j = i + 1; j < n; j++)
a[i][n] = (mod + a[i][n] - a[i][j] * a[j][n] % mod) % mod;
for(int i = 0; i < n; i++)
ans[i] = a[i][n];
return 1;
}
int main() {
scanf("%d%d", &n, &mod);
ll x;
for(int i = 0; i < n; i++) {
int cnt;
scanf("%d", &cnt);
while(cnt--) {
scanf("%lld", &x);
a[x - 1][i] = 1;
}
a[i][i] = 1;
}
for(int i = 0; i < n; i++)
scanf("%lld", &a[i][n]);
for(int i = 0; i < n; i++) {
scanf("%lld", &x);
a[i][n] = x - a[i][n];
}
int type = gauss();
if(type >= 0)
for(int i = 0; i < n; i++)
printf("%lld ", ans[i]);
else puts("niuza");
return 0;
}