hdu2062 Subset sequence----递推
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=2062
题目大意:
给出n和m,集合{1,2,,,,n}的非空子集,按照一定方式排列,例如n==3时,
1
1 2
1 2 3
1 3
1 3 2
2
2 1
2 1 3
2 3
2 3 1
3
3 1
3 1 2
3 2
3 2 1
然后输出第m个序列。
思路:
先小规模枚举,n = 1时有1个序列,n=2时有4个序列,n=3时有15个序列。
可推出F[n] = n * (F[n - 1] + 1),F[n]表示n个元素的序列个数。那就可以想到,给定一个m,可以先推出第1位,比如n = 3,m = 6时,第一位肯定是2,那我们可以递推下去,确定第一位,然后m减小,再递推出下一位。
还是之前的例子,n = 3, m = 6, 1开头的有5个,2开头的有5个,这样可以用7除以5向上取整,求出目前是第几位,所以得出以下解法:
首先设c = (m + F[n - 1] ) / (F[n - 1] + 1)(这是m除以F[n - 1] + 1 向上取整),c就表示当前这一位是第c小的。
然后m = m - (c - 1) * (F[n - 1] + 1),n--,这样就可以循环下去,求目前的n和m中的第一位是什么,这里要注意的是m还得自减一,还是用上面的例子说明,求出第一位为2后,m = 1, n = 2;我们知道m = 1, n = 2按照上面的公式算出来c = 1,也就是在1,3之中第一小的数字,就是1,序列是2,1,这与答案有误,答案应该就是2,错误的原因在于这个序列的构造,n = 2时只有4种,而我们的m算出来最大可以为5(就是n=3,m = 10的时候,循环之后n = 2, m = 5),所以m应该自减一,这样正好可以对应上,如果自减一之后等于0,就应该退出循环,结束构造,这样就可以得到之前的正确答案“2”。在每次求出一位之后,数组应该左移一位,这样可以直接利用移动后的数组来求当前第c小的数字
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<string> 6 using namespace std; 7 typedef long long ll; 8 int cases; 9 const int maxn = 1e5 + 100; 10 typedef long long ll; 11 ll n, m, a[30]; 12 void init() 13 { 14 a[1] = 1; 15 for(ll i = 2; i < 21; i++)a[i] = i * (a[i - 1] + 1); 16 //for(int i = 1; i <= 20; i++)cout<<a[i]<<endl; 17 } 18 int main() 19 { 20 init(); 21 while(cin >> n >> m) 22 { 23 int b[30], ans[30], tot = 0; 24 for(int i = 1; i <= n; i++)b[i] = i; 25 while(m > 0) 26 { 27 int c = (m + a[n - 1]) / (a[n - 1] + 1); 28 //cout<<m << " "<<c<<" "<< n<<endl; 29 ans[tot++] = b[c]; 30 n--; 31 for(int i = c; i <= n; i++)b[i] = b[i + 1]; 32 m -= (c - 1) * (a[n] + 1); 33 m--; 34 if(m <= 0)break; 35 36 } 37 cout<<ans[0]; 38 for(int i = 1; i < tot; i++)cout<<" "<<ans[i]; 39 cout<<endl; 40 } 41 }
越努力,越幸运