题目
题面
有这么一个游戏:
写出一个1至N的排列ai,然后每次将相邻两个数相加,构成新的序列,再对新序列进行这样的操作,显然每次构成的序列都比上一次的序列长度少1,直到只剩下一个数字位置。下面是一个例子:
3,1,2,4
4,3,6
7,9
16
最后得到16这样一个数字。
现在想要倒着玩这样一个游戏,如果知道N,知道最后得到的数字的大小sum,请你求出最初序列ai,为1至N的一个排列。若答案有多种可能,则输出字典序最小的那一个。
(这里的字典序是指1,2,3,…9,10,11。即从小到大排序)
如
输入 4 16
输出 3 1 2 4
思路
看了之后好像有点规律,但是没找出来,看了题解之后才知道原来是杨辉三角。(感谢大佬)
设第一行的数字是a,b,c…
n为4的时候,sum=a+3b+3c+d;
n为5的时候,sum=a+4b+6c+4d+e;
n为6的时候,sum=a+5b+10c+10d+5e+f;
这样,就发现了杨辉三角,
知道了杨辉三角就能直接搜索1~n这n个数字,
找到满足sum=a+3b+3c+d (n==4),的a b c d 数组就行啦
但是还需要知道杨辉三角的性质:
第n行的第i个数(0<i<n-1, i从0到n-1),
有pc[i]=pc[i-1]*(n-i)/i;
pc[i]是第i个数等于多少,
比如n==4时,pc数组就是1,3,3,1
而每一行的 pc[0]==pc[n-1]==1; 这是杨辉三角的性质
接下来就是搜索了,直接用dfs进行搜索,直到找到满足题意的序列,要注意的是,我们需要传入三个参数:
1.是记录我们当前已经搜索到了第几个数字i,用来判断现在是否到达了最后一个,以及用来记录答案的顺序(确定找到答案时会回溯,此时会按照i的顺序把答案填进答案数组ans)
2.第二个参数是记录当前搜索用的数字,因为每个数字只能用一次,所以需要标记。记得以前写dfs的时候,都是在扔进dfs之前先标记,一进函数就判断是否访问给,这样少传一个参数。不过写成现在这样看起来更加整洁。。。
记得找不到答案要回溯的时候把标记过的取消标记.
3.第三个参数是记录当前积累的“和”是多少,判断是否已经到达了题目所给的和
代码
#include<iostream>
#include<cstring>
using namespace std;
int pc[13];
int vis[13];
int ans[13];
int n, sum;
int dfs(int i, int num, int v)
{
//参数:i是目前搜索到第几个,从1开始
//num是目前填入的答案是多少
//v是从第一个累计到目前一共“和”是多少
if (v > sum)
return 0;
if (n == i && v == sum)//递归到最后一个并且累计的“和”与题目要求相同
return 1;
vis[num] = 1;//因为1~n每个数字只能使用一次,所以需要标记
for (int j = 1; j <= n; j++)
{
if (vis[j] == 0 && dfs(i + 1, j, v + pc[i] * j))
{
//当dfs返回1,说明已经能搜索到最后一个数字并且成功了,
//这时候就会回溯,把每一个ans都填上,并且返回1
ans[i] = j;
return 1;
}
}
vis[num] = 0;//没有找到正确答案,所以回溯的时候要把访问的置零
return 0;
}
int main()
{
cin >> n >> sum;
memset(vis, 0, sizeof(vis));
pc[0] = pc[n - 1] = 1;
for (int i = 1; i <= n / 2; i++)
pc[n - i - 1] = pc[i] = pc[i - 1] * (n - i) / i;
//杨辉三角的每一行中每一个元素的计算都是由前一个元素和该行一共有几个元素n以及所在位置i计算所得
//for (int i = 0; i < n; i++)
// cout << pc[i] << " ";
if (dfs(0, 0, 0))
for (int i = 0; i < n; i++)
cout << ans[i] << " ";
cout << endl;
return 0;
}