蓝桥杯第七届:剪邮票 枚举or排列组合

题目

如【图1.jpg】, 有12张连在一起的12生肖的邮票。
在这里插入图片描述
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
12
答案:116

解题思路

(1)枚举

思路:首先我们设x[5]:x[0],x[1],x[2],x[3],x[4]五个解向量,观察图我们不难发现
1 <= x[0] < x[1] < x[2] < x[3] < x[4] <= 12
即:
1 <= x[0] <= 8
x[0] < x[1] <= 9
x[1] < x[2] <= 10
x[2] < x[3] <= 11
x[3] < x[4] <= 12

就可以根据这个范围进行枚举,每枚举完一次判断其是否连成一片,为方便判断所以我的x[0]是从0开始。

int ans = 0;
    for(x[0] = 0; x[0] < 8 ; x[0]++)
    {
        for(x[1] = x[0] + 1; x[1] < 9 ; x[1]++)
        {
            for(x[2] = x[1] + 1; x[2] < 10; x[2]++)
            {
                for(x[3] = x[2] + 1; x[3] < 11; x[3]++)
                {
                    for(x[4] = x[3] + 1; x[4] < 12; x[4]++)
                    {
                        if(check())
                        {
                            ans++;
                        }
                    }
                }
            }
        }
    }

那么又该如何判断连通性呢?

连通性判定1:邻接矩阵

首先就是要了解一个概念,什么是邻接矩阵和度。
什么是邻接矩阵:
邻接矩阵既然是矩阵,我们就要使用一个二维数组来表示矩阵,那么就需要一个12行12列的矩阵来分别表示每个“点”之间的关系。比如表示第i行第j列表示i和j这两个点是否相邻,如果相邻那么cc[i][j] = 1,否则等于0,我们来看看这个图:在这里插入图片描述
1和2,5相邻,2和1,3,6相邻,7和3,6,8,11相邻,那么就将与其相邻的点置为1,如果不相邻就置为0

int cc[12][12] = {0,1,0,0,1,0,0,0,0,0,0,0,
                  1,0,1,0,0,1,0,0,0,0,0,0,
                  0,1,0,1,0,0,1,0,0,0,0,0,
                  0,0,1,0,0,0,0,1,0,0,0,0,
                  1,0,0,0,0,1,0,0,1,0,0,0,
                  0,1,0,0,1,0,1,0,0,1,0,0,
                  0,0,1,0,0,1,0,1,0,0,1,0,
                  0,0,0,1,0,0,1,0,0,0,0,1,
                  0,0,0,0,1,0,0,0,0,1,0,0,
                  0,0,0,0,0,1,0,0,1,0,1,0,
                  0,0,0,0,0,0,1,0,0,1,0,1,
                  0,0,0,0,0,0,0,1,0,0,1,0};

有了邻接矩阵我们就可以判断两个点是否相邻,接着我们要判断这五个点的度是否大于等于8,因为我们有5个点所以至少有4条边相连1比如这个图,2和6相连2有一条相邻边所以度为1,6和2,7相连所以度为1 + 2 = 3 依次累加下去算出来度最少为8,所以这道题就变成了每次枚举完一种情况就判断是否连成一片,而是否连成一片的依据就是每个点是否有相邻的点以及最后度是否大于等于8

bool check()
{
    bool flag = true;
    int s = 0;
    for(int i = 0 ;(flag && i < 5); i++)
    {
        int d = 0;
        for(int j = 0; j < 5; j++)
        {
            if(cc[x[i]][x[j]] == 1)/*如果两点相邻*/
            {
                d++;
            }
        }
        if( d == 0 )
        {
            flag = false;/*如果找不到一个和i这一点相邻的点那么一定不连通*/
        }
        s += d;
    }
    if( s < 8)
    {
        flag = false;/*如果度小于8就不连通*/
    }
    return flag;
} 
完整代码1
#include <iostream>

using namespace std;
int x[5];
int cc[12][12] = {0,1,0,0,1,0,0,0,0,0,0,0,
                  1,0,1,0,0,1,0,0,0,0,0,0,
                  0,1,0,1,0,0,1,0,0,0,0,0,
                  0,0,1,0,0,0,0,1,0,0,0,0,
                  1,0,0,0,0,1,0,0,1,0,0,0,
                  0,1,0,0,1,0,1,0,0,1,0,0,
                  0,0,1,0,0,1,0,1,0,0,1,0,
                  0,0,0,1,0,0,1,0,0,0,0,1,
                  0,0,0,0,1,0,0,0,0,1,0,0,
                  0,0,0,0,0,1,0,0,1,0,1,0,
                  0,0,0,0,0,0,1,0,0,1,0,1,
                  0,0,0,0,0,0,0,1,0,0,1,0};
