In C++ userei una std::map<std::string, std::set<std::string>> (in C++11 al posto del set "normale" userei un std::unordered_set).
La mappa (di fatto un albero RB) associa ad ogni tag le stringhe corrispondenti, e ti consente un lookup in tempo logaritmico; a differenza di una hashtable (std::unordered_map), che vuole sempre un match esatto, std::map ti consente di cercare anche i subtag in tempo logaritmico (usando il metodo lower_bound, che cerca un match di tipo >=).
Il set, a sua volta, ti consente di inserire impunemente gli elem senza dover controllare se sono già stati inseriti, ma evitando comunque di inserire duplicati. Dato che non hai bisogno di sfruttare il fatto che std::set mantiene gli elementi ordinati, come dicevo sopra in C++11 puoi usare unordered_set (di fatto una hashtable), che ti fornisce inserimento e lookup in tempo costante invece del tempo logaritmico del set "normale" (oltre ad avere una migliore cache locality).
Tra parentesi, ho un po' sistemato la formattazione del testo dell'esercizio, che tra a-capi di troppo, "o" al posto di elenchi puntati e un punto in cui si mischiavano pezzi di due sezioni diverse era abbastanza illeggibile, in futuro ti raccomando di starci più attento, un testo incasinato scoraggia molto a rispondere.