You must make a distinction between the static type of a variable (the type known at compile time) and the run time type of an object reference assigned to a variable.
The static type of p is Parent in both cases because it is declared as Parent p. No matter what you assign it. You may even assign it null.
The run time type of the value of p after the assignment is Child in the first case and Parent in the second case. It would be undetermined if p was null.
It is okay to assign a Child to a Parent, because the Child class derives from Parent. A Child is therefore a Parent (in OO terms).
However, a Parent is not a Child, because it does not derive from it. Therefore, in the second case, you cannot cast p to Child. In the first case the cast is valid, because you cast the object to its actual run time type. It tells the compiler, I know that this object assigned to a Parent variable is a Child, so, please, treat it as a Child. This does not involve any conversion; however, a runtime check will be performed, possibly throwing an exception, if the cast was not allowed.
You were asked to explain it from point of view of stack and heap memory. I'm sorry to say, but this has absolutely nothing to do with stack or heap memory. It has to do with inheritance rules and assignment compatibility.
Of course, here we are dealing with reference types (classes). This allows us to derive Child from Parent. This would not be possible with value types (structs). The point is reference type versus value type, not heap versus stack, which is an implementation detail. And btw., a value type (struct) field in a class will also be stored on the heap.
See also: The Stack Is An Implementation Detail, Part One and Two (Eric Lippert's blog)
You did not ask for it but casting to a derived type is mostly preceded by a type test, because you usually do not know the run time type in advance. Let us say that the Child class adds a new method DriveParentsToDespair() (only children can do this). Then you could use Type testing with pattern matching to test the type and assign it to a new variable at the same time:
if (p is Child child) {
    child.DriveParentsToDespair();
}
This avoids an exception in case p has a run time type of Parent.