我正在尝试实施一个非常基本的,非常简单的版本,从预先确定的班次列表和预先确定的人员列表中生成学校日程安排。
约束和基本设置
为了举例,让我们假设以下问题数据:
5-人,我们称他们为A,B,C,D,E,他们希望分配他们各自的时间表。
每个人都有一份之前选择的班次清单。
每周有5天,让我们假设每天有3个班次,所以,我们有一个3行,5列的矩阵。表示班次的单元格从上到下从左到右编号,从1开始。
列表:
A = {1,2,3,5,10,11}
B = {6,7,1,3,8,15}
C = {2,6,8,9,12,13}
D = {3,4,5,6,7,8}
E = {6,8,10,11,13,14}
归因于所有轮班后,时间表将是:
A - 5,10,11
B - 1,7,15
C - 2,6,12
D - 3,4,9
E - 8,13,14
我如何将这个概念概括为一个真实的案例,让我们说20个人,40个班次,每个人从8个班次的名单中选择2个。
我的代码如下:
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cmath>
using namespace std;
#define OPT_DEGREE 300
#define DEBUG 0
#define vpbvi vector<pair<bool,vector<ULL> > >
#define ULL unsigned long long
static string dict[20];
void showV(vector<ULL> & v)
{
for(int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
}
inline bool do_vectors_intersect(vector<ULL> v1, vector<ULL> v2)
{
unsigned long long int target_sz = v1.size()+v2.size();
set<ULL> s;
for(int i = 0; i < v1.size(); i++)
s.insert(v1[i]);
for(int j = 0; j < v2.size(); j++)
s.insert(v2[j]);
return !(static_cast<ULL>(s.size()) == target_sz); //True se ha intersecçao False c.c.
}
void generateAllPossibleShifts(vector<vector<ULL> > & auxiliar, vector<ULL> & _shifts, int N, int K)
{
string bitmask(K, 1); // K leading 1's
bitmask.resize(N, 0); // N-K trailing 0's
// print integers and permute bitmask
do {
vector<ULL> aux;
for (int i = 0; i < N; ++i) // [0..N-1] integers
{
if (bitmask[i])
{
aux.push_back(_shifts[i]);
if(DEBUG){
cout << " " << _shifts[i];}
}
}
if(DEBUG){
cout << endl << aux.size() << " Done. Create Pair and add to scheudle." << endl;}
pair<bool,vector<ULL> > pbvi = make_pair(true,aux);
for(int i = 0; i < aux.size();i++)
if(DEBUG){
cout << "aux[" <<i<<"] = " << pbvi.second[i] << endl;}
auxiliar.push_back(aux);
// fullVec.push_back(pbvi);
aux.resize(0); //vector is cleared here
if(DEBUG){
cout << "Clear vec" << endl;
cout << pbvi.second.size() << " Done" << endl;
cout << endl;}
} while ( prev_permutation(bitmask.begin(), bitmask.end()));
}
vector<ULL> SumVecs(vector<ULL> & a, vector<ULL> & b)
{
vector<ULL> newVec;
for(int i = 0; i < a.size(); i++)
newVec.push_back(a[i]);
for(int i = 0; i < b.size(); i++)
newVec.push_back(b[i]);
return newVec;
}
void AppendToFirst(vector<ULL> & fst, vector<ULL> & snd, int ind)
{
for(int k = 0; k < snd.size(); k++)
fst.push_back(snd[k]);
}
void OptimizeBeforeNextPassLeft(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
{
int opt = 0;
for(int i = 0; i < bsc.size(); i++)
{
for(int j = 0; j < arg2.size(); j++)
{
if(do_vectors_intersect(bsc[i],arg2[j])==true){
arg2.erase(remove(arg2.begin(), arg2.end(), arg2[j]), arg2.end());
}
}
}
}
void OptimizeBeforeNextPassRight(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
{
int opt = 0;
for(int i = bsc.size()-1; i >= 0 ; i--)
{
for(int j = 0; j < arg2.size(); j++)
{
if(do_vectors_intersect(bsc[i],arg2[j])==true){
bsc.erase(bsc.begin()+i);
}
}
}
}
void HardOptimize(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
{
int opt = 0;
for(int i = bsc.size()-1; i >= 0 ; i--)
{
for(int j = 1; j < arg2.size(); j+=2)
{
if(do_vectors_intersect(bsc[i],arg2[j])==true){
bsc.erase(bsc.begin()+i);
}
}
}
}
//Recursive Function that filters bad attempts and uses "basic" look-ahead technique to narrow search space
//while building an iterative solution. Can still be optimized.
void ExpandSearchSpace(vector<vector<vector<ULL> > > & v, vector<vector<ULL> > & buildSol, int guesslvl, vector<vector<ULL> > & placeholder)
{
if(guesslvl==4) //Num de pessoas-1
{
// cout << "inside ret " << endl << buildSol.size();
placeholder = buildSol;
return;
}
else
{
vector<vector<ULL> > BuildSolCp;
const int ssz = buildSol.size();
for(int i = 0; i < ssz;i++)
{
vector<ULL> arg1 = buildSol[i];
const int ssz2 = v[guesslvl+1].size();
//cout << "arg1.sz() = " << arg1.size() << endl;
for(int j = 0; j < ssz2;j++)
{
if(do_vectors_intersect(buildSol[i], v[guesslvl+1][j])==false){
// cout << "Iter " << guesslvl << " " << buildSol[i].size() << " ";
vector<ULL> arg2 = v[guesslvl+1][j];
// cout << "arg2 " << arg1.size() << " --- ";
vector<ULL> auxi = SumVecs(arg1,arg2);
// cout << "OLFOFKODSJFDSIHFDSFDS" << endl;
BuildSolCp.push_back(auxi);
// cout << "PUSHDED SDUSHFUDSHF"<<endl;
}
}
}
guesslvl++;
if(BuildSolCp.size()> 1000){
cout << "WE neeed optimize Jon" << endl;
for(int i = 0; i < BuildSolCp.size();i++)
showV(BuildSolCp[i]);
vector<vector<ULL> > s= v[guesslvl+1];
//vector<vector<ULL> > s3= v[guesslvl+4];
// vector<vector<ULL> > s4= v[guesslvl+5];
// OptimizeBeforeNextPassLeft(BuildSolCp, s);
OptimizeBeforeNextPassLeft(buildSol,v[guesslvl+2]);
// OptimizeBeforeNextPassRight(BuildSolCp, s);}
// OptimizeFromBothSidesAtOnce(BuildSolCp, v[guesslvl+1][j]);
}
cout << BuildSolCp.size() << " " << guesslvl << endl;
ExpandSearchSpace(v,BuildSolCp, guesslvl, placeholder);
}
// cout << "end" << endl;
}
void ShowPrettyScheudle(vector<vector<ULL> > sol)
{
vector<int> scheudle(15);
for(int j = 0; j <= 10; j+=5){
for(int i = j; i < j+5; i++)
{
cout << sol[0][i] << "\t | ";
}
cout << endl;}
}
int main()
{
static vector<vector<ULL> > WorkerVec1,WorkerVec2,WorkerVec3,WorkerVec4, WorkerVec5;
static vector<vector<ULL> > WorkerVec6,WorkerVec7,WorkerVec8,WorkerVec9, WorkerVec10;
static vector<vector<ULL> > WorkerVec11,WorkerVec12,WorkerVec13,WorkerVec14, WorkerVec15;
static vector<vector<ULL> > WorkerVec16,WorkerVec17,WorkerVec18,WorkerVec19, WorkerVec20;
vector<vector<ULL> > sol;
static vector<vector<vector<ULL>>> v;
vector<ULL> v1{5,10,11,3,1,2}, v2{1,7,3,15,6,8} ,v3{2,6,12,8,13,9}, v4{3,4,5,6,7,8},v5{6,8,10,11,13,14};
generateAllPossibleShifts(WorkerVec1, v1,6,3);
generateAllPossibleShifts(WorkerVec2, v2,6,3);
generateAllPossibleShifts(WorkerVec3, v3,6,3);
generateAllPossibleShifts(WorkerVec4, v4,6,3);
generateAllPossibleShifts(WorkerVec5, v5,6,3);
v.insert(v.end(), {WorkerVec1,WorkerVec2, WorkerVec3,WorkerVec4, WorkerVec5} );
cout << "SIZE OF v[0] in main is " << v[0].size() << endl; //20
for(int i = 0; i < v[0].size(); i++)
{
sol.push_back(v[0][i]);
}
cout << sol.size() << endl; //20
vector<vector<ULL> > plcholder;
cout << "OMG " << plcholder.size()<<endl;
ExpandSearchSpace(v,sol,0,plcholder);
cout << sol.size() << endl;
for(int i = plcholder.size()-1; i >= 0; i--){
cout << plcholder[i].size() << endl;
if(plcholder[i].size()==15){
cout << "FUCK YEA ";showV(plcholder[i]);
cout << endl << endl;
vector<vector<ULL> > vect{plcholder[i]};
ShowPrettyScheudle(vect);
break;}
}
cout << endl;
// cout << endl << Ans[0].size() << " " << Ans[1].size() << " " << Ans[2].size() << " " << Ans[3].size();
return 0;
}
我知道代码很乱,但其本质很简单:
我基本上做了一个蛮力,在每次通过时,我“积累”3个可能的班次的块,并将它们与下一组可能的班次进行比较,直到我到达结尾,只选择了可能的班次。
我尝试用一个简单的DP配方来思考,甚至用图表来思考,但是,我完全陷入困境......也许考虑个人转变而不是“转变块”更好,但是,现在,我很茫然。 我已经在这件事上待了2天了,真诚地让我感到紧张
答案 0 :(得分:3)
根据您的情况很简单,您可以尝试以下算法,
该算法在O(N * M)中工作,其中N是移位,M是人。它也始终找到解决方案,但不必然是正确的解决方案。请参阅j_random_hacker的答案。
下面是一个不检查输入数据是否有效的实现。我将shift 15更改为0以映射矢量索引。
#include <list>
#include <vector>
#include <iostream>
#include <algorithm>
class VectorComp{
public:
bool operator()(const std::vector<int>& v1,const std::vector<int>& v2){
if (v1.size()==0) return false;
if (v2.size()==0) return true;
return v1.size()<v2.size();
}
};
int main(){
std::vector<std::vector<int>> personToShift{{1,2,3,5,10,11},
{6,7,1,3,8,0},
{2,6,8,9,12,13},
{3,4,5,6,7,8},
{6,8,10,11,13,14}};
//Map shifts to persons
std::vector<std::vector<int>> shiftToPerson(15);
for (size_t i=0;i<personToShift.size();++i){
for (auto s:personToShift[i]){
shiftToPerson[s].push_back(i);
}
}
//Result vector
std::vector<std::vector<int>> res(personToShift.size());
for (size_t i=0;i<shiftToPerson.size();++i){
auto minPersonsForShift = std::min_element(shiftToPerson.begin(),
shiftToPerson.end(),
VectorComp());//Find shift with minimum persons
size_t shift=minPersonsForShift-shiftToPerson.begin();
size_t minShifts=shiftToPerson.size();
size_t minPerson=0;
for (auto person:*minPersonsForShift){//Find person in shift with minimum shifts so far
if (res[person].size()<minShifts){
minPerson=person;
minShifts=res[person].size();
}
}
res[minPerson].push_back(shift);//Update the result
shiftToPerson[shift].clear();//Mark the shift assigned by clearing the vector
}
for (size_t i=0;i<res.size();++i){//Print the result
std::cout << char(('A'+i)) << " - ";
for (auto t:res[i]){
std::cout << t << " ";
}
std::cout << std::endl;
}
}
输出:
A - 1 2 11
B - 0 7 3
C - 9 12 13
D - 4 5 6
E - 14 10 8
答案 1 :(得分:3)
假设有n个人,m个班次可用,并且您想要为每个人分配s(必须s <= m / n)班次。可以将此问题建模为在二分图中找到maximum matching的问题。匹配是一组边,使得在多个边中不使用顶点;最大匹配是最大可能大小的匹配。构建图表:
结果图是二分的,因为A中的2个顶点之间或B中的2个顶点之间没有边。您可以使用Hopcroft找到O(sqrt(| V |)| E |)时间内的最大匹配-Karp算法在第一个链接中;如果存在,这将为您提供最佳解决方案(每个人都被分配了轮班)。这里| V | = sn + m,| E | = O(snm),因为在最坏的情况下,每个人都可能列出每个班次的可能性,因此整体时间复杂度将为O(sqrt(sn + m)snm)。
Ari Hietanen's solution是一个很好的启发式方法,但即使存在一个解决方案也无法找到解决方案,如下面的示例问题实例所示。
假设我们有8个人A,B,......,H和8个班次1,2,...,8,我们希望为每个人分配一个班次,可能的班次矩阵如下所示:
12345678
A XXXXX...
B XXXX....
C XXXX....
D XXXX....
E XXXX....
F ....XXXX
G .....XXX
H .....XXX
其中X表示该行中的人可以在该列中进行移位。
Ari的算法将首先选择shift(列)5,因为只有2个人(A和F),这个班次可以被比所有其他班次更少的人使用(所有人都可以使用至少3人)。由于此时A和F都没有指定任何轮班,所以未决定是否会选择A或F来指定轮班5,所以它可能选择F - 当然,如果它通过选择那个人来打破联系它有可用的最少轮班,它会这样做(因为F有4个可能的轮班,A有5个)。但是一旦做出这个选择,它就无法解决问题,因为这意味着4个班次1,2,3和4需要以某种方式分成5个人A,B,C,D和E ,这是不可能的。为了确定解决方案确实存在,假设我们将shift 5分配给A:现在我们只需要在 4 人B,C,D中分配4个班次1,2,3和4 E,以及3人F,G和H的3班6,7和8,这很容易完成。