多次分组人员,其中所有成员在同一组中至少相遇一次

时间:2017-07-15 01:44:02

标签: dynamic-programming combinatorics alloy

我希望将人们分组到较小的子群组中,并且在连续会话中多次改组后,让所有人至少相遇一次。

  1. 在每个会话中,人们被分成不同数量的群体。每个人都必须在每次会议中加入一个小组。
  2. 组大小应最接近(人数)/(组数)。不应该有太多人或太多人。
  3. 会议一直持续到每对人至少相遇一次为止。
  4. 优选地,应该使相同对彼此相遇的次数最小化。
  5. 以下是针对11人(编号0-10)和3组(3列)的此问题的答案。它需要5个会话。

    Session 1: 3,6,8,10     0,1,7,9     2,4,5
    Session 2: 3,5,7,8      0,1,2,10    4,6,9
    Session 3: 0,1,6,8      2,3,4,9     5,7,10
    Session 4: 0,3,5,9      1,4,8,10    2,6,7
    Session 5: 1,3,5,6      2,8,9,10    0,4,7
    

    Members of two groups of different size must meet each other (1v1, once)

    上面的问题是类似的,但我想让人们只是在一个更大的群体中相遇,而不是一对一。

    以下是我使用Alloy的方法。这适用于少数人(~15)和组(~2),但是当尺寸增加时,它会迅速导致计算时间爆炸。我需要计算~25人和~5组。

    module Teaming
    
    sig Person { groups: some Group }
    
    sig Group { people: some Person }
    
    sig Session { groups: some Group }
    
    one sig Sessions { sessions: some Session }
    
    sig GroupPerSession {}
    
    -- Tree structures
    fact {
        all s: Session | s in Sessions.sessions
        all g: Group | g in Session.groups
        all s: Session | all p:Person | p in s.groups.people
        people =~ groups
    }
    
    -- The total number of people
    fact {
        all s: Session | #s.groups.people = #Person
    }
    
    -- The number of groups per session
    fact {
        all s: Session | #s.groups = #GroupPerSession
    }
    
    -- The number of people in a group
    fact {
        all g: Group | (#g.people) >= div[#(Person), #(GroupPerSession)] and (#g.people) <= add[div[#Person,#GroupPerSession],1]
    }
    
    -- Mutually exclusive grouping in a session
    fact separate {
        all s: Session | all disj a,b: s.groups | no p: Person | p in a.people and p in b.people
    }
    
    -- Every pair of people meets somewhere
    pred sameGroup {
        all disj a,b: Person | some g: Group | a in g.people and b in g.people
    }
    
    -- The same people should not meet too many times
    fact sameGroupNotTooMuch {
        all disj a,b: Person | #{a.groups & b.groups} <= 3
    }
    
    run sameGroup for 6 Int, 5 Session, 15 Group, exactly 3 GroupPerSession, exactly 16 Person
    run sameGroup for 6 Int, 6 Session, 24 Group, exactly 4 GroupPerSession, exactly 18 Person
    run sameGroup for 6 Int, 7 Session, 35 Group, exactly 5 GroupPerSession, exactly 18 Person
    

    我认为动态编程应该可行,但我找不到具体的东西。任何改进Alloy代码或其他算法的指针都会很棒。

2 个答案:

答案 0 :(得分:2)

这是我快速解决这个问题的方法。 总的来说,实例的生成似乎更快,但在尝试将> 20个人分配到&gt; 4个组时仍然难以完成。

module Teaming

one sig Settings{
    maxEncounter:Int,
    minGroupSize:Int,
    maxGroupSize:Int
}{
// Manually filling values there helps (1)reducing the integer bit-width needed (2) decrease the complexity (in terms of clauses)
  maxEncounter=4
 //minGroupSize=5
 //maxGroupSize=5
  minGroupSize=div[#Person, #Group]
  maxGroupSize=add[minGroupSize,1]
}

sig Session{}{
    Group.people[this]=Person // all person are assigned in group during a session
    no disj g1,g2 :Group| g1.people[this]  & g2.people [this] !=none // a person can't be in two disjoint groups of a same session

}

sig Group { 
    people: Session some -> some Person 
}{
   all s:Session|  #people[s]<= Settings.maxGroupSize and   #people[s]>=Settings.minGroupSize 
}

sig Person {}


pred allMeet {
    all disj a,b: Person | people. a & people.b != none->none
}

pred allMeetAndMinEncounter {
    all disj a,b: Person | let x= (people. a & people.b) {
        #x <=Settings.maxEncounter
        x != none ->none
   }
}


run allMeet for 6 Int, 9 Session, exactly 4 Group, exactly 20 Person

突出显示所带来的变化:

  • 尽可能删除量化
  • 删除了多余约束
  • 用三元关系替换了两个二元关系组和人。这有几个优点:
    • 它将实例中存在的组原子数减少到每个会话的组总数。
    • 它允许在实例查看器中使用实例投影。你现在能够,例如分别查看每个会话的组分配。

答案 1 :(得分:1)

我认为Alloy不是优化的正确工具。它是一个专注于查找反例的规范工具。然而,当然可以找到像这样的谜题的解决方案。 (我认为有一个团队开发了Alloy的扩展,最大限度地减少了找到的解决方案。)

虽然我是初学者,但我还是刺了一下。令人惊讶的是,我找到了一个解决方案,有4个会议11人和3个小组

  s0   {2,9,10},  {5,6,7,8},  {0,1,3,4}, 
  s1   {2,4,7,8}, {1,3,6,10}, {0,5,9}, 
  s2   {1,2,3,5}, {0,7,8,10}, {4,6,9}, 
  s3   {0,2,6},   {4,5,10},   {1,3,7,8,9} 

由于Alloy不是优化器,我使用二进制方式来查找最小会话数,我发现你需要至少7个会话才能获得25/5。

这是我的完整模特:

sig P, Group {}
sig Session {
  participants : Group -> P
}

fact {
  // tuning goes here (max sure <= scope )
  # Group = 5
  # P = 25
  # Session = 7

  // In every session, people are divided into a constant number 
  // of groups.
  all s : Session | s.participants.P = Group

  // The sessions are continued until every pair of people 
  // meets each other at least once.
  // Preferably, the number of times the same pair meet 
  // each other should be minimized.
  all disj a,b : P | some participants.a & participants.b

  // Everyone has to join one group in every session.
  all p : P, s : Session | one s.participants.p

  // The group size should be closest to (the number 
  // of people)/(# of groups).
  // There should not be a 
  // groups of too few people or too many people.
  all g : Group, s : Session | (# s.participants[g]) >= 3
}

run {} for 5 Group, 25 P, 24 Session, 6 int

指定这些数字的int宽度至关重要。

找到一系列的8个会话花了5秒钟,发现7个会话耗时更长,34秒。通过增加最小值来强制更加相等的组大小仍在运行: - )

我认为该工具完全符合预期:找到 a 解决方案。找到最佳解决方案并不是那么好。