Две идеи. Как применить, пока не очень понятно.
1) вместо строки использовать двусвязный список символов. Это поможет делать замены более экономно, не растрачивая память и не сдвигая толпу символов. Хотя, если строки В и С одинаковой длины, можно и массивом обойтись, но это частный случай, не думаю что надо с ним возиться.
2) сделали первый обход с заменами. Теперь новые вхождения заменяемой строки надо искать не по всей получившейся строке, а только там, где были замены (в районе левого и правого краев вставленной строки; так же условие гарантирует, что В не является подстрокой С). Точнее, даже так: сначала просмотреть строку А, найти (средствами стандартной библиотеки) все вхождения В и поместить их в очередь, потом перегнать строку А в двусвязный список, и далее по принципу обхода в ширину - берем из очереди позицию для замены, делаем замену, находим новые позиции замены (одну или две), добавляем в очередь, тут ещё проверить, что эти вновь добавленные позиции ни с кем не пересекаются..
Да, этот способ не позволяет сократить число замен, но это уже лучше, чем на каждом этапе заново искать позиции.
Дальнейшее ускорение - как то сократить число замен, поскипав промежуточные и вычислив результат, но тут много кейсов, и навскидку сказать трудно.