【Good Bye 2020 F】Euclid's nightmare
题目链接
翻译
给你 \(n\) 个向量, 每个向量都是 \(m\) 维的,在他们之间你可以做任意次数的模 \(2\) 加法,用 \(T\) 表示你能用这些向量得到的向量, 然后
问你用 \(n\) 个向量中最少多少个向量就已经足够表示出 \(T\) 了,设这个最少用的向量集合为 \(S\),
让你求出\(T\) 的大小, 以及 \(S\) 的大小, 和 \(S\) 的各个向量下标,多个答案,要求 \(S\) 的字典序也最小。
给的 \(n\) 个向量中,只会有两个或者一个 \(1\),其余都是 \(0\)
题解
把每个维度都看出一个顶点,那么所给的 \(n\) 个向量中,为 \(1\) 的两个维度就连一条边。
显然就可以形成一个图了。
如果某个向量中只有一个 \(1\),那么就增加一个维度,让那个向量变成 \(m+1\) 维的,然后第 \(m+1\) 维是 \(1\)。
这样做有啥效果呢?
会发现图中你任意走一条路径的话(路上的节点所在维上的 \(1\) 进行累加),最终的结果就是让路径头尾两个节点 \(x\) 和 \(y\) (中间节点因为偶数次累加都是 \(0\))
对应的维度上的数字变成 \(1\),那么此时如果有一条边 \(x,y\) 直接连接 \(x\) 和 \(y\), 也就对应了有一个向量在第 \(x\) 和第 \(y\)
维上都是 \(1\),那么形成了一个环,可以发现这条边 \((x,y)\) 对应的向量是不需要的,冗余的。因为直接沿着路径累加就能替代
这个向量了。
所以思路就是,让形成的图没有环, 可能有多个联通分量,所以最后所求的是一个森林。再进一步,字典序要求最小。
把边上标上向量的下标作为权重。
那么,最后要求的 \(S\) 就是一个 \(MST\) 了。
最后,能表示的向量个数 |T| 这样求:设这个 \(MST\) 的边的个数为 \(cnt\), 答案就为 \(2^{cnt}\), 每个向量用或者不用,因为是最小的表示
了,所以 \(S\) 中的向量只要增减,都能对应一个独一无二的向量。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 5e5;
const int MOD = 1e9 + 7;
int n, m;
int f[N + 10];
int ff(int x) {
if (f[x] == x) {
return x;
}
else {
return f[x] = ff(f[x]);
}
}
vector<int> ans;
int main() {
#ifdef LOCAL_DEFINE
freopen("in.txt", "r", stdin);
#endif
ios::sync_with_stdio(0), cin.tie(0);
int T;
T = 1;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= m + 1; i++) {
f[i] = i;
}
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
int x, y = m + 1;
cin >> x;
if (k == 2) {
cin >> y;
}
int r1 = ff(x), r2 = ff(y);
if (r1 != r2) {
f[r1] = r2;
ans.push_back(i);
}
}
int cnt = 1;
for (int i = 0; i < (int)ans.size(); i++) {
cnt = (cnt + cnt) % MOD;
}
cout << cnt << " " << (int)ans.size() << endl;
for (int x : ans) {
cout << x << " ";
}
cout << endl;
}
return 0;
}