【Java与数学】在国庆或春节七天安排四人值班,每人值一到两天,问有多少种方案?

【数学解法】

先把7天分4段,每段1到2天。

可以试着分一下,发现只允许一段分配一天,另外三段都是两天。

如果有两段一天,那么剩下五天分两段,怎么分都超过限制。

三段都一天更不可能。

故7天必备分成2,2,2,1这四段。

1这段从7选1过来,C_7_1;

剩下三段是C_6_2*C_4_2*C_2_2;

四段分四人,是C_4_1*C_3_1*C_2_1*C_1_1=A_4_4,这步实际上就是四人的全排列;

综上,分配方案分两步:一步七天分四段,二步四段分四人。

Sum=(C_7_1*C_6_2*C_4_2*C_2_2)*(A_4_4)=630*24=15120

故总方案有15120种。

网上此题还有一种解法是2520种方案,但经过程序验证可以发现15120种是正确答案。
【代码解法】

辅助类Arranger,此类用来作排列:

package test230916;

import java.util.ArrayList;
import java.util.List;

/**
 * 用于产生排列结果的工具类
 * 从n个元素中取出m个元素,按照一定的顺序排成一列。得到所有排列的方案
 */
class Arranger {
    // 保存在内部的对原始元素数组的引用
    private int[] arr;

    // 总计多少元素,此即数组长度
    private final int n;

    // 选多少个
    private final int m;

    // 返回结果
    private List<List<Integer>> results;

    /**
     * 构造函数一
     * 这个构造函数是用于全排列的(n=m=数组长度)
     *
     * @arr 原始元素数组
     */
    public Arranger(int[] arr) {
        this.arr = arr;
        this.n = arr.length;
        this.m = arr.length;

        this.results = new ArrayList<>();
        doArrange(new ArrayList<>());
    }

    /**
     * 构造函数二
     * 这个构造函数是用于部分排列的(m<n=数组长度)
     *
     * @param arr    原始元素数组
     * @param selCnt 选多少个
     */
    public Arranger(int[] arr, int selCnt) {
        this.arr = arr;
        this.n = arr.length;
        this.m = selCnt;
        if (m > n) {
            throw new ArrayIndexOutOfBoundsException("m:" + m + " >n:" + n);
        }

        this.results = new ArrayList<>();
        doArrange(new ArrayList<>());
    }

    /**
     * 使用递归进行全排列,结果放在results中
     *
     * @param initialList 初始链表
     */
    private void doArrange(List<Integer> initialList) {
        List<Integer> innerList = new ArrayList<>(initialList);

        if (m == initialList.size()) {
            results.add(innerList);
        }

        for (int i = 0; i < arr.length; i++) {
            if (innerList.contains(arr[i])) {
                continue;
            }

            innerList.add(arr[i]);
            doArrange(innerList);
            innerList.remove(innerList.size() - 1);
        }
    }

    /**
     * 获得结果链表的引用
     *
     * @return
     */
    public List<List<Integer>> getResults() {
        return results;
    }

    // 测试
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4};
        Arranger arranger = new Arranger(numbers);

        System.out.println("四元素全排列示例:");
        int idx = 0;
        for (List<Integer> re : arranger.getResults()) {
            System.out.println(String.format("%02d", ++idx) + "." + re);
        }

        /*Arranger arranger2 = new Arranger(numbers, 2);
        System.out.println("\n四选二排列示例:");
        idx = 0;
        for (List<Integer> re : arranger2.getResults()) {
            System.out.println(String.format("%02d", ++idx) + "." + re);
        }*/
    }
}

辅助类Combination,此类用于作组合:

package test230916;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 数学中排列组合中的组合器实现
 * 传入一个数组及选择的个数,传出所有选择方案
 */
class Combination {
    /**
     * 用于存放中间结果
     */
    private Stack<Integer> stack;
    
    /**
     * 用于存放结果
     */
    private List<List<Integer>> results;
    
    /**
     * 构造函数
     * @param arr 进行组合的元素
     * @param count 选多少个
     */
    public Combination(int[] arr,int count) {
        if(count>arr.length) {
            throw new ArrayIndexOutOfBoundsException(count+">"+arr.length);
        }
        
        stack = new Stack<>();
        results=new ArrayList<>();
        doSelect(arr,count,0,0);
    }
    
    /**
     * 进行选择
     * @param arr 目标数组
     * @param expect 期望选择数量
     * @param actual 实际选择数量
     * @param current 当前下标
     */
    private void doSelect(int[] arr, int expect, int actual, int current) {
        if(actual == expect) {
            List<Integer> list=new ArrayList<>();
            
            for(int i:stack) {
                list.add(i);
            }
            
            results.add(list);
            
            return;
        }
         
        for(int i=current;i<arr.length;i++) {
            if(!stack.contains(arr[i])) {
                stack.add(arr[i]);
                doSelect(arr, expect, actual+1, i);
                stack.pop();
            }
        }
    }
    
    /**
     * 取得组合结果
     * @return
     */
    public List<List<Integer>> getResults(){
        return results;
    }
    
    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        final int[] arr= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30};
        final int count=2;
        
        Combination c=new Combination(arr,count);
        List<List<Integer>> results=c.getResults();
        
        int idx=0;
        for(List<Integer> res:results) {
            System.out.println(String.format("%02d", ++idx) +"."+res);
        }
    }
}

主类DutyPlan:

package test230916;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * 将国庆/春节七天假期分四段,每段一到两天,分给四人值班,看具体有几种方案
 * @author 逆火
 * @date 230916
 */
public class DutyPlan {
    public static void main(String[] args) {
        int idx = 0;
        List<String> sentences=new ArrayList<>();
        
        final String[] names={"甲","乙","丙","丁"};
        int[] idxes = {0,1, 2, 3,};
        Arranger arranger = new Arranger(idxes);
        
        // 代表假期第几天
        final int[] arr1= {1,2,3,4,5,6,7};
        
        Combination c1=new Combination(arr1,1);
        List<List<Integer>> results1=c1.getResults();
        for(List<Integer> result1:results1) {
            // result1=C_7_1
            int[] arr2=minus(arr1,result1);
            //System.out.println(getArrDesc(arr2));
            
            Combination c2=new Combination(arr2,2);
            List<List<Integer>> results2=c2.getResults();
            for(List<Integer> result2:results2) {
                // result1=C_6_2
                int[] arr3=minus(arr2,result2);
                //System.out.println(getArrDesc(arr3));
                
                Combination c3=new Combination(arr3,2);
                List<List<Integer>> results3=c3.getResults();
                for(List<Integer> result3:results3) {
                    // result3=C_4_2
                    
                    int[] arr4=minus(arr3,result3);
                    // arr4=C_2_2
                    
                    for (List<Integer> re : arranger.getResults()) {
                        //System.out.print(String.format("%02d.", ++idx));
                        //System.out.print(names[re.get(0)]+"值第"+result1+"天,");
                        //System.out.print(names[re.get(1)]+"值第"+result2+"天,");
                        //System.out.print(names[re.get(2)]+"值第"+result3+"天,");
                        //System.out.println(names[re.get(3)]+"值第"+getArrDesc(arr4)+"天.");
                        
                        String sentence=String.format("%02d.", ++idx);
                        sentence+=names[re.get(0)]+"值第"+result1+"天,";
                        sentence+=names[re.get(1)]+"值第"+result2+"天,";
                        sentence+=names[re.get(2)]+"值第"+result3+"天,";
                        sentence+=names[re.get(3)]+"值第"+getArrDesc(arr4)+"天.";
                        sentences.add(sentence);
                    }
                }
            }
        }
        
        try {
            FileOutputStream writerStream = new FileOutputStream("c:\\hy\\DutyPlan230916.txt");
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(writerStream, "UTF-8"));
            for(String s:sentences) {
                writer.write(s+"\n");
            }
            writer.close();
        }catch(Exception ex) {
            ex.printStackTrace();
        }
        
        System.out.println("End");
    }
    
    /**
     * 返回整形数组的描述,打印数组时用的
     * @param arr
     * @return
     */
    private static String getArrDesc(int[] arr) {
        String[] strs=new String[arr.length];

        int idx=0;
        for(int i:arr) {
            strs[idx++]=""+i;
        }
        
        return "["+String.join(",", strs)+"]";
    }
    
    /**
     * 返回存在于arr中,不存在于result的元素组成的数组
     * @param arr
     * @param result
     * @return
     */
    private static int[] minus(int[] arr,List<Integer> result) {
        int[] retval=new int[arr.length-result.size()];
        
        int idx=0;
        for(int i:arr) {
            if(result.contains(i)==false) {
                retval[idx++]=i;
            }
        }
        
        return retval;
    }
}

 输出总15120种方案(下载地址:https://files.cnblogs.com/files/heyang78/DutyPlan230916.rar?t=1694855168&download=true),以下是截取的部分:

15091.丁值第[7]天,甲值第[5, 6]天,乙值第[2, 4]天,丙值第[1,3]天.
15092.丁值第[7]天,甲值第[5, 6]天,丙值第[2, 4]天,乙值第[1,3]天.
15093.丁值第[7]天,乙值第[5, 6]天,甲值第[2, 4]天,丙值第[1,3]天.
15094.丁值第[7]天,乙值第[5, 6]天,丙值第[2, 4]天,甲值第[1,3]天.
15095.丁值第[7]天,丙值第[5, 6]天,甲值第[2, 4]天,乙值第[1,3]天.
15096.丁值第[7]天,丙值第[5, 6]天,乙值第[2, 4]天,甲值第[1,3]天.
15097.甲值第[7]天,乙值第[5, 6]天,丙值第[3, 4]天,丁值第[1,2]天.
15098.甲值第[7]天,乙值第[5, 6]天,丁值第[3, 4]天,丙值第[1,2]天.
15099.甲值第[7]天,丙值第[5, 6]天,乙值第[3, 4]天,丁值第[1,2]天.
15100.甲值第[7]天,丙值第[5, 6]天,丁值第[3, 4]天,乙值第[1,2]天.
15101.甲值第[7]天,丁值第[5, 6]天,乙值第[3, 4]天,丙值第[1,2]天.
15102.甲值第[7]天,丁值第[5, 6]天,丙值第[3, 4]天,乙值第[1,2]天.
15103.乙值第[7]天,甲值第[5, 6]天,丙值第[3, 4]天,丁值第[1,2]天.
15104.乙值第[7]天,甲值第[5, 6]天,丁值第[3, 4]天,丙值第[1,2]天.
15105.乙值第[7]天,丙值第[5, 6]天,甲值第[3, 4]天,丁值第[1,2]天.
15106.乙值第[7]天,丙值第[5, 6]天,丁值第[3, 4]天,甲值第[1,2]天.
15107.乙值第[7]天,丁值第[5, 6]天,甲值第[3, 4]天,丙值第[1,2]天.
15108.乙值第[7]天,丁值第[5, 6]天,丙值第[3, 4]天,甲值第[1,2]天.
15109.丙值第[7]天,甲值第[5, 6]天,乙值第[3, 4]天,丁值第[1,2]天.
15110.丙值第[7]天,甲值第[5, 6]天,丁值第[3, 4]天,乙值第[1,2]天.
15111.丙值第[7]天,乙值第[5, 6]天,甲值第[3, 4]天,丁值第[1,2]天.
15112.丙值第[7]天,乙值第[5, 6]天,丁值第[3, 4]天,甲值第[1,2]天.
15113.丙值第[7]天,丁值第[5, 6]天,甲值第[3, 4]天,乙值第[1,2]天.
15114.丙值第[7]天,丁值第[5, 6]天,乙值第[3, 4]天,甲值第[1,2]天.
15115.丁值第[7]天,甲值第[5, 6]天,乙值第[3, 4]天,丙值第[1,2]天.
15116.丁值第[7]天,甲值第[5, 6]天,丙值第[3, 4]天,乙值第[1,2]天.
15117.丁值第[7]天,乙值第[5, 6]天,甲值第[3, 4]天,丙值第[1,2]天.
15118.丁值第[7]天,乙值第[5, 6]天,丙值第[3, 4]天,甲值第[1,2]天.
15119.丁值第[7]天,丙值第[5, 6]天,甲值第[3, 4]天,乙值第[1,2]天.
15120.丁值第[7]天,丙值第[5, 6]天,乙值第[3, 4]天,甲值第[1,2]天.

以上15120种方案用文本编辑器打开,随意找一行(去掉行号部分),在整体中查找,发现没有重复行,说明15120这个方案是对的。

 END

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值