First, you should not use %d to print the address of a pointer, but %p. %d is meant for signed integers that may be or not appropriate to represent an address depending on the implementation. Next you will find interesting references in that other answer from the C tag wiki.
Now for a direct answer. int a[3][3]; declares an array of 3 arrays of three integers. So a[0] (or *a) is the first array of three elements.
In printf("%p\n", *a); the array *a decays to a pointer to its first element, namely &a[0][0], said differently you get the address of the beginning of the array.
In printf("%p\n", a); the array a also decays to a pointer to its first element &a[0]. Here again you get the address of the beginning of the array, that is why the printed values are the same.
But even if they point to same address, &a[0] and &a[0][0] are pointers to different types: first points to an array of 3 integers, while second points to an integer. And the same, &a will still point to the same address, but to still another type and array of 3 arrays to 3 integers.
As the address of an object is the address of its first byte, (char *) a, (char *) *a and (char *) &a are all equal and are a char pointer to the first byte of the array. This is legal because the C language specifies that a pointer to an object can be casted to a pointer to char pointing to the first byte of an object. But it is not allowed to pass a to a function expecting an int * and a correct compiler should emit a warning for different indirection levels if you did.