bool check()
{
    bool flag = true;
    int s = 0;
    for(int i = 0 ;(flag && i < 5); i++)
    {
        int d = 0;
        for(int j = 0; j < 5; j++)
        {
            if(cc[x[i]][x[j]] == 1)/*如果两点相邻*/
            {
                d++;
            }
        }
        if( d == 0 )
        {
            flag = false;/*如果找不到一个和i这一点相邻的点那么一定不连通*/
        }
        s += d;
    }
    if( s < 8)
    {
        flag = false;/*如果度小于8就不连通*/
    }
    return flag;
}
int main()
{
    int ans = 0;
    for(x[0] = 0; x[0] < 8 ; x[0]++)
    {
        for(x[1] = x[0] + 1; x[1] < 9 ; x[1]++)
        {
            for(x[2] = x[1] + 1; x[2] < 10; x[2]++)
            {
                for(x[3] = x[2] + 1; x[3] < 11; x[3]++)
                {
                    for(x[4] = x[3] + 1; x[4] < 12; x[4]++)
                    {
                        if(check())
                        {
                            ans++;
                        }
                    }
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}

连通性判定2:行列之差

在这里插入图片描述
根据这个图我们可以看到同一行两个相邻的点之差的绝对值为1,但是存在4,5之间不相邻但差的绝对值还是为1的情况,我们就将第二行每一个点加1,第三行每一个点加2变成如下图所示
在这里插入图片描述
这里同一行每个点差的绝对值还是1,但是就不存在不同行之间差为1,相反第一行和第二行,第二行和第三行每个元素之差的绝对值为5,因此我们就可以定义一个一维数组来存放这些值:int c[12] = {1,2,3,4,6,7,8,9,11,12,13,14};,那么连通性判断就可以变成这样

bool check()
{
    bool flag = true;
    int s = 0;
    for(int i = 0 ;(flag && i < 5); i++)
    {
        int d = 0;
        for(int j = 0; j < 5; j++)
        {
            if(abs(c[x[i]] - c[x[j]]) == 1 || abs(c[x[i]] - c[x[j]]) == 5)/*如果两点相邻*/
            {
                d++;
            }
        }
        if( d == 0 )
        {
            flag = false;/*如果找不到一个和i这一点相邻的点那么一定不连通*/
        }
        s += d;
    }
    if( s < 8)
    {
        flag = false;/*如果度小于8就不连通*/
    }
    return flag;
}
完整代码2
#include <iostream>
#include <cmath>
using namespace std;
int x[5];
int c[12] = {1,2,3,4,6,7,8,9,11,12,13,14};
bool check()
{
    bool flag = true;
    int s = 0;
    for(int i = 0 ;(flag && i < 5); i++)
    {
        int d = 0;
        for(int j = 0; j < 5; j++)
        {
            if(abs(c[x[i]] - c[x[j]]) == 1 || abs(c[x[i]] - c[x[j]]) == 5)/*如果两点相邻*/
            {
                d++;
            }
        }
        if( d == 0 )
        {
            flag = false;/*如果找不到一个和i这一点相邻的点那么一定不连通*/
        }
        s += d;
    }
    if( s < 8)
    {
        flag = false;/*如果度小于8就不连通*/
    }
    return flag;
}
int main()
{
    int ans = 0;
    for(x[0] = 0; x[0] < 8 ; x[0]++)
    {
        for(x[1] = x[0] + 1; x[1] < 9 ; x[1]++)
        {
            for(x[2] = x[1] + 1; x[2] < 10; x[2]++)
            {
                for(x[3] = x[2] + 1; x[3] < 11; x[3]++)
                {
                    for(x[4] = x[3] + 1; x[4] < 12; x[4]++)
                    {
                        if(check())
                        {
                            ans++;
                        }
                    }
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}

(2)排列组合

方法1:普通排列组合

和枚举的思想差不多,都是找到一种情况然后判断连通性,不过是采用排列组合的方式,从12个里面选5个并且连通的组合,这个连通性的判断也与之前的不同,也用了度为8这个条件,但同时也不能存在孤立点,即度为8但是存在一个与谁都不相邻的点这也是不符合要求的
判断连通性

bool link_check(int a[5])
{
    set<int>a_set(a,a + 5);
    int s = 0;
    for(int i = 0 ; i < 5; i++)
    {
        int d = 0;
        if(a[i] - 4 >= 1 && a_set.count(a[i] - 4))
        {
            d++;
        }
        if(a[i] + 4 <= 12 && a_set.count(a[i] + 4))
        {
            d++;
        }
        if(a[i] != 1 && a[i] != 5 && a[i] != 9 && a_set.count(a[i] - 1))
        {/*1,5,9左边没有元素*/
            d++;
        }
        if(a[i] != 4 && a[i] != 8 && a[i] != 12 && a_set.count(a[i] + 1))
        {/*4,8,12右边没有元素*/
            d++;
        }
        if(d == 0)/*存在孤立点*/
        {
            return false;
        }
        s += d;
    }
    return s >= 8;
完整代码3
#include <iostream>
#include <cmath>
#include <set>
using namespace std;
int a[5];
int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12};/*邮票*/
int ans = 0;/*最终答案*/
bool link_check(int a[5])
{
    set<int>a_set(a,a + 5);
    int s = 0;
    for(int i = 0 ; i < 5; i++)
    {
        int d = 0;
        if(a[i] - 4 >= 1 && a_set.count(a[i] - 4))
        {
            d++;
        }
        if(a[i] + 4 <= 12 && a_set.count(a[i] + 4))
        {
            d++;
        }
        if(a[i] != 1 && a[i] != 5 && a[i] != 9 && a_set.count(a[i] - 1))
        {
            d++;
        }
        if(a[i] != 4 && a[i] != 8 && a[i] != 12 && a_set.count(a[i] + 1))
        {
            d++;
        }
        if(d == 0)/*存在孤立点*/
        {
            return false;
        }
        s += d;
    }
    return s >= 8;
}
void f(int ind,int len)
{
    if( len >= 5 )
    {
        if(link_check(a))/*如果五个点连成一片*/
        {
            ans++;
        }
        return;
    }
    for(int i = ind ; i < 12; i++)
    {
        a[len] = b[i];
        f(i + 1,len + 1);/*不同的组合情况*/
    }
}
int main()
{
    f(0,0);
    cout << ans << endl;
    return 0;
}

方法2:STL——next_permutation实现排列组合

连通性判断:判断一个图里有多少个连通区域

void mark(int a[][4],int i,int j)
{
    int tag = a[i][j];
    a[i][j] = 0;
    if(i - 1 >= 0 && a[i - 1][j] == tag) mark(a,i - 1,j);
    if(i + 1 < 3 && a[i + 1][j] == tag) mark(a,i + 1,j);
    if(j - 1 >= 0 && a[i][j - 1] == tag) mark(a,i,j - 1);
    if(j + 1 < 4 && a[i][j + 1] == tag) mark(a,i,j +1);
}
bool link_check(int b[5])
{
    set<int>b_set(b,b + 5);
    int a[3][4] = {0};
    for(int i = 0 ; i < 3; i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            if(b_set.count(i * 4 + j + 1))
            {
                a[i][j] = 1;
            }
        }
    }
    int link_num = 0;
    for(int i = 0 ; i < 3 ; i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            if(a[i][j] == 1)
            {
                link_num++;
                mark(a,i,j);
            }
        }
    }
    return (link_num == 1);/*如果只有一个连通区域就符合*/
}

使用next_permutation来实现排列组合

int ans = 0;
int num[12] = {0,0,0,0,0,0,0,1,1,1,1,1};
do
{
    int a[5] = {0};/*每次都初始化数组a*/
    for(int i = 0,j = 0 ; i < 12; i++)
    {
        if(num[i] == 1)
        {
            a[j++] = i + 1;
        }
    }
    if(link_check(a)) ans++;
}while(next_permutation(num,num + 12));
cout << ans <<endl;
完整代码4
#include <iostream>
#include <cmath>
#include <set>
#include <algorithm>
using namespace std;
int a[5];
int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12};/*邮票*/
int ans = 0;/*最终答案*/
void mark(int a[][4],int i,int j)
{
    int tag = a[i][j];
    a[i][j] = 0;
    if(i - 1 >= 0 && a[i - 1][j] == tag) mark(a,i - 1,j);
    if(i + 1 < 3 && a[i + 1][j] == tag) mark(a,i + 1,j);
    if(j - 1 >= 0 && a[i][j - 1] == tag) mark(a,i,j - 1);
    if(j + 1 < 4 && a[i][j + 1] == tag) mark(a,i,j +1);
}
bool link_check(int b[5])
{
    set<int>b_set(b,b + 5);
    int a[3][4] = {0};
    for(int i = 0 ; i < 3; i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            if(b_set.count(i * 4 + j + 1))
            {
                a[i][j] = 1;
            }
        }
    }
    int link_num = 0;
    for(int i = 0 ; i < 3 ; i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            if(a[i][j] == 1)
            {
                link_num++;
                mark(a,i,j);
            }
        }
    }
    return (link_num == 1);/*如果只有一个连通区域就符合*/
}
int main()
{
    int ans = 0;
    int num[12] = {0,0,0,0,0,0,0,1,1,1,1,1};
    do
    {
        int a[5] = {0};/*每次都初始化数组a*/
        for(int i = 0,j = 0 ; i < 12; i++)
        {
            if(num[i] == 1)
            {
                a[j++] = i + 1;
            }
        }
        if(link_check(a)) ans++;
    }while(next_permutation(num,num + 12));
    cout << ans <<endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值