Да точно так же можно, как и при делении на два.
Предположим, аргументы нормированны, то есть от 0 до 1.
Тогда по шагам:
1) точка 0.5; 0.5; 0.5,…
2) точки (0.25a), (0.25b), (0.25c),… | a,b,c = 1...3
3) точки (0.125a), (0.125b), (0.125c),… | a,b,c = 1...7
…
Ну, само собой, каждый раз берем те, которые еще не брали.
Как получать точки для итерации?
iters = new int[Nd] ;
for (i = 1; ; i++) { // номер итерации
step = 1 / 2^i; // полдлины интервала семплирования
for ( /* iters пробегает все возможные наборы из чисел от 1 до (2^i - 1) */ ) {
if ( /* все числа в iters четные */ )
continue; // уже брали раньше
sample F(iters[0] * step, iters[1] * step, iters[2] * step, ...)
}
}
Ну и в строчке с sample надо повозиться с аргументами, чтобы сдвинуть их обратно в те диапазоны, где они были.
Как добиться однородной плотности по всем аргументам. Если у них разные, но не слишком, диапазоны, нормировать их нужно самым большим из них. Т.е. сдвинуть все в [0; x], где x будет равно 1 только для самого длинного диапазона. А потом во втором форе (который будет работать как куча вложенных друг в друга) отсекать лишние хвосты. Т. е. если, например, 0.125 * iters[2] уже больше, чем величина х для третьего аргумента, то по этой переменной дальше не итерируем. Перед входом во второй for эти границы можно просчитать, это совсем несложно.
И последнее. Если диапазоны отличаются значительно, то может иметь смысл как-то посчитать, с какой итерации следует начинать цикл, т.к. часть из них будет пропущена. Тем больше, чем больше различаются диапазоны.
Как-то так, я думаю.