All of the expressions a, &a and &a[0] will yield the same address1 - the address of the array is the same as the address of its first element:
   +---+
a: | 1 | a[0]
   +---+
   | 2 | a[1]
   +---+
The difference is in the types of the expressions.  a has type int [2], but unless it is the operand of the sizeof or unary & operators, it "decays" to type int * and the value of the expression will be the address of the first element2. &a[0] has type int *, and &a has type int (*)[2] (pointer to 2-element array of int).
This is why it didn't work when you wrote
int *ptr = &a; // int * = int (*)[2]
The types aren't compatible.  It would have worked if you had written
int *ptr = a; // int * = int *
Again, a has type int [2], but unless it's the operand of the sizeof or unary & operators, it "decays" to type int * and its value is the address of the first element - in this context, it's identical to writing &a[0].
- Not accounting for any type conversion - on platforms like x86 and x86-64, int *andint (*)[2]have the same size and representation, but that's not guaranteed everywhere.
- This is important - an array expression is not a pointer.  It will be converted to a pointer as necessary, but there's no separate pointer object that explicitly points to the first element of the array.