音乐家观演问题编程求解(二)
音乐家观演问题描述参见:音乐家观演问题求解与拓展分析 和 音乐家观演问题通解初探。
此前的编程求解尝试见:音乐家观演问题编程求解(一)。
这里,新的尝试采用的求解思路是实现一种通用的构造法,去直接构造出一个可能的最优解。新的console程序的实现代码如下:
AudPerf.h
1 #ifndef _AUD_PERF_H 2 #define _AUD_PERF_H 3 4 #include <stdio.h> 5 #include <stdint.h> 6 #include <string> 7 #include <vector> 8 #include <set> 9 #include <map> 10 #include <iostream> 11 #include <windows.h> 12 13 typedef unsigned char ucell; 14 typedef std::set<ucell> AudSet; 15 typedef std::set<ucell> PerfSet; 16 17 struct Show { 18 AudSet auds; 19 PerfSet perfs; 20 }; 21 22 class AudPerfSlu 23 { 24 public: 25 ucell m_musiSum; 26 ucell m_showSum; 27 std::vector<Show> m_vecShow; 28 std::map<ucell, PerfSet> m_mapPair; 29 30 AudPerfSlu(ucell sum) : m_musiSum(sum), m_showSum(2) { 31 PerfSet empty; 32 for (ucell idx = 0; idx < m_musiSum; ++idx) 33 m_mapPair[idx] = empty; 34 } 35 36 void reset() { 37 m_vecShow.clear(); 38 for (ucell idx = 0; idx < m_musiSum; ++idx) 39 m_mapPair[idx].clear(); 40 } 41 42 bool tryShows(ucell val); 43 void freshPairs(); 44 bool condMet(); 45 void buildNewShow(); 46 void genNextShow(); 47 ucell calcNextAud(ucell beign, Show& show); 48 void build1stShow(); 49 void showDetail(); 50 }; 51 52 #endif
AudPerf.cpp
1 #include "AudPerf.h" 2 3 void AudPerfSlu::freshPairs() 4 { 5 if (m_vecShow.empty()) 6 return; 7 AudSet& auds = m_vecShow[m_vecShow.size() - 1].auds; 8 AudSet& perfs = m_vecShow[m_vecShow.size() - 1].perfs; 9 for (AudSet::iterator itA = auds.begin(); itA != auds.end(); ++itA) 10 for (PerfSet::iterator itP = perfs.begin(); itP != perfs.end(); ++itP) 11 m_mapPair[*itA].insert(*itP); 12 } 13 14 bool AudPerfSlu::condMet() 15 { 16 for (ucell idx = 0; idx < m_musiSum; ++idx) { 17 if (m_mapPair[idx].size() < m_musiSum - 1) 18 return false; 19 } 20 return true; 21 } 22 23 inline void genPerfSet(const AudSet& auds, ucell musiSum, PerfSet& perfs) 24 { 25 for (ucell idx = 0; idx < musiSum; ++idx) 26 if (auds.find(idx) == auds.end()) 27 perfs.insert(idx); 28 } 29 30 void AudPerfSlu::build1stShow() 31 { 32 Show show; 33 ucell audSum = (m_musiSum / 2); 34 for (ucell idx = 0; idx < audSum; ++idx) { 35 show.auds.insert(idx); 36 } 37 genPerfSet(show.auds, m_musiSum, show.perfs); 38 m_vecShow.push_back(show); 39 freshPairs(); 40 } 41 42 ucell AudPerfSlu::calcNextAud(ucell begin,Show& show) 43 { 44 ucell aud = begin; 45 size_t level = m_mapPair[begin].size(); 46 for (ucell idx = begin + 1; idx < m_musiSum; ++idx) { 47 if (show.auds.find(idx) != show.auds.end()) 48 continue; 49 if (show.perfs.find(idx) != show.perfs.end()) 50 continue; 51 if (level > m_mapPair[idx].size()) { 52 level = m_mapPair[idx].size(); 53 aud = idx; 54 } 55 } 56 return aud; 57 } 58 59 void AudPerfSlu::genNextShow() 60 { 61 Show newShow; 62 for (ucell midx = 0; midx < m_musiSum; ++midx) { 63 if (newShow.auds.size() == m_musiSum - m_musiSum / 2) 64 break; 65 if (newShow.auds.size() == 1 && m_musiSum == 3) 66 break; 67 if (newShow.perfs.find(midx) != newShow.perfs.end()) 68 continue; 69 if (m_mapPair[midx].size() == m_musiSum - 1) 70 continue; 71 /// among auds of 1st show 72 if (m_vecShow[0].auds.find(midx) != m_vecShow[0].auds.end()) { 73 newShow.auds.insert(midx); 74 AudSet& firstAuds = m_vecShow[0].auds; 75 for (AudSet::iterator it = firstAuds.begin(); it != firstAuds.end(); ++it) { 76 if (*it != midx) 77 newShow.perfs.insert(*it); 78 } 79 continue; 80 } 81 ucell aud = calcNextAud(midx, newShow); 82 newShow.auds.insert(aud); 83 if (midx != aud) { 84 --midx; 85 } 86 } 87 genPerfSet(newShow.auds, m_musiSum, newShow.perfs); 88 m_vecShow.push_back(newShow); 89 freshPairs(); 90 } 91 92 void AudPerfSlu::buildNewShow() 93 { 94 if (m_vecShow.size() == 0) 95 build1stShow(); 96 else 97 genNextShow(); 98 } 99 100 bool AudPerfSlu::tryShows(ucell val) 101 { 102 ucell half = m_musiSum / 2; 103 if (val * half * (m_musiSum - half) < m_musiSum * (m_musiSum - 1)) { 104 std::cout << " No need to try " << (int)val << " shows." << std::endl; 105 return false; 106 } 107 std::cout << " Start to try " << (int)val << " shows." << std::endl; 108 m_showSum = val; 109 reset(); 110 while (true) { 111 if (m_vecShow.size() == m_showSum) 112 return condMet(); 113 buildNewShow(); 114 } 115 return false; 116 } 117 118 void printShow(const Show& show) 119 { 120 for (AudSet::iterator it = show.auds.begin(); it != show.auds.end(); ++it) 121 std::cout << " " << (int)(*it); 122 std::cout << " -"; 123 for (PerfSet::iterator it = show.perfs.begin(); it != show.perfs.end(); ++it) 124 std::cout << " " << (int)(*it); 125 std::cout << std::endl; 126 } 127 128 void AudPerfSlu::showDetail() 129 { 130 std::cout << "Total Shows: " << m_vecShow.size() << std::endl; 131 for (ucell idx = 0; idx < m_vecShow.size(); ++idx) 132 printShow(m_vecShow[idx]); 133 } 134 135 int main() 136 { 137 printf("The sum of musicians please: "); 138 std::string strInput; 139 getline(std::cin, strInput); 140 unsigned long raw = strtoul(strInput.c_str(), 0 ,10); 141 if (raw >= 255 || raw < 2) { 142 printf("\n The sum of musicians should be in [2,254].\n"); 143 getline(std::cin, strInput); 144 return 0; 145 } 146 AudPerfSlu oSlu((ucell)raw); 147 ucell showSum = (raw > 6 ? raw / 2 : 2); 148 DWORD tick = GetTickCount(); 149 while (showSum <= raw) { 150 bool ret = oSlu.tryShows(showSum); 151 if (ret) { 152 oSlu.showDetail(); 153 std::cout << " Done." << std::endl; 154 break; 155 } 156 ++showSum; 157 } 158 std::cout << " Time used(ms): " << GetTickCount() - tick << std::endl; 159 getline(std::cin, strInput); 160 return 0; 161 }
运行效果分析
H:\Read\prime\Debug>AudPerf
The sum of musicians please: 9
Start to try 4 shows.
Start to try 5 shows.
Start to try 6 shows.
Total Shows: 6
0 1 2 3 - 4 5 6 7 8
0 4 5 6 7 - 1 2 3 8
1 4 5 6 8 - 0 2 3 7
2 4 5 7 8 - 0 1 3 6
3 4 6 7 8 - 0 1 2 5
5 6 7 8 - 0 1 2 3 4
Done.
Time used(ms): 31
H:\Read\prime\Debug>AudPerf
The sum of musicians please: 10
Start to try 5 shows.
Start to try 6 shows.
Total Shows: 6
0 1 2 3 4 - 5 6 7 8 9
0 5 6 7 8 - 1 2 3 4 9
1 5 6 7 9 - 0 2 3 4 8
2 5 6 8 9 - 0 1 3 4 7
3 5 7 8 9 - 0 1 2 4 6
4 6 7 8 9 - 0 1 2 3 5
Done.
Time used(ms): 47
H:\Read\prime\Debug>AudPerf
The sum of musicians please: 19
Start to try 9 shows.
Start to try 10 shows.
Start to try 11 shows.
Total Shows: 11
0 1 2 3 4 5 6 7 8 - 9 10 11 12 13 14 15 16 17 18
0 9 10 11 12 13 14 15 16 17 - 1 2 3 4 5 6 7 8 18
1 9 10 11 12 13 14 15 16 18 - 0 2 3 4 5 6 7 8 17
2 9 10 11 12 13 14 15 17 18 - 0 1 3 4 5 6 7 8 16
3 9 10 11 12 13 14 16 17 18 - 0 1 2 4 5 6 7 8 15
4 9 10 11 12 13 15 16 17 18 - 0 1 2 3 5 6 7 8 14
5 9 10 11 12 14 15 16 17 18 - 0 1 2 3 4 6 7 8 13
6 9 10 11 13 14 15 16 17 18 - 0 1 2 3 4 5 7 8 12
7 9 10 12 13 14 15 16 17 18 - 0 1 2 3 4 5 6 8 11
8 9 11 12 13 14 15 16 17 18 - 0 1 2 3 4 5 6 7 10
10 11 12 13 14 15 16 17 18 - 0 1 2 3 4 5 6 7 8 9
Done.
Time used(ms): 125
H:\Read\prime\Debug>AudPerf
The sum of musicians please: 20
Start to try 10 shows.
Start to try 11 shows.
Total Shows: 11
0 1 2 3 4 5 6 7 8 9 - 10 11 12 13 14 15 16 17 18 19
0 10 11 12 13 14 15 16 17 18 - 1 2 3 4 5 6 7 8 9 19
1 10 11 12 13 14 15 16 17 19 - 0 2 3 4 5 6 7 8 9 18
2 10 11 12 13 14 15 16 18 19 - 0 1 3 4 5 6 7 8 9 17
3 10 11 12 13 14 15 17 18 19 - 0 1 2 4 5 6 7 8 9 16
4 10 11 12 13 14 16 17 18 19 - 0 1 2 3 5 6 7 8 9 15
5 10 11 12 13 15 16 17 18 19 - 0 1 2 3 4 6 7 8 9 14
6 10 11 12 14 15 16 17 18 19 - 0 1 2 3 4 5 7 8 9 13
7 10 11 13 14 15 16 17 18 19 - 0 1 2 3 4 5 6 8 9 12
8 10 12 13 14 15 16 17 18 19 - 0 1 2 3 4 5 6 7 9 11
9 11 12 13 14 15 16 17 18 19 - 0 1 2 3 4 5 6 7 8 10
Done.
Time used(ms): 125
相比前一次地毯式排查的尝试,这里直接构造一种可行实例的方法,求解是很快的。但给出的解是否为满足最少演出的解还有待进一步考察。
比如,上面的一组示例里,严格来说,只是给出了如下一些结论:
F(9) ≤ 6, F(10) ≤ 6, F(19) ≤ 11, F(20) ≤ 11
计算出F(100) ≤ 51也仅需6秒钟左右。
n=10的构造实例分析
上述代码对n=10的情形会构造出满足[6,10]满观演的如下一个实例:
Total Shows: 6
0 1 2 3 4 - 5 6 7 8 9
0 5 6 7 8 - 1 2 3 4 9
1 5 6 7 9 - 0 2 3 4 8
2 5 6 8 9 - 0 1 3 4 7
3 5 7 8 9 - 0 1 2 4 6
4 6 7 8 9 - 0 1 2 3 5
第1场演出的安排很简单,让序号小的5位音乐家观看序号大的5位音乐家的表演。每位音乐家有一个观演计数,以记录该音乐家观看了多少人的表演。第一场演出之后,0、1、2、3、4的观演计数都为5,而5、6、7、8、9的观演计数都为0。
随后5场演出的安排也很简单,第1场以观众身份出现的0、1、2、3、4被看成一个小团体,随后这个小团体的成员逐个以观众身份出现观看这个小团体的其他成员的表演:
先看第2场,考虑序号最小的0号音乐家,他已经观看了5、6、7、8、9一共5人的表演,还没有观看1、2、3、4的表演,于是把0安排为观众,而把1、2、3、4安排为表演者,观众席还剩余4个位置,把还未安排的音乐家以其观演计数最小优先(观演计数并列最小时则以序号小者优先)安排到观众席。因此,5、6、7、8被安排到观众席,9被安排再次表演。第2场演出安排构造完成。
第3场,序号最小的0观演计数已经达到9(即观看了其余所有人的表演),进而考虑1号音乐家,他的观演计数为5安排到观众席,而0、2、3、4被安排为表演者。此时9的观演计数最小,9被安排到观众席;剩余的5、6、7、8的观演计数都为1,于是5、6、7被安排到观众席,8被安排为表演者。
后面3场演出的构造情形类似,不再赘述。
仔细观察上述n=10的构造实例所呈现出来的对称性,并由此推及n=2m的一般情形(m > 2),就可以证明以下结论:
F(2m) ≤ m+1, m > 2
进一步探究下去,或许可以最终证明出以下通解公式:
F(n)=[(n+1)/2]+1, n>1, n≠4
F(n)=[(n+1)/2]+2, n=4
这些随后另起专题探究。
n=9的构造实例分析
上述代码对n=9的情形会构造出满足[6,9]满观演的如下一个实例:
Total Shows: 6
0 1 2 3 - 4 5 6 7 8
0 4 5 6 7 - 1 2 3 8
1 4 5 6 8 - 0 2 3 7
2 4 5 7 8 - 0 1 3 6
3 4 6 7 8 - 0 1 2 5
5 6 7 8 - 0 1 2 3 4
n=2m+1的情形,在构造上要复杂一些,因为要兼顾m对m+1的演出和m+1对m的演出。
这里m=4,第1场演出,0、1、2、3为一个小团体A,观看了另一个小团体B(4、5、6、7、8)的表演。
随后的4场演出,小团体A的成员逐个再次以观众身份出现观看A中其他成员的表演,而B中的成员根据其观演计数和序号按从小到大的顺序加入到空缺的观众席上。
最后一场演出是必需的,因为当时5、6、7、8的观演计数都是7,都还差4的表演没看到。就是说最后一场演出实际可以安排为
5 6 7 8 - 4
另外,n=9的构造实例,实际上可以从n=10的构造实例变换而来。具体看一下:
0 1 2 3 4 - 5 6 7 8 9
0 5 6 7 8 - 1 2 3 4 9
1 5 6 7 9 - 0 2 3 4 8
2 5 6 8 9 - 0 1 3 4 7
3 5 7 8 9 - 0 1 2 4 6
4 6 7 8 9 - 0 1 2 3 5
从n=10的构造实例中去掉4:
0 1 2 3 - 5 6 7 8 9
0 5 6 7 8 - 1 2 3 9
1 5 6 7 9 - 0 2 3 8
2 5 6 8 9 - 0 1 3 7
3 5 7 8 9 - 0 1 2 6
6 7 8 9 - 0 1 2 3 5
易知新得到的这个实例满足[6,9]满观演。再把大于4的序号做减1处理:
0 1 2 3 - 4 5 6 7 8
0 4 5 6 7 - 1 2 3 8
1 4 5 6 8 - 0 2 3 7
2 4 5 7 8 - 0 1 3 6
3 4 6 7 8 - 0 1 2 5
5 6 7 8 - 0 1 2 3 4
就得到了n=9的构造实例。
更一般地,上述的程序实现的构造算法对于n=2m+1的构造实例,总是等价于n=2m+2的构造实例。