递归之排列组合

D的小L

时间限制: 4000 ms  |  内存限制: 65535 KB
难度: 2
描述
      一天TC的匡匡找ACM的小L玩三国杀,但是这会小L忙着哩,不想和匡匡玩但又怕匡匡生气,这时小L给匡匡出了个题目想难倒匡匡(小L很D吧 ),有一个数n(0<n<10),写出1到n的全排列,这时匡匡有点囧了 ,,,聪明的你能帮匡匡解围吗?
输入
第一行输入一个数N(0<N<10),表示有N组测试数据。后面的N行输入多组输入数据,每组输入数据都是一个整数x(0<x<10)
输出
按特定顺序输出所有组合。
特定顺序:每一个组合中的值从小到大排列,组合之间按字典序排列。
样例输入
2
2
3
样例输出
12
21
123
132
213
231
312
321

//以下是全排列的基本模板

#include <stdio.h>
int flag[10], a[10], b[10];
int n;
void fun(int x)
{
    int i;
    if(x == n)//递归出口,如果一次全排列排完了就输出
    {
        for(i = 0; i < n; i++)
            printf("%d", b[i]);
        printf("\n");
        return ;
    }
    for(i = 0; i < n; i++)//遍历需要排列的总数
    {
        if(flag[i] == 0)//现在才明白这里标记的不是数字而是位置,如果当前需要排列的位置没有进行排列赋值则进入赋值递归
        {
            flag[i] = 1;
            b[x] = a[i];
            fun(x + 1);
            flag[i] = 0;//回溯回来的时候再把该位置标记为没有数
        }
    }
}
int main()
{
    int i, num;
    scanf("%d", &num);
    for(i = 0; i < 10; i++)//给原始数组赋值,其实好像也可以不用这个数组,直接用下标i来给b赋值也可以,用数组可能改动的时候比较方便吧,只需要改原始数组就可以了
        a[i] = i + 1;
    while(num--)
    {
        scanf("%d", &n);
        fun(0);
    }
    return 0;
}


//nyoj32组合数

//组合

组合数

时间限制: 3000 ms  |  内存限制: 65535 KB
难度: 3
描述
找出从自然数1、2、... 、n(0<n<10)中任取r(0<r<=n)个数的所有组合。
输入
输入n、r。
输出
按特定顺序输出所有组合。
特定顺序:每一个组合中的值从大到小排列,组合之间按逆字典序排列。
样例输入
5 3
样例输出
543
542
541
532
531
521
432
431
421
321
//组合,有点模拟的味道
#include <stdio.h>
int a[10], b[10], flag[10];
int n, m;
void fun(int x)
{
    int i;
    if(x == m)//递归出口控制好
    {
        for(i = 0; i < m; i++)
            printf("%d", b[i]);
        printf("\n");
        return ;
    }
    if(x == 0)//为啥会把要组合的第一个数拿出来呢,这其实就可以相当于是模拟我们组合的时候,总是会先保持哪几个数不变,然后再变哪几个数,这样就可以避免重复
    {
        for(i = n - 1; i >= m - 1; i--)//因为下标从0开始,所以要-1,其中这里只需要遍历一下前几个数就可以了,至于为啥,自己模拟一下数学上的求组合数就知道啦
        {
            if(flag[i] == 0)//那么这里我觉得标记的就是数,而不是位置了,标记是否该数有没有被用过
            {
                flag[i] = 1;
                b[x] = a[i];
                fun(x + 1);
                flag[i] = 0;
            }
        }
    }
    else
    {
        for(i = n - 1; i >= 0; i--)//这里需要遍历原始数组里面的每个数
        {
            if(flag[i] == 0)
            {
                if(a[i] < b[x - 1])//这个判断很关键啊,不得不说发明这个的人很牛啊
                {
                    flag[i] = 1;
                    b[x] = a[i];
                    fun(x + 1);
                    flag[i] = 0;
                }
            }
        }
    }
}
int main()
{
    int i;
    for(i = 0; i < 10; i++)
        a[i] = i + 1;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        fun(0);
    }
    return 0;
}

//下面有个简单版本,也比较好理解

#include<stdio.h>
#include <string.h>
#define MAX 0xfffffff
int n, m;
int a[11] = {MAX};
int vis[11];
void dfs(int cur)
{
    int k,i;
    if(cur == m + 1)
    {
        for(k = 1; k <= m; k++)
            printf("%d", a[k]);
        return ;
    }
    for(i = n; i >= 1;i--)
    {
        if(!vis[i] && a[cur - 1] > i)//关键应该是这个判断吧
        {
            a[cur] = i;
            vis[i] = 1;
            dfs(cur + 1);
            vis[i] = 0;
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        dfs(1);
        memset(vis, 0, sizeof(vis));
    }
    return 0;
}




//nyoj19擅长排列的小明

//排列组合

 

擅长排列的小明

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述
小明十分聪明,而且十分擅长排列计算。比如给小明一个数字5,他能立刻给出1-5按字典序的全排列,如果你想为难他,在这5个数字中选出几个数字让他继续全排列,那么你就错了,他同样的很擅长。现在需要你写一个程序来验证擅长排列的小明到底对不对。
输入
第一行输入整数N(1<N<10)表示多少组测试数据,
每组测试数据第一行两个整数 n m (1<n<9,0<m<=n)
输出
在1-n中选取m个字符进行全排列,按字典序全部输出,每种排列占一行,每组数据间不需分界。如样例
样例输入
2
3 1
4 2
样例输出
1
2
3
12
13
14
21
23
24
31
32
34
41
42
43
//这个是排列组合,感觉跟全排列一个样子
#include <stdio.h>
int flag[10], a[10], b[10];
int n, m;
void fun(int x)
{
    int i;
    if(x == m)//这里控制一下递归出口就好了,至于为什么会这么做,还没有想得特别明白
    {
        for(i = 0; i < m; i++)
            printf("%d", b[i]);
        printf("\n");
        return ;
    }
    for(i = 0; i < n; i++)
    {
        if(flag[i] == 0)
        {
            flag[i] = 1;
            b[x] = a[i];
            fun(x + 1);
            flag[i] = 0;
        }
    }
}
int main()
{
    int i, num;
    scanf("%d", &num);
    for(i = 0; i < 10; i++)
        a[i] = i + 1;
    while(num--)
    {
        scanf("%d%d", &n, &m);
        fun(0);
    }
    return 0;
}
        

素数环

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 2
描述

有一个整数n,把从1到n的数字无重复的排列成环,且使每相邻两个数(包括首尾)的和都为素数,称为素数环。

为了简便起见,我们规定每个素数环都从1开始。例如,下图就是6的一个素数环。

输入
有多组测试数据,每组输入一个n(0<n<20),n=0表示输入结束。
输出
每组第一行输出对应的Case序号,从1开始。
如果存在满足题意叙述的素数环,从小到大输出。
否则输出No Answer。
样例输入
6
8
3
0
样例输出
Case 1:
1 4 3 2 5 6
1 6 5 2 3 4
Case 2:
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2
Case 3:
No Answer


//nyoj488素数环

//排列的一个变形,这个题目貌似必须要剪枝,不然的话好像是超时,坑啊

#include <stdio.h>
#include <string.h>
#include <math.h>
int a[25], b[25], flag[25];
int n, mark;
int prime[45] = {0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,
1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,
0,0,0,1,0,0,0
};
/*int is_prime(int num)//其实我不知道这里用这个函数来判断会不会超时,因为开始我用函数加上没有剪枝提交的超时了,所以我又改成了数组,但是看最优代码好像是用函数写的,不过他只用了一个函数调用
{
    int i, flag = 1;
    for(i = 2; i <= sqrt(num); i++)
    {
        if(num % i == 0)
        {
            flag = 0;
            break;
        }
    }
    return flag;
}*/
int check(int i, int x)
{
    if(x != n - 1)//这里让我卡了好久,注意这里应该判断的事b数组的位置,而不是a数组的位置
    {
        if(prime[i + 1 + b[x - 1]] == 1)
            return 1;
        else
            return 0;
    }
    else
    {
        if(prime[i + 1 + b[x - 1]] == 1 && prime[i + 1 + 1] == 1)
            return 1;
        else
            return 0;
    }
    return 0;
}
void dfs(int x)//以下就是全排列的变形啦
{
    int i;
    if(x == n)
    {
        mark = 1;
        for(i = 0; i < n; i++)
        {
            printf("%d ", b[i]);
        }
        printf("\n");
        return ;
    }
    for(i = 1; i < n; i++)
    {
        if(flag[i] == 0 && check(i, x) == 1)
        {
            flag[i] = 1;
            b[x] = i + 1;
            dfs(x + 1);
            flag[i] = 0;
        }
    }
}
int main()
{
    int j;
    scanf("%d", &n);
    j = 0;
    while(n != 0)
    {
        b[0] = 1;//因为题目里面说始终以1开头,所以b的第一个位置必须赋值为1
        printf("Case %d:\n", j + 1);
        if(n % 2 != 0)//这儿就应该是剪枝了
        {
            if(n == 1)
                printf("1\n");
            else
                printf("No Answer\n");
        }
        else
            dfs(1);
        j++;
        scanf("%d", &n);
        memset(flag, 0, sizeof(flag));
    }
    return 0;
}

P1080 N皇后
时间: 1000ms / 空间: 131072KiB / Java类名: Main

描述

检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。 

         列号
   1  2  3  4  5  6 

  ------------------------- 

1 |  | O |  |  |  |  | 

  ------------------------- 

2 |  |  |  | O |  |  | 

  ------------------------- 

3 |  |  |  |  |  | O | 

  ------------------------- 

4 | O |  |  |  |  |  | 

  ------------------------- 

5 |  |  | O |  |  |  | 

  ------------------------- 

6 |  |  |  |  | O |  | 

  ------------------------- 

上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下: 
行号 1 2 3 4 5 6 
列号 2 4 6 1 3 5 
这只是跳棋放置的一个解。请编一个程序找出所有跳棋放置的解。并把它们以上面的序列方法输出。解按字典顺序排列。请输出前3个解。最后一行是解的总个数。 
特别注意: 对于更大的N(棋盘大小N x N)你的程序应当改进得更有效。不要事先计算出所有解然后只输出(或是找到一个关于它的公式),这是作弊。如果你坚持作弊,那么你登陆tyvj的帐号将被无警告删除 

输入格式

一个数字N (6 <= N <= 13) 表示棋盘是N x N大小的。 

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。 

测试样例1

输入

6

输出

2 4 6 1 3 5 
3 6 2 5 1 4 
4 1 5 2 6 3 
4
//我又开始了八皇后之路,路漫漫
#include <stdio.h>
int n, cnt;
int print[15];
int visit1[15][15], visit2[15][15], visit3[15][15];//用三个二维数组来分别标记列,主对角线,副对角线
void dfs(int cur)
{
    int i;
    if(cur == n)
    {
        cnt++;
        if(cnt <= 3)
        {
            for(i = 0; i < n; i++)
            {
                printf("%d ", print[i]);
            }
            printf("\n");
        }
        return ;
    }
    for(i = 1; i <= n; i++)
    {
        if(visit1[0][i] == 0 && visit2[1][cur + i] == 0 && visit3[2][cur - i + n] == 0)//判断一下就好,苦了我好久,我在怀疑自己的智商,我去....
        {
            print[cur] = i;
            visit1[0][i] = 1;
            visit2[1][cur + i] = 1;//副对角线
            visit3[2][cur - i + n] = 1;//主对角线
            dfs(cur + 1);
            visit1[0][i] = 0;
            visit2[1][cur + i] = 0;
            visit3[2][cur - i + n] = 0;
        }
    }
}
int main()
{
    scanf("%d", &n);
    dfs(0);
    printf("%d\n", cnt);
    return 0;
}
棋盘问题
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。 对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

Sample Output

2
1

有点类似于八皇后问题,但是又稍微比八皇后简单一点,毕竟不需要判断对角线问题,而又比八皇后麻烦一点,毕竟不是始终n==k了,还有k<n的情况考虑,所以又有种排列组合的味道在里面了,所以递归的时候注意处理一下这些特殊状态就好了,注意不能改原地图的,可以用标记数组

#include <stdio.h>
#include <string.h>
int n, k, cnt;
char chess[9][9];
int mark[9][9];
int check(int row, int col)//判断是否该行该列里面有没有放元素
{
    int i, flag = 1;
    for(i = 0; i < row; i++)
    {
        if(mark[i][col] == 1)//用标记数组判断就可以了
        {
            flag = 0;
            break ;
        }
    }
    for(i = 0; i < col; i++)
    {
        if(mark[row][i] == 1)
        {
            flag = 0;
            break ;
        }
    }
    return flag;
}
void dfs(int row, int num)
{
    if(num == k)//一次递归完毕
    {
        cnt++;
        return ;
    }
    if(row == n)//这里是为了判断越界
    {
        return ;
    }
    int i;
    for(i = 0; i < n; i++)//下面的就有种全排列的味道了
    {
        if(mark[row][i] == 0 && chess[row][i] == '#' && check(row, i) == 1)//check需要,因为可能一行里面有好几个可以走的棋盘位置
        {
            mark[row][i] = 1;//表示该点放了一个元素,至于放的是什么东西就没那么重要了,这里一起记录了行数和列数
            dfs(row + 1, num + 1);
            mark[row][i] = 0;
        }
    }
    dfs(row + 1, num);//这个是为了处理k<n的情况
}
int main()
{
    int i, j;
    char ch;
    while(~scanf("%d%d", &n, &k) && n != -1 && k != -1)
    {
        for(i = 0; i < n; i++)
        {
            getchar();
            for(j = 0; j < n; j++)
            {
                scanf("%c", &ch);
                if(ch == '#')
                {
                    chess[i][j] = '#';//这里其实做了一点优化,就是让可以走的路才进入棋盘,貌似也不是优化,其实好像效果是一样的
                }
            }
        }
        cnt = 0;
        dfs(0, 0);
        printf("%d\n", cnt);
        memset(mark, 0, sizeof(mark));
        memset(chess, 0, sizeof(chess));
    }
    return 0;
}

部分和问题

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 2
描述
给定整数a1、a2、.......an,判断是否可以从中选出若干数,使它们的和恰好为K。
输入
首先,n和k,n表示数的个数,k表示数的和。
接着一行n个数。
(1<=n<=20,保证不超int范围)
输出
如果和恰好可以为k,输出“YES”,并按输入顺序依次输出是由哪几个数的和组成,否则“NO”
样例输入
4 13
1 2 4 7
样例输出
YES
2 4 7

//下面的这个代码超时了


#include<stdio.h>
#include <string.h>
#define MAX 0xfffffff//正数最大值
int n, m, flag;
int a[22] = {MAX}, b[22];
int vis[22];
int is_can(int cur)//我想调用这个函数式导致超时的原因
{
    int sum = 0, i;
    for(i = 1; i < cur; i++)
    {
        sum += a[i];
    }
    if(sum == m)
        return 1;
    else
        return 0;
    return 0;
}
void dfs(int cur)
{
    int k,i;
    if(is_can(cur) == 1)
    {
        printf("YES\n");
        for(k = cur - 1; k >= 1; k--)
            printf("%d ", a[k]);
        printf("\n");
        flag = 1;
        return ;
    }
    for(i = n - 1; i >= 0;i--)
    {
        if(!vis[i] && a[cur - 1] > b[i])//因为这里是cur-1所以为了避免越界,也为了让数组的第一个元素不影响到判断,所以赋给它一个很大的数
        {
            a[cur]=b[i];
            vis[i]=1;
            dfs(cur+1);
            vis[i]=0;
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        int i;
        flag = 0;
        for(i = 0; i < n; i++)
            scanf("%d", &b[i]);
        dfs(1);
        if(flag == 0)
            printf("NO\n");
        memset(vis, 0, sizeof(vis));
        memset(b, 0, sizeof(b));
    }
    return 0;
}
//下面是正确代码

 
#include <stdio.h>
#include <string.h>
int n, k, flag, cnt;
int a[22], b[22], vis[22];
void dfs(int cur)
{
    int i;
    if(cnt == k)
    {
        printf("YES\n");
        for(i = 0; i < n; i++)
        {
            if(vis[i] == 1)
                printf("%d ", a[i]);
        }
        printf("\n");
        flag = 1;
        return ;
    }
    for(i = cur; i < n; i++)//这儿i是从cur 开始的
    {
        if(vis[i] == 0 && flag == 0)
        {
            vis[i] = 1;
            cnt += a[i];//这里有个技巧,就是一步一步的加,一步一步的减,就不用调用函数来判断了
            dfs(i + 1);
            cnt -= a[i];
            vis[i] = 0;
        }
    }
}
int main()
{
    while(~scanf("%d%d", &n, &k))
    {
        int i;
        cnt = 0;
        flag = 0;
        for(i = 0; i < n; i++)
            scanf("%d", &a[i]);
        dfs(0);
        if(flag == 0)
            printf("NO\n");
        memset(a, 0, sizeof(a));
        memset(b, 0, sizeof(b));
        memset(vis, 0, sizeof(vis));
    }
    return 0;
}
        



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值