• 【u004】数列


    Time Limit: 1 second
    Memory Limit: 128 MB

    【问题描述】

    这样一种数列A1、A2、A3、……An,其中A1=0,且对任意一项Ai满足|Ai-A(i+1)|=1(1<=i<n)。设S=A1+A2+A3+……+An,表示前n项之和。 现在给出数列长度n与数列前n项之和S,要求: 输出满足条件的数列的总数。 
    输出满足条件的100个数列(如果不满100个就全部输出)。

    【输入格式】

    一行,包含两个整数n和S(1<=n<=100),用1个空格隔开。

    【输出格式】

    第1行一个整数t(0<=t<=263-1),表示满足条件的数列总数。 接下来每行输出一个数列,数列各项之间用一个空格隔开。 若满足条件的数列数目不满100个,全部输出即可。

    Sample Input

    4 0
    
    
    
    
    

    Sample Output

    2
    0 -1 0 1
    0 1 0 -1
    【题解】

    设f[i][j]为前n个数,组成和为s的方案数。

    f[i+1][j+i] =f[i+1][j+i]+ f[i][j];

    f[i+1][j-i] =f[i+1][j-i]+ f[i][j];

    更新的时候这样更新

    for (int i = 1;i <= n;i++)

    for (int j = -9999;j <= 10000;j++)

    if (f[i][j] > 0)

    {

    f[i+1][j+i] =f[i+1][j+i]+ f[i][j];

    f[i+1][j-i] =f[i+1][j-i]+ f[i][j];

    }

    //先忽略数组下标是负的这个问题。

    首先是边界问题。

    f[1][0] = 1;{0}

    f[2][1] = 1;{0,1}

    f[2][-1] = 1;{0,-1}

    比如我们现在枚举到

    i = 2;j = 1;

    j+i== 3;

    f[3][3] = f[3][3] + f[2][1];

    为什么?

    f[2][1]->{0,1};

    这里我们进行的操作相当于在0和1之间插入一个数字1.

    然后原来的1递增。

    即{0,1,2} 也即f[3][3];

    然后j-i==-1;

    f[3][-1] =f[3][-1]+f[2][1];

    为什么?

    f[2][1]->{0,1};

    这里我们进行的操作相当于在0和1之间插入一个数字-1;

    然后原来的1要递减才能满足|a[n]-a[n+1]|==1;

    即{0,-1,0}也即f[3][-1];

    再往下举例子;

    i = 4,j==6;

    {0,1,2,3};

    对应状态f[4][6];

    然后j+i==10;

    则f[5][10] = f[5][10]+f[4][6];

    为什么?

    f[4][6]->{0,1,2,3};

    这里我们相当于在0和1之间插入一个1.然后把1,2,3都递增1;

    也即{0,1,2,3,4};也即f[5][10];

    然后j-i ==2;

    则f[5][2] = f[5][2]+f[4][6];

    为什么?

    f[4][6]->{0,1,2,3}

    这里我们相当于在0和1之间插入一个-1.然后原来的其他数字全都递减1.

    即{0,-1,0,1,2};也即f[5][2];


    加深理解:

    每次进行枚举序列的时候必然要对第i号元素做出决策。

    决策的内容就是当前数字比前一个数字大1还是小1;

    假设我们每个阶段做出的决策为what[i] ∈{-1,1};

    则题目的要求就是

    (0+what[1]) + (0+what[1]+what[2]) + (0+what[1]+what[2]+what[3])+..+

    (0+what[1]+what[2]+..+what[n-1]) == s

    可以合并同类项为

    what[n-1]+2*what[n-2]+3*what[n-3]+..+(n-1)*what[1] == s;

    然后what[i]只能为1或-1

    则原问题转化为

    0口1口2口3口4口..口(n-1) == s

    其中的口要填入减号或加号。

    这个动规就比较好写了

    f[i][j] = f[i-1][j-(i-1)]+f[i-1][j+(i-1)];

    //等号右边分别表示最后一个口做加法和做减法的情况;

    这也是我们上面那种解释的写法。只不过变成顺推了而已。

    f[i+1][j+i] += f[i][j]

    我们插入1和插入-1到a[1]和a[2]之间。

    原本是

    0+(0+what[1]) + (0+what[1]+what[2]) + (0+what[1]+what[2]+what[3])+..+

    (0+what[1]+what[2]+..+what[i-1]) == j

    我们插入一个1之后就变成这样了

    0+(0+1)+(0+what[1]+1)+(0+what[1]+what[2]+1)+(0+what[1]+what[2]+what[3]+1)

    +..+(0+what[1]+what[2]+..+what[i-1]+1) == j + i

    //what下标从1..i-1,然后a[2]变成(0+1)还有一个1.所以总共是i个1.

    而这是一个符合要求的序列!

    插入-1同理。只不过右边变成j-i了。


    然后要输出100个方案的时候。就可以按照上面说的原理。即在第1和第2个数字之间插入1个1或者-1。来获取方案。(用f[i][j]数组来剪枝!)

    总和的话1+..+100 为5050.然后考虑到下标会出现i+j的情况。你直接开到10000就好。

    数组下标为负数的话 就直接加上20000;

    即数组开为f[101][20001];


    【代码】

    #include <cstdio>
    
    int n, s,what[101],sum;
    __int64 f[101][20001] = { 0 };//如果下标为负数就直接加上20000;
    __int64 tot;
    
    void solve(int);
    
    int main()
    {
    	scanf("%d%d", &n, &s);//输入n和s
    	f[1][0] = 1;//第一个数字为0
    	for (int i = 1;i <= n-1;i++)//枚举前i个数字
    		for (int tj = -9999; tj <= 10000; tj++)//枚举总和。
    		{
    			int j = tj; 
    			if (j < 0) //小于0就直接加上20000
    				j += 20000;
    			if (f[i][j] <= 0) //如果不能到达这个状态就跳过。
    				continue;
    			int temp = tj + i ;//这是在第1和第2个数字之间插入一个1的情况,原来的其他数字就都要加上1
    			if (temp < 0) //同理,小于0就加20000;
    				temp += 20000;
    			f[i + 1][temp] += f[i][j];//递增答案。
    			temp = tj - i;//这是在第1和第2个数字之间插入一个-1的情况,原来的其他数字就都要减去1了。
    			if (temp < 0)
    				temp += 20000;
    			f[i + 1][temp] += f[i][j];//递增相应的答案即可。
    		}
    	if (s < 0) //为下标小于0特判
    		printf("%I64d
    ", f[n][s + 20000]);
    	else
    		printf("%I64d
    ", f[n][s]);
    	sum = s;//这是当前的和。我们采用递减的方式让sum一直减到0且到达了第一个数字。
    	if (s < 0) //这是所需要输出的方案数。
    		tot = f[n][s + 20000];
    	else
    		tot = f[n][s];
    	if (tot > 100)//大于100就直接为100;
    		tot = 100;
    	solve(n);//输出方案。
    	return 0;
    }
    
    void solve(int now)
    {
    	if (now == 1 && sum == 0)//如果已经到了第一个元素(即0),且sum也从s减到了0.则我们
    	{//搜索的这个方案是可行的。
    		tot--;//找到了这个方案。方案数递减。
    		printf("0");//第一个数字肯定是0
    		int kk = 0; //第一个数字是0
    		for (int i = n; i >= 2; i--)//我们是把后面出现的数字插入到a[1]和a[2]之间。
    		{//所以进行的操作是从后往前的。
    			kk += what[i]; //对初始数字(一开始kk==a[1]==0)进行递增或递减操作。
    			printf(" %d", kk);//输出kk。
    		}
    		printf("
    ");
    		return;
    	}
    	if (tot == 0)//如果要求的方案数都输出了。则退出
    		return;
    	int tt = sum;
    	if (tt < 0)
    		tt += 20000;
    	if (f[now][tt] == 0)//如果前now个数和为tt的情况不存在则退出。
    		return;//这是一个很棒的剪枝。
    	what[now] = -1;//表示当前的状态是插入一个-1得来的。
    	sum += (now - 1);//然后它之前的状态当然就是把-1去掉。然后和要相应的递增。
    	solve(now - 1);//然后往前继续枚举,要注意一定要先枚举-1的情况(可以看样例输出。这是顺序问题);
    	sum -= (now - 1);//回溯
    
    	what[now] = 1;//表示当前这个状态是插入一个1得来的。
    	sum -= (now-1);//则它之前的状态就是把这个1去掉。其他数字相应也要减去1(now-1个1);
    	solve(now - 1);//往前继续枚举。
    	sum += (now - 1);//回溯。
    }


  • 相关阅读:
    Thinkphp 控制器
    Thinkphp 框架基础
    smarty练习:考试系统
    smarty 自定义函数
    smarty 变量调节器
    python 格式化的三种方法
    python 随机生成汉字
    python中的and和or用法
    pytest 运行用例报错:unknown hook 'pytest_namespace' in plugin <module 'allure.pytest_plugin'
    Jenkins安装插件报错:该Jenkins实例似乎已离线
  • 原文地址:https://www.cnblogs.com/AWCXV/p/7632303.html
Copyright © 2020-2023  润新知