Hi Brent, I would like to share my 'oldschool' R base solution with you. It's quite not the simplest way, but I try to made it short... I think the tidyverse comfort zone is not such a bad way 
So, back to your example:
set.seed(1993)
df <- data.frame(time=rep(0:2,each=2),
grp=rep(0:1,3),
n=c(200,190,180,175,150,150),
p=c(0.1,0.2,0.15,0.26,0.21,0.32),
age=round(rnorm(6,50,6),1))
#okay, some base R (the good old days...) ;D
newdf <- merge(x=df, y=subset(df, time==0, select=c('grp','n')),
by='grp', all.x=TRUE, suffixes=c('','.base'))
newdf$'pct' <- 100*newdf$'n'/newdf$'n.base'
#result (note: merge(...) will change your original row order)
newdf
#> grp time n p age n.base pct
#> 1 0 0 200 0.10 47.0 200 100.00000
#> 2 0 1 180 0.15 50.3 200 90.00000
#> 3 0 2 150 0.21 56.9 200 75.00000
#> 4 1 0 190 0.20 47.7 190 100.00000
#> 5 1 1 175 0.26 46.1 190 92.10526
#> 6 1 2 150 0.32 51.9 190 78.94737
#final result, reset your row order and get your colnames of interest
newdf[order(newdf$'time', newdf$'grp'),c(colnames(df),'pct')]
#> time grp n p age pct
#> 1 0 0 200 0.10 47.0 100.00000
#> 4 0 1 190 0.20 47.7 100.00000
#> 2 1 0 180 0.15 50.3 90.00000
#> 5 1 1 175 0.26 46.1 92.10526
#> 3 2 0 150 0.21 56.9 75.00000
#> 6 2 1 150 0.32 51.9 78.94737