I'm working with something that can modeled as the following type of (multi-)graph $G = (V,E)$, which I've been calling an "unlockable" graph. (There may be existing terminology for this.)
Let $G$ be a graph with the following additional structure. There is a set $K$ of keys, and edges may be "unlocked" or they may be "locked" by decorating them with the name of some $k \in K$. Multiple edges can be locked by the same $k$. Each vertex in the graph $v_i$ has a (possibly empty) ordered list $L_i$ of locations, which may be empty or contain a key. One of the vertices of the graph is marked as the start vertex.
The graph can be completely unlocked if every vertex can be visited from the start vertex. A locked edge can only be traversed if a vertex containing the key for that edge is first visited. Traversals are allowed to repeat edges and vertices.
Such a graph is valid if a) all keys are placed in exactly one location and b) the graph can be completely unlocked.
I'd like to uniformly randomly select a valid key placement on a fixed underlying graph. (That is, a map from $K$ into the locations $L = \cup_i L_i$ of the graph.)
The analogy (and essentially the context) is as follows: The graph represents a dungeon composed of rooms, and the edges are doors, which can be locked or unlocked by various keys. These keys are scattered around the dungeon in treasure chests. An adventurer enters the dungeon and explores it, and wins if they can enter every single room (to loot it, I suppose). How can the keys be distributed so that the adventurer always wins, and cannot have an unfair advantage by assuming that one valid configuration is more likely than others?
Here's a naive algorithm that produces a random valid key placement:
current_keys = {}
placed_keys = {}
current_rooms = {}
while not all rooms are in current_rooms:
current_rooms = largest connected component of start_room via unlocked doors
for key not in current_keys and key not in placed_keys:
if key has door connected to room in current_rooms:
add key to current_keys
randomly select new_key in current_keys
place new_key in random unused location in room in current_rooms
remove new_key from current_keys
add new_key to placed_keys
unlock all doors that can be opened using new_key
However, this algorithm does not randomly select a valid key placement uniformly.
For instance, imagine a dungeon consisting of three rooms: a start room and two side rooms connected to it. The door to one side room needs key $a$ and the door to the other needs key $b$. Each side room has one chest, and the starting room has two. I'll draw this dungeon as [_|__|_]
, with the start room in the middle, the $a$ side room on the left, and the $b$ side room on the right.
There are six distinct valid configurations:
[_|ab|_], [_|ba|_], [b|a_|_], [b|_a|_], [_|b_|a], [_|_b|a]
However, the naive algorithm over-weights the first two choices, selecting them 1/4 of the time each and the remaining choices only 1/8 of the time. This is because earlier areas are unlocked for longer, so they have a higher chance to have a key placed in them.
Also notice that [_|ab|_]
and [_|ba|_]
are distinct configurations, since the elements of $L_i$ are distinguishable from each other. It is not the same as specifying that $v_i$ can hold at most $|L_i|$ keys.
I thought about something like a dynamic programming-type algorithm, but the issue is that a valid configuration can be produced from an invalid configuration. For example, if we always placed $a$ before $b$ in the above example, then the configurations [_|b_|a]
and [_|_b|a]
could never be reached because [_|__|a]
is not a valid partial configuration.
It's pretty clear that there are roughly at most $|L|^{|K|}$-many key placements, although for a generic graph, a good portion of these will not be valid.
In my particular problem, I have about 40 rooms with about 100 treasure chests spread around among them and about 20 keys. Enumerating all possible valid locations and then uniformly choosing a random one seems like it would be quite slow, especially if I want to use this for a larger scenario, where I have about 50 rooms with 1000 chests and 40 keys.
This question is pretty specific and I couldn't find any information about this type of graph, but I also don't really know what to search for it as.
I did find some information about generating such graphs where you can also choose which doors are unlocked with which keys -- in the context of surprise, surprise, dungeon generation for a game -- but here that information is fixed in advance and only the keys can be freely placed.
(I'm also interested in a harder problem where some of the doors are one-way (so the graph is a digraph) and we don't want the adventurer to ever get stuck in a room with no way out, but I suspect the above problem is hard enough to start.)