强盗分赃问题的计算机求解
1.问题描述n个强盗(编号1,2,3,…,n)分赃m个金币。先由强盗1提出分配方案,所有的强盗投票,超过半数支持则方案通过,否则将强盗1杀死、由强盗2继续提方案,以此类推。假设所有的强盗都足够聪明,并且有以下三个目的,优先级递降,但互相之间不能达成协议:
1、尽可能保住自己的性命;
2、尽可能得到更多的金币;
3、尽可能杀死更多的同伙。
试用计算机求解:强盗1应该采取怎样的分配方案来保住性命并获得最多的金币?
2.问题分析由于强盗是按照编号依次提出方案的,所以除了强盗n以外的强盗都有可能被杀死。由于所有的强盗之间不能有协议,故除了提出方案的强盗以外,其他的强盗都只会考虑如果杀死当前提出方案的强盗,下一个提方案者是否可以使自己获得更多的金币。所以最终问题递归到两个强盗分金币的情况,该情况下强盗1即使将金币全部给强盗2,也会被杀死。这些重复的解必须去除,否则会使得之后的计算中产生很多不必要的时间空间消耗以及更多重复的解。
3.问题求解计算过程中可以边计算边打表,这样消耗一些内存空间但是可以从统计上减少重复的计算。例如计算20个强盗分赃方案时,将计算过程中得到的3-19个强盗的分赃方案都保存在内存中,之后如果要计算3-19个强盗的分配方案就可以直接从内存中读出计算结果,计算大于20个强盗分赃的方案时也无需再计算3-20个强盗的分赃方案了。枚举组合问题可以用比较原始的算法。从1,2,…,n中选出r个元素的组合,算法伪代码如下:
填入强盗人数和金币数之后点击“分赃”按钮即可将所有的不重复的解列在界面下方的列表视图中。在列表视图的任意行上右击鼠标即可弹出快捷菜单,将已计算出结果导出到文件中或者将文件中的计算结果导入软件中。
5.存在问题本文存在的问题首先是递推并枚举组合的计算复杂度为O(n!),n为强盗人数,稍大一些就无法计算了。在Intel Centrino2 P7450(2.13GHz,双核)、2GB内存、Win7 Ulti 32位(.net 4.0)下,需要两个多小时和近200MB内存才能完成20个强盗分赃的求解。要进一步提高还需要找到不需要递推的算法,即使找不到也可以采用更好的枚举组合的算法。
上图中是本文计算出的20个强盗分20个金币的109315个不同解。
其次,即使采用递推和枚举组合,在去重时也可以对已有解建立便于查询和插入的索引,提高查询的速度。
这些问题有待进一步研究解决。
附录主要代码:/*** author: Solari* email: bianhaoqiong@163.com* 2012.8.27*/using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.IO;namespace SpoilsDivider{ public partial class Form_Main : Form { public Form_Main() { InitializeComponent(); } private ArrayList finished = null; private static int CoinNum = 20; private ArrayList DoDivide(int robberNum) { ArrayList pre = null; if (finished == null) { //如果第1次进行计算 finished = new ArrayList(); pre = new ArrayList(); ArrayList tmp = new ArrayList(); tmp.Add(new Robber(1, CoinNum)); pre.Add(tmp); finished.Add(pre); if (robberNum == 1) { return pre; } } else if (finished.Count >= robberNum) { //如果已有计算结果 return (ArrayList)(finished[robberNum - 1]); } else { //如果有1个较接近的计算结果 pre = (ArrayList)finished[finished.Count - 1]; } for (int i = finished.Count; i < robberNum; ++i) { //已有若干组i个强盗的分配方案 ArrayList tmppre = new ArrayList(); foreach (Object obj in pre) { //已有1组i个强盗的分配方案 ArrayList preres = (ArrayList)obj; DoNexRes(ref tmppre, preres, i);//产生下一个强盗的分配方案添加tmppre中 } pre = tmppre; finished.Add(pre); } return pre; } private void button_Divide_Click(object sender, EventArgs e) { try { int robberNum = int.Parse(textBox_RobberNum.Text); CoinNum = int.Parse(textBox_CoinNum.Text); if (robberNum <= 20 && robberNum >= 3) { ArrayList results = DoDivide(robberNum); int i = 0; listView_DivideRes.Items.Clear(); foreach (Object obj in results) { ArrayList result = (ArrayList)obj; ListViewItem item = new ListViewItem((++i).ToString()); String str = ""; foreach (Object obj2 in result) { str += ((Robber)obj2).coins.ToString() + ","; } item.SubItems.Add(str.Substring(0, str.Length-1)); listView_DivideRes.Items.Add(item); } } else { throw new Exception("强盗人数须在[3, 20]之间"); } } catch (Exception err) { MessageBox.Show(err.Message); } } private void DoNexRes(ref ArrayList temppre, ArrayList preres0, int i) { int sum = 0;//分给别的强盗的金币数 ArrayList preres = new ArrayList(); foreach (Object obj in preres0) { Robber ro = new Robber(((Robber)obj).id, ((Robber)obj).coins); preres.Add(ro); } preres.Sort(new RobberCoinsCom()); int j = (i + 1) / 2; int midCoins = ((Robber)preres[j - 1]).coins; int s = j - 1, d = j; for (int k = j - 2; k >= 0; --k) { if (((Robber)preres[k]).coins < midCoins) { s = k + 1; break; } } for (int k = j; k < i; ++k) { if (((Robber)preres[k]).coins > midCoins) { d = k; break; } } for (int k = 0; k < s; ++k) { ((Robber)preres[k]).coins++; sum += ((Robber)preres[k]).coins; } int n = d - s, r = j - s; int[] a = new int[r + 1]; int[] m = new int[r + 1]; for (int k = 1; k <= r; ++k) { a[k] = k; m[k] = n - r + k; } int sum1 = sum, L = 0; ArrayList preres1 = new ArrayList(); foreach (Object obj in preres) { Robber ro = new Robber(((Robber)obj).id, ((Robber)obj).coins); preres1.Add(ro); } for (int k = s; k < s+r ; ++k) { ((Robber)preres1[k]).coins++; sum1 += ((Robber)preres1[k]).coins; } for (int k = s + r; k < i; ++k) { ((Robber)preres1[k]).coins = 0; } preres1.Add(new Robber(i + 1, CoinNum - sum1)); preres1.Sort(new RobberIdCom()); bool exist = false; foreach (Object obj in temppre) { if (resEqual(preres1, (ArrayList)obj, i + 1)) { exist = true; break; } } if (!exist) { temppre.Add(preres1); } while (!isMax(a, m, r)) { sum1 = sum; L = 0; for (int k = r; k > 0; --k) { if (a[k] < m[k]) { a[k]++; L = a[k] + 1; for (int p = k + 1; p <= r; p++) { a[p] = L; L++; } break; } } preres1 = new ArrayList(); foreach (Object obj in preres) { Robber ro = new Robber(((Robber)obj).id, ((Robber)obj).coins); preres1.Add(ro); } for (int p = 1; p <= r; ++p) { for (int k = s; k < d; ++k) { if (k == a[p] + s - 1) { ((Robber)preres1[k]).coins++; sum1 += ((Robber)preres1[k]).coins; } else { ((Robber)preres1[k]).coins = 0; } } } for (int k = d; k < i; ++k) { ((Robber)preres1[k]).coins = 0; } preres1.Add(new Robber(i + 1, CoinNum - sum1)); preres1.Sort(new RobberIdCom()); exist = false; foreach (Object obj in temppre) { if (resEqual(preres1, (ArrayList)obj, i + 1)) { exist = true; break; } } if (!exist) { temppre.Add(preres1); } } } private bool isMax(int[] a, int[] m, int r) { bool flag = true; for (int i = 1; i <= r; ++i) { if (a[i] < m[i]) { flag = false; break; } } return flag; } private bool resEqual(ArrayList l1, ArrayList l2, int len) { bool res = true; for (int i = 0; i < len; ++i) { if (((Robber)l1[i]).coins != ((Robber)l2[i]).coins) { res = false; break; } } return res; } public partial class Robber { public Robber(int i, int c) { id = i; coins = c; } public int id; public int coins; } public partial class RobberIdCom : IComparer { public int Compare(object x, object y) { return ((Robber)y).id - ((Robber)x).id; } } public partial class RobberCoinsCom : IComparer { public int Compare(object x, object y) { if (((Robber)x).coins == ((Robber)y).coins) { return ((Robber)y).id - ((Robber)x).id; } return ((Robber)x).coins - ((Robber)y).coins; } }}引用或转载请注明原文,谢谢!