I will describe the solution in terms of the native win32 COM APIs; it shouldn't be too difficult to write the interop to do it in C# (or find it at pinvoke.net). Alternatively, you may need to use the properites that the managed objects expose to get the native ones.
You're not likely to be able to build the DOM yourself faster than IE's parser, so create a blank HTMLDocument (which in native code would be CoCreateInstance(CLSID_HTMLDocument)) and QueryInterface() the HTMLDocument for its IMarkupServices implementation. Also create two IMarkupPointers using the IMarkupServices::CreateMarkupPointer() method.
Next call IMarkupServices::ParseString() to parse your HTML. This will give you a pointer to an IMarkupContainer that contains your DOM, as will as two IMarkupPointers that point to the beginning and end of you DOM. Now you can use IMarkupServices::Move() to move your data from one IMarkupContainer to another.
So the general scheme you would use is to have a single HTMLDocument which is your "display" document, and it's associated IMarkupContainer (which you can just QueryInterface() for). Then you have a vector or list or whatever of all the non-displaying markup containers. Then you just create a markup pointer for your display doc, call IMarkupPointer::MoveToContainer(displayDocumentContainer, true) and then use that to move stuff around from your display container to the not-displaying containers and vice-versa.
One thing to note: you must only access these objects on the thread you create them from, or acquire them on. All IE objects are STA objects. If you need multi-threaded access, you must marshal.
If you have specific follow up questions, let me know.
References: