Here's another alternative. I haven't tested the performance as I don't have a setup to do that, and I don't know if it will be similar to Jeff's in query plan. It does avoid the COUNT(DISTINCT...) that I read a while back (can't find the linky) was sometimes bad (something about rewinds).
EDIT: Eh, nevermind... you can ignore this one... Did a small test and it's between 5 and 10x slower than other methods with about 2x the reads.
Second Edit: BUT, it's easier to adjust if you know that the situation is a random number of "Purchased these" with a single "but didn't purchase this". Most of the other methods rely on replicating a block of SQL for each item in the list of "Purchased". I think both Jeff's (and mine) work without that restriction.
SELECT
b.CustomerID
FROM
(
SELECT
a.CustomerID
FROM
(
SELECT
DISTINCT
CustomerID,
Product
FROM
Purchase
WHERE
Product IN ('A','B')) AS a
GROUP BY
a.CustomerID
HAVING
COUNT(Product) = 2
) AS b LEFT OUTER JOIN
Purchase p ON b.CustomerID = p.CustomerID AND p.Product = 'C'
WHERE
p.Product IS NULL