Spices(线性基、贪心)
题意
有\(2^n-1\)个数字,分别是\(1,2,\dots,2^n-1\)。它们具有权值,分别为\(c_1,c_2,\dots,c_{2^n-1}\)。
从这些数字中选择一些数字组成集合\(S\),对于\(1,2,\dots,2^n-1\)中任意一个数字\(i\),都可以从\(S\)中找到一个子集,使得子集里面所有数异或起来为\(i\)。
问:集合\(S\)中所有数权值之和的最小值为多少?
数据范围
\(2 \leq n \leq 16\)
思路
线性基模板题。
首先不考虑权值的问题,只考虑集合\(S\)如何得到。这个问题为线性基的基本应用,只需要在插入的过程中,记录当前元素是否使用了即可。
现在考虑权值,只需要在上面的基础上,对权值进行排序即可(因为在\(b\)数组的构造过程中,肯定是优先选择权值小的数)。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
const int N = 20;
int n;
pii a[1 << N];
ll b[N];
ll ans;
void ins(int x, ll val) //插入数字,获取线性基
{
for(int i = 15; i >= 0; i --) {
if(x >> i & 1) {
if(!b[i]) {
b[i] = x;
ans += val;
return;
}
else x ^= b[i];
}
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i < (1 << n); i ++) {
ll x;
scanf("%lld", &x);
a[i] = {x, i};
}
sort(a + 1, a + (1 << n));
for(int i = 1; i < (1 << n); i ++) {
ins(a[i].second, a[i].first);
}
printf("%lld\n", ans);
return 0;
}