Mutually fill in the blanks
Retina, 102 100 93 88 bytes

Byte count assumes ISO 8859-1 encoding.

$
!¶$`
m`(?&lt;=^(\w+ )*)(?=_.*¶(?&lt;-1&gt;\w+ )*(\w+))
$2
(([a-z])+)(?&lt;-2&gt;_)*(_*)\3|!\D+
$3$1$3


The strings will are separated by a linefeed. If there is an odd number of underscores left, the extraneous one will be after the word.

Try it online!

Explanation

I guess this is the "duplicate-append-lookback-match-add-centre approach", or something close...

$
!¶$`


We start by duplicating the input (separated with a ! and a linefeed). The purpose of this is that we can then process both lines by fetching words from the next line (instead of having to treat the second line separately).

m`(?&lt;=^(\w+ )*)(?=_.*¶(?&lt;-1&gt;\w+ )*(\w+))
$2


This prepends the correct word to each gap. We start by counting the current word position with the lookbehind (?&lt;=^(\w+ )*) (the position is stored as the depth of group 1). Then the lookahead a) ensures that we're at the beginning of a gap by matching _, then skips to the next line with .*¶, matches the correct number of words with (?&lt;-1&gt;\w+ )* to get to the right position, and then matches the word found there with (\w+) into group 2.

(([a-z])+)(?&lt;-2&gt;_)*(_*)\3|!\D+
$3$1$3


This stage does three things:


It removes the underscores corresponding to each word length. This is done by counting the word length into group 2 with ([a-z])+ and then matching that many underscores (which are never written back).
It shifts the word to the centre of the gap by capturing half of the remaining underscores with (_*)\3 and writing $3$1$3 back.
It removes the duplicated input by matching !\D+ and replacing it with nothing.