微软的技术开发岗的一道测试题。
题目要求如下:在由N个0和M个1组成的全排列中,选出第K个位置上的排列。
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
Description
Consider a string set that each of them consists of {0, 1} only. All strings in the set have the same number of 0s and 1s. Write a
program to find and output the K-th string according to the dictionary order. If such a string doesn’t exist, or the input is
not valid, please output “Impossible”. For example, if we have two ‘0’s and two ‘1’s, we will have a set with 6 different strings,
{0011, 0101, 0110, 1001, 1010, 1100}, and the 4th string is 1001.
Input
The first line of the input file contains a single integer t (1 ≤ t ≤ 10000), the number of test cases, followed by the input data
for each test case. Each test case is 3 integers separated by blank space: N, M(2 <= N + M <= 33 and N , M >= 0), K(1 <= K <= 1000000000).
N stands for the number of ‘0’s, M stands for the number of ‘1’s, and K stands for the K-th of string in the set that needs to
be printed as output.
Output
For each case, print exactly one line. If the string exists, please print it, otherwise print “Impossible”.
样例输入
3
2 2 2
2 2 7
4 7 47
样例输出
0101
Impossible
01010111011
这道题的关键点在于明白一个定理,即:假设有k种物体,每种物体的个数为ni个, 1 <= i <= k,则这k中物体的排列数为 n! / ( n1! * n2! * ... * nk!)种, 其中 n = Sum(n1 + n2 + .. + nk ). 在这道题中,分成2类物体0和1,它们的个数为N和M,所以它们的全排列数为(N+M)! / (N! * M!).
下面以一个例子来说明如何使用上述定理来解这道题:假设我们要求由3个0和0个1组成的全排列中的第15个数。
因为由3个0和0个1组成的全排列一共有 6! / (3! * 3!) = 20种,所以一定存在解。
(1)确定第一个 1:
因为由3个1和2个0组成的全排列有 10 < 15, 在这种情况下,最大的数为 0 111 00
由3个1和3个0组成的全排列有 20 > 15, 在这种情况下,最大的数为 1111 000
所以第15个数字一定在0 111 00 ~ 1111 000 之间, ==> 我们就可以确定 第15个数的第一个1的位置,即它满足 1** ***。
下面我们需要确定那些 ** *** 具体代表什么。
(2)确定** ***
我们知道这5个*中,有2个为1,3个为0.
我们已经知道1** *** 在排列中11 -- 20位,所以我们只需要找出 ** *** 中第5个位置上的排列n1 n2 n3 n3 n5, 那么排在第15位的数字就是1 n1 n2 n3 n3 n5。
所以第二步就可以转换为求 由2个为1,3个为0组成的全排列中第5个数。下面就可以递归来求解了。
本人写的代码如下:
#include <iostream>
#include <string>
#include <queue>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
long long T;
long long N, M, K;
long long KindNum[40][40]; // KindNum[i][j] = (i+j)! / (i! * j!)
int Array[30]; // Array[i] 表示排列中第i位的数字0/1
/*
求出0-33的所有情况的排列数
*/
long long kind()
{
memset(KindNum, 0, sizeof(KindNum));
for(int i = 0; i <= 33; i++)
{
for(int j = 0; j <= 33; j++)
{
if(i == 0 || j == 0)
KindNum[i][j] = 1;
else
{
KindNum[i][j] = KindNum[i][j-1] * (i + j) / j;
}
//cout << KindNum[i][j] << " ";
}
//cout << endl;
}
}
/*
求由m个1,n个0组成的全排列中第k个位置的数
*/
void findNum(long long m, long long n, long long k)
{
for(int i = 0; i <= 33; i++)
{
if(KindNum[m][i] == k) // 第K个数恰好是由m个1和i个0组成的排列中的最大数
{
int index = N + M -1;
index -= i;
while(m > 0)
{
Array[index] = 1;
index--;
m--;
}
return;
}
else if(KindNum[m][i] < k && KindNum[m][i+1] > k) // 确定第k个数在哪个区间
{
int index = (N + M -1) - (i + 1) - m + 1;
Array[index] = 1; // 确定第index位为1
findNum(m-1, i+1, k - KindNum[m][i]); // 递归查询
return ;
}
}
}
int main()
{
//freopen("data.txt", "r", stdin);
kind();
cin >> T;
while(T--)
{
memset(Array, 0, sizeof(Array));
cin >> N >> M >> K;
if(KindNum[M][N] < K)
{
printf("Impossible\n");
}
else {
findNum(M, N, K);
for(int i = 0; i < N + M; i++)
printf("%d", Array[i]);
printf("\n");
}
}
return 0;
}
在C++ SLT中,
next_permutation
可以用来计算全排列的下一个排列。据说这道题可以使用它从第一个排列开始,一直调用k次
next_permutation,就可以求出第k的排列,并且不会超时。
怎么可以这样,我们都是信奉“非暴力,不合作”的人!!!