菠菜

敏感而豁达

趣味编程1

不知道下面描述的问题是否有个好听的名字,所以标题不知取什么的好。

问题描述:

根据上排给出十个数,在其下排填出对应的十个数:要求下排每个数都是先前上排那十个数在下排出现的次数。

举一个例子:
数值: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;
}

posted on 2012-07-12 20:46  ~菠菜~  阅读(295)  评论(0编辑  收藏  举报

导航