It's not clear to me how the expected output relates to the input. Is it necessary that the hf value match the ac value? The output's rows all show matching values, but the input often has values in hf columns that are different from the ac columns.
Does this sort of solution with separate and a combination of pivot_longer and pivot_wider help?
df1 %>%
pivot_longer(cols = -(starts_with("np_"))) %>%
separate(name, into = c("cc", "type", "num"), sep = "_") %>%
select(-cc) %>%
pivot_wider(names_from = type, values_from = value)
# A tibble: 54 x 5
np_id np_city_size num hf ac
<chr> <chr> <chr> <int> <int>
1 81 village 1 NA NA
2 81 village 2 NA NA
3 81 village 3 1 NA
4 81 village 4 NA NA
5 81 village 5 NA NA
6 81 village 6 NA NA
7 82 village 1 1 NA
8 82 village 2 NA NA
9 82 village 3 NA NA
10 82 village 4 NA 1
# ... with 44 more rows