Disclaimer
I know that a formal proof using the pumping lemma has been posted above. However, I'll go for a completely informal explanation, because I believe it usually helps having some intuition about the problem before going for a formal solution.
General intuition
In general, when a language depends on some sort of counting, it should ring a bell that it is probably not regular. The reason is that counting can get arbitrarily large. You can see this concretely in your example.
Why is it not regular?
Imagine trying to create a DFS for your language. What you care about is the difference between the number of a
and the sum of the number of b
and c
(call this D_abc
). In a DFS, all information is captured in the state itself. As an example, consider the state after reading 10 consecutive a
and the one after reading 100 consecutive a
. These two states have to be different. Now, extending this argument for any number of a
(or equivalently any D_abc
) you can conclude that you need an infinite number of states, i.e. the language is not regular.
Bonus: Why is it context-free?
Now, think about using a pushdown automaton. The PDA allows you to capture the difficulty of (infinite) counting by using its (infinite) stack. In your example, you can do it like this:
If the stack is empty (i.e. D_abc = 0
), push whatever symbol you encounter onto the stack (i.e. if an a
comes along D_abc <- 1
, else if a b
or c
comes along D_abc <- -1
).
If the top element of the stack is a
(i.e. D_abc
> 0), if an a
comes along push it onto the stack (i.e. D_abc <- D_abc + 1
, else pop the top a
from the stack (i.e. D_abc <- D_abc - 1
).
Similarly, if the top element is b
or c
(i.e. D_abc < 0
), if a b
or c
comes along push it onto the stack (i.e. D_abc <- D_abc - 1
), else remove the top element from the stack (i.e. D_abc <- D_abc + 1
).
Using the above rules, the stack keeps count of D_abc
at each moment, which is exactly what you need to accept or not accept a string. Thus, you can conclude that the language is context-free.