Я пытаюсь найти n-th
установить в powerset. От n-th
Я имею в виду, что powerset генерируется в следующем порядке — сначала по размеру, а затем по лексикографическому признаку — и так, индексы наборов в powerset [a, b, c]
является:
0 - []
1 - [a]
2 - [b]
3 - [c]
4 - [a, b]
5 - [a, c]
6 - [b, c]
7 - [a, b, c]
При поиске решения все, что я мог найти, — это алгоритм возврата n-й перестановки списка элементов — например, Вот.
контекст:
Я пытаюсь восстановить всю мощность вектора V
элементов, но мне нужно сделать это с одним набором за раз.
Требования:
n-th
установить от powerset V
— Вот почему я хочу иметь n-th set
функционировать здесь;n-th
один;У меня нет закрытой формы для функции, но у меня есть хакерский цикл next_combination
функция, к которой вы можете обратиться, если это поможет. Предполагается, что вы можете поместить битовую маску в некоторый целочисленный тип, что, вероятно, не является необоснованным предположением, учитывая, что есть 264 возможности для набора из 64 элементов.
Как говорится в комментарии, я нахожу это определение «лексикографического упорядочения» несколько странным, поскольку я бы сказал, что лексикографическое упорядочение будет следующим: [], [a], [ab], [abc], [ac], [b], [bc], [c]
, Но раньше мне приходилось делать перечисление «сначала по размеру, потом по лексикографии».
// Generate bitmaps representing all subsets of a set of k elements,
// in order first by (ascending) subset size, and then lexicographically.
// The elements correspond to the bits in increasing magnitude (so the
// first element in lexicographic order corresponds to the 2^0 bit.)
//
// This function generates and returns the next bit-pattern, in circular order
// (so that if the iteration is finished, it returns 0).
//
template<typename UnsignedInteger>
UnsignedInteger next_combination(UnsignedInteger comb, UnsignedInteger mask) {
UnsignedInteger last_one = comb & -comb;
UnsignedInteger last_zero = (comb + last_one) &~ comb & mask;
if (last_zero) return comb + last_one + (last_zero / (last_one * 2)) - 1;
else if (last_one > 1) return mask / (last_one / 2);
else return ~comb & 1;
}
Строка 5 выполняет бит-хакерский эквивалент замены (расширенного) регулярного выражения, которая находит последний 01
в строке переворачивает 10
и сдвигает все следующие 1
до упора вправо.
s/01(1*)(0*)$/10\2\1/
Строка 6 делает это (только если предыдущий не удался), чтобы добавить еще один 1
и сдвинуть 1
до упора вправо:
s/(1*)0(0*)/\21\1/
Я не знаю, помогает ли это объяснение или мешает 🙂
Вот быстрый и грязный драйвер (аргумент командной строки — это размер набора, по умолчанию 5, максимальное количество битов в беззнаковых длинных):
#include <iostream>
template<typename UnsignedInteger>
std::ostream& show(std::ostream& out, UnsignedInteger comb) {
out << '[';
char a = 'a';
for (UnsignedInteger i = 1; comb; i *= 2, ++a) {
if (i & comb) {
out << a;
comb -= i;
}
}
return out << ']';
}
int main(int argc, char** argv) {
unsigned int n = 5;
if (argc > 1) n = atoi(argv[1]);
unsigned long mask = (1UL << n) - 1;
unsigned long comb = 0;
do {
show(std::cout, comb) << std::endl;
comb = next_combination(comb, mask);
} while (comb);
return 0;
}
Трудно поверить, что эта функция может быть полезна для набора из более чем 64 элементов, учитывая размер перечисления, но может быть полезно перечислить некоторую ограниченную часть, такую как все подмножества трех элементов. В этом случае бит-хакерство действительно полезно, только если модификация помещается в одно слово. К счастью, это легко проверить; вам просто нужно выполнить вычисления, как указано выше, для последнего слова в наборе битов, вплоть до проверки на last_zero
быть ноль. (В этом случае вам не нужно mask
и, возможно, вы захотите выбрать другой способ задания заданного размера.) Если last_zero
оказывается равным нулю (что на самом деле будет довольно редко), тогда вам нужно выполнить преобразование каким-то другим способом, но принцип тот же: найдите первое 0
который предшествует 1
(обратите внимание на случай, когда 0
в конце слова и 1
в начале следующего); изменить 01
в 10
выясни сколько 1
s вы должны двигаться, и переместить их до конца.
Рассматривая список элементов L = [a, b, c]
, powerset of L
дан кем-то:
P(L) = {
[],
[a], [b], [c],
[a, b], [a, c], [b, c],
[a, b, c]
}
Рассматривая каждую позицию как немного, у вас будет отображение:
id | positions - integer | desired set
0 | [0 0 0] - 0 | []
1 | [1 0 0] - 4 | [a]
2 | [0 1 0] - 2 | [b]
3 | [0 0 1] - 1 | [c]
4 | [1 1 0] - 6 | [a, b]
5 | [1 0 1] - 5 | [a, c]
6 | [0 1 1] - 3 | [b, c]
7 | [1 1 1] - 7 | [a, b, c]
Как видите, id
не отображается напрямую на целые числа. Необходимо применить правильное отображение, чтобы у вас было:
id | positions - integer | mapped - integer
0 | [0 0 0] - 0 | [0 0 0] - 0
1 | [1 0 0] - 4 | [0 0 1] - 1
2 | [0 1 0] - 2 | [0 1 0] - 2
3 | [0 0 1] - 1 | [0 1 1] - 3
4 | [1 1 0] - 6 | [1 0 0] - 4
5 | [1 0 1] - 5 | [1 0 1] - 5
6 | [0 1 1] - 3 | [1 1 0] - 6
7 | [1 1 1] - 7 | [1 1 1] - 7
Чтобы попытаться решить эту проблему, я решил использовать бинарное дерево для отображения — я публикую его, чтобы кто-то мог увидеть решение из него:
#
______________|_____________
a / \
_____|_____ _______|______
b / \ / \
__|__ __|__ __|__ __|__
c / \ / \ / \ / \
[ ] [c] [b] [b, c] [a] [a, c] [a, b] [a, b, c]
index: 0 3 2 6 1 5 4 7
Предположим, ваш набор имеет размер N.
Итак, существует (N выбирают k) наборов размера k. Вы можете найти правильное k (то есть размер n-го набора) очень быстро, просто вычитая (N выбирайте k) из n, пока n не станет отрицательным. Это сводит вашу проблему к поиску n-го k-подмножества N-множества.
Первые (N-1 выбирают k-1) k-подмножества вашего N-набора будут содержать его наименьший элемент. Таким образом, если n меньше (N-1 выбирает k-1), выберите первый элемент и рекурсивно на остальной части набора. В противном случае у вас есть один из (N-1 выберите k) других наборов; отбросьте первый элемент, вычтите (N-1 выберите k-1) из n и рекурсивно.
Код:
#include <stdio.h>
int ch[88][88];
int choose(int n, int k) {
if (n<0||k<0||k>n) return 0;
if (!k||n==k) return 1;
if (ch[n][k]) return ch[n][k];
return ch[n][k] = choose(n-1,k-1) + choose(n-1,k);
}
int nthkset(int N, int n, int k) {
if (!n) return (1<<k)-1;
if (choose(N-1,k-1) > n) return 1 | (nthkset(N-1,n,k-1) << 1);
return nthkset(N-1,n-choose(N-1,k-1),k)<<1;
}
int nthset(int N, int n) {
for (int k = 0; k <= N; k++)
if (choose(N,k) > n) return nthkset(N,n,k);
else n -= choose(N,k);
return -1; // not enough subsets of [N].
}
int main() {
int N,n;
scanf("%i %i", &N, &n);
int a = nthset(N,n);
for (int i=0;i<N;i++) printf("%i", !!(a&1<<i));
printf("\n");
}