不知道下面描述的问题是否有个好听的名字,所以标题不知取什么的好。
问题描述:
根据上排给出十个数,在其下排填出对应的十个数:要求下排每个数都是先前上排那十个数在下排出现的次数。
举一个例子:
数值:0,1,2,3,4,5,6,7,8,9
分配:6,2,1,0,0,0,1,0,0,0
0在下排出现了6次,1在下排出现了2次,2在下排出现了1次,3在下排出现了0次....以此类推。
程序说明:
这个问题是在http://blog.csdn.net/v_july_v/article/details/5934051看到的,一时兴起编程实现了。
程序允许输入任意个任意整数(可以是负整数,数允许重复),输出所有可行解。对于例子中的0~9可以在很快的时间内给出解来。
程序的实现应该还有可以优化的地方,欢迎讨论交流。
程序源码:
/* 根据上排给出十个数,在其下排填出对应的十个数:要求下排每个数都是先前上排那十个数在下排出现的次数。 上排的十个数如下: 【0,1,2,3,4,5,6,7,8,9】 举一个例子: 数值:0,1,2,3,4,5,6,7,8,9 分配:6,2,1,0,0,0,1,0,0,0 0在下排出现了6次,1在下排出现了2次, 2在下排出现了1次,3在下排出现了0次.... 以此类推。 */ #include <iostream> #include <vector> #include <bitset> #include <string> #include <algorithm> using namespace std; #define MAXN 100 // 支持最大输入数个数 typedef struct tagNumFeq // 表示某个数字出现多少次的条件 { int num; // 数字 int feq; // num的频率 }NumFeq; int A[MAXN]; // 原始输入数 int B[MAXN]; // 一个可靠的输出 int ACount; // 实际输入数个数 bitset<MAXN> testPos; // 测试位标志,为1表示已为相应位试取值 vector<NumFeq> nfConds; // 条件集 int posibleResultCount(0); // 可行结果计数 // 数据输入 void Input() { cout << "请输入数字个数:"; cin >> ACount; if(ACount <= 0 || ACount > MAXN) { cout << "输入错误!" << endl; exit(-1); } cout << "请输入" << ACount << "个数:" << endl; for(int i = 0; i < ACount; i++) { cin >> A[i]; } sort(A, A + ACount); // 对输入排序,方便处理 } // 获得还有多少位没有测试 int GetUnTestCount() { int re = ACount; int i = 0; while(testPos.test(i)) { re--; i++; } return re; } // 显示一个可行的结果 void ShowResult() { posibleResultCount++; cout << "----------可行结果" << posibleResultCount << "----------" << endl; cout << "a:"; for(int i = 0; i < ACount; i++) { cout << "\t" << A[i]; } cout << endl; cout << "b:"; for(int i = 0; i < ACount; i++) { cout << "\t" << B[i]; } cout << endl; cout << "------------end" << posibleResultCount << "------------" << endl; } // 获取所有条件所要求的未测试位数 int GetCondNumCount() { int condNumCount; // 所有条件所要求的数的总和 int i; // 先求出当前已测试位的情况 condNumCount = 0; for(i = 0; i < ACount; i++) { if(testPos.test(i)) { condNumCount += B[i]; } else { break; // 排序了的,如果没有置位,则表示是未测试位 } } // 减去已经满足的位 for(int j = 0; j < i; j++) { for(int k = 0; k < i; k++) { if(B[k] == A[j]) { condNumCount--; } } } return condNumCount; } // 返回是否所有条件都是零条件限制 bool IsAllZeroCond() { for(int j = 0; j < nfConds.size(); j++) { if(nfConds[j].feq != 0) { return false; } } return true; } // 查找是否有关于num的条件,有的话,把相应条件所要求的feq减1。如果该条件所要求的feq为0,则置zeroRequested为真(其默认为假) // 返回值表示是否有关于num的条件 bool MMIfNum(int num, bool &zeroRequested) { zeroRequested = false; for(int i = 0; i < nfConds.size(); i++) { if(nfConds[i].num == num) { if(nfConds[i].feq > 0) { nfConds[i].feq--; } else if(nfConds[i].feq == 0) { zeroRequested = true; } else { // 本分支理论上不会有的 cout << "程序数据异常……。" << endl; } return true; } } return false; } // 恢复因调用MMIfNum所产生的影响,hasCond是调用MMIfNum时的返回值,num是传入的参数,isZeroRequested是调用后函数对它第二个参数的设置值。 // 返回值表示是否恢复了影响 bool PPmm(bool hasCond, int num, bool isZeroRequested) { if(hasCond && !isZeroRequested) { for(int i = 0; i < nfConds.size(); i++) { if(nfConds[i].num == num) { nfConds[i].feq++; return true; } } return false; } return true; } // 还原设置某频率为num进而对num数频率的影响 // 返回值表示是否还原了影响 bool PPSetPos(int num) { for(int i = 0; i < nfConds.size(); i++) { if(nfConds[i].num == num) { nfConds[i].feq++; return true; } } return false; } // 修正添加条件前条件的默认值,处理的情况是前面已经出现过条件中所示的num值了。nf的feq域应保证能满足已经出现的num个数 void JustfyCondWhenAdd(NumFeq & nf) { for(int i = 0; i < ACount; i++) { if(testPos.test(i)) { if(B[i] == nf.num) { nf.feq--; // 这里可能减到负值 } } else { break; } } } // 获得已经设置的位中频度为num的个数 int GetSetedNumFeq(int num) { int re = 0; for(int i = 0; i < ACount; i++) { if(testPos.test(i)) { if(B[i] == num) { re++; } } else { break; } } return re; } // 打印当前数据状态,VS中调试用 string PA() { char buf[1024]; char *bp = buf; int cnt; cnt = sprintf(bp, "t");bp += cnt; for(int i = 0; i < ACount; i++) { cnt = sprintf(bp, "\t%d", testPos.test(i));bp += cnt; } cnt = sprintf(bp, "\n");bp += cnt; cnt = sprintf(bp, "A");bp += cnt; for(int i = 0; i < ACount; i++) { cnt = sprintf(bp, "\t%d", A[i]);bp += cnt; } cnt = sprintf(bp, "\n");bp += cnt; cnt = sprintf(bp, "B");bp += cnt; for(int i = 0; i < ACount; i++) { cnt = sprintf(bp, "\t%d", B[i]);bp += cnt; } cnt = sprintf(bp, "\n");bp += cnt; cnt = sprintf(bp, "d");bp += cnt; for(int i = 0; i < nfConds.size(); i++) { cnt = sprintf(bp, "\t(%d,%d)", nfConds[i].num, nfConds[i].feq);bp += cnt; } cnt = sprintf(bp, "\n\n");bp += cnt; return string(buf); } // 添加条件,纯调用push_back,只是为了方便测试用。 void AddCondition(NumFeq nf) { nfConds.push_back(nf); //printf("ad (%d,%d)\n", nf.num, nf.feq); //if(nf.num == 9 && nf.feq == 0) //{ // printf("a"); //} //printf(PA().c_str()); } // 删除条件,纯调用pop_back,只是为了方便测试用。 void DelCondition() { //NumFeq nf = nfConds[nfConds.size() - 1]; //printf("dd (%d,%d)\n", nf.num, nf.feq); nfConds.pop_back(); //printf(PA().c_str()); } // 获得前面条件所要求必须要有的数 void GetLimitedValues(vector<int> & cont) { cont.clear(); int num, feq; for(int i = 0; i < nfConds.size(); i++) { feq = nfConds[i].feq; num = nfConds[i].num; for(int j = 0; j < feq; j++) { cont.push_back(num); } } } // 获得前面条件中所要求不能出现的数 void GetForbidValues(vector<int> & cont) { cont.clear(); for(int i = 0; i < nfConds.size(); i++) { if(nfConds[i].feq == 0) { cont.push_back(nfConds[i].num); } } } // 获取已设置的值所占用的位数 int GetUsedPosCount() { int re = 0; int i; for(i = 0; i < ACount - 1; i++) { if(testPos.test(i)) { re += B[i]; } else { break; } if(testPos.test(i + 1) && A[i] == A[i + 1]) // 这里有重复值,直接跳过re+=操作 { i++; } } if(i == ACount - 1 && testPos.test(i)) // 最后两项不是相同的 { re += B[i]; } // 从这里要求所有的都要排序,并且穷举是从A[0]到A[ACount-1]逐一开始的 return re; } // 已测试的A中,是否测试过当前A[pos],即aValue,如果有,本变量代表其在A数组中的索引 int FindAPosValueIndex(int aValue) { for(int i = 0; i < ACount; i++) { if(testPos.test(i)) { if(A[i] == aValue) { return i; } } else { break; } } return -1; } // 递归开始测试各位的取值情况 void TestPos(int pos) { int condNumCount = GetCondNumCount(); int unTestCount = GetUnTestCount(); if(condNumCount > unTestCount) // 如果本轮刚开始的时候,就发现所要求的位数大于未测试的位数,则不用说,测试失败。 { return; } int lastAPosValueIndex = FindAPosValueIndex(A[pos]); if(lastAPosValueIndex >= 0) // 前面有条件限制,则与前面的保持一致,不用逐一测试 { // 设置当前位为相应值 B[pos] = B[lastAPosValueIndex]; // 在设置当前位后,看当前的条件是否满足,满足的话,递归测试其他处理 // 这里的影响是,看是否有对num为B[lastAPosValueIndex]的条件,有的话,更改条件。 bool zr1, hasCond; hasCond = MMIfNum(B[pos], zr1); // 这里的调用!!不一定!!返回为真 if(hasCond && zr1) // 有这样的条件,并且要求不能出来B[pos],则当前设置B[pos]就不符合要求了,本次测试应返回失败 { PPmm(hasCond, B[pos], zr1); // 恢复影响,本调用与MMIfNum调用相对应 // 注意,这里没有设置testPos相应标志位 return; } else { // 要么没有关于B[pos]的条件,要么有条件并且已经把条件弱化了1,假如存在弱化1,则是必要的,不需要调用PPmm恢复影响 // 到这里,在本调用开始就测试过,所要求的位数符合要求 testPos.set(pos); // 所有测试条件满足,置测试位表示通过 // 这里可以开始递归测试了 int np = (pos >= ACount - 1) ? -1 : (pos + 1); if(np == -1) // 测试到这里,所有位都顺利通过了 { if(IsAllZeroCond()) { ShowResult(); // 这里在开头知有足够的位满足条件,而这里找下一个未测试位又说没有,则说明条件全是零条件限制。直接显示结果。 } } else { // 这里的情况是,还有其他位没有测试,则选择下一个待测试位继续(在下一轮中可能有若干循环,都无所谓了,它们不应该找到一种可行结果就返回,所以这里可以调用return语句) TestPos(np); } testPos.set(pos, false); // 还原 } } else // 要没前面没有A[pos]的条件限制,则循环测试可行的取值 { // 先计算三类条件限制 // 1 有取值限制,取何值 bool valueLimited = (unTestCount == condNumCount); // 未测试位必须全部用于满足前面的条件要求 vector<int> limitedValues; if(valueLimited) { GetLimitedValues(limitedValues); } // 2 不能取何值 vector<int> forbidValues; GetForbidValues(forbidValues); // 同时获得不准出现的取值 // 3 最大可能的取值 int maxValue = ACount - GetUsedPosCount(); // 同时限定最大取值 for(int i = 0; i <= ACount; i++) // 注意,这里是可以取ACount值的,比如只有一个1, { if(i > maxValue) { break; // 直接退出循环。 } if(valueLimited // 这里取值的过滤还是有一些作用啦,只是还有等更多优化 && (find(limitedValues.begin(), limitedValues.end(), i) == limitedValues.end())) { continue; // 当前值可以直观地看出不符合要求。 } if(find(forbidValues.begin(), forbidValues.end(), i) != forbidValues.end()) // 找到不准出现的,就是零次取值条件要求的。 { continue; } B[pos] = i; // 对应一个新的条件:A[pos]得出现i次 // 添加一个新的条件(A[pos], B[pos]) int naFeq = GetSetedNumFeq(A[pos]); if(naFeq > i) // 当前A[pos]的频度不得少于naFeq,所以小于等于naFeq的i的取值是不可取的。 // 另外为等于的情况是说A[pos] == B[pos] == naFe,所以这样的情况是不可以的 { continue; } if(A[pos] == i && naFeq == i/* && i != 0*/) // 这里要考虑剔除为0的情况吗? { continue; } // 添加一个新条件 NumFeq nf; nf.num = A[pos]; nf.feq = i; // 也即是当前的B[pos] JustfyCondWhenAdd(nf); // 修正影响,如前面有出现过 AddCondition(nf); // 验证新条件是否满足当前情况 // 检测当前取值是否满足继续向里测试的条件 bool zr1, hasCond; hasCond = MMIfNum(B[pos], zr1); // 这里的调用!!不一定!!返回为真 if(hasCond && zr1) // 有这样的条件,并且要求不能出来B[pos],则当前设置B[pos]就不符合要求了,本次测试应返回失败 { // 需要考虑的清理工作是删除新加的条件 DelCondition(); continue;// 继续其他值的测试 考虑删除本行 } else { // 要么没有关于B[pos]的条件,要么有条件并且已经把条件弱化了1 // 那么这里只需要关心在没有B[pos]的条件下,需要添加一个B[pos]条件。 // 到这里,在本调用开始就测试过,所要求的位数符合要求 testPos.set(pos); // 所有测试条件满足,置测试位表示通过 // 这里可以开始递归测试了 int np = (pos >= ACount - 1) ? -1 : (pos + 1); if(np == -1) // 测试到这里,所有位都顺利通过了 { if(IsAllZeroCond()) { ShowResult(); } } else { // 这里的情况是,还有其他位没有测试,则选择下一个待测试位继续(在下一轮中可能有若干循环,都无所谓了,它们不应该找到一种可行结果就返回,所以这里可以调用return语句) TestPos(np); } DelCondition(); testPos.set(pos, false); PPSetPos(B[pos]); // 还原影响 } } } } int main(int argc, char **argv) { Input(); TestPos(0); return 0; }