Let's assume that instead of keeping for each cage the Operation and target number we pre-compute all the combinations of values that will satisfy the cage. For example, assume that in a 4x4 game, we have a cage that refers to cells C1 and C2 and that the cage requires those two cells to produce a value of 2 using division,
Pre-computing the possibilities, we store [(1, 2), (2, 1), (2, 4), (4, 2)] in this cage.
The cage points to the two cells it constrains, and each cell points to the cage that constrains it.
When a cell (say C1) gets a value of (say) 2, we want to change the cage to be [(2, 1), (2, 4)]. But when that happens, it's necessary to change cell C2 to point to the new cage -- and of course since the cell was changed the construct that held that cell has to be changed also.
It's a bit of a pain to write the code to do all that. This isn't to say that the code will take that long to run or that it will use excessive memory -- only that it feels like one is being forced to write code that one shouldn't have to write.