I think that "elegant" should be syntax as close to the normal handling of symbols as possible.
I shall define a function, unimaginatively named bump, that has a syntax similar to Part but which allows operations on symbols by way of Unevaluated and UpSet. If you will consider other storage formats besides Hold[v1, v2, ...] e.g. Hold @ {v1, v2, ...} this might be simplified somewhat.
func_[a___, bump[lst_, idx___], b___] ^:= func[a, #, b] & @ Part[List @@@ Unevaluated @@ {lst}, {1}, idx] Examples:
varsH = Hold[U0[1], U0[2], B0, V0[1], V0[2]]; bump[varsH] = Range[5]; (* set all values *) bump[varsH, 3] = 8; (* reassignment to B0 *) varsH[[3]] (* recall B0 *) ToString @ bump[varsH, 3] (* get name of B0 as String *) bump[varsH, 3] =. (* Unset B0 *) bump[varsH] =. (* Unset all *) This works with other operations and shapes of lists too. (For consistency I think Hold @ {...} is better but I will use the original form.)
vars2 = Hold[a, b, {c1, c2}, d]; bump[vars2, 3, 1] = 5; bump[vars2, 3, 2] = 7; bump[vars2, 3] += 1; List @@ vars2 (* show the result *) {a, b, {6, 8}, d}
bump[vars2, 3] =.; List @@ vars2 {a, b, {c1, c2}, d}
For a simpler function upon which this one is based see:
Assigning values to a list of variable namesAssigning values to a list of variable names