Розв'язок
Потрібно порахувати кількість пар маршрутів, які мають рівно один спільний неперервний відрізок, що містить щонайменше дві клітинки.
Кожен маршрут може рухатися лише вправо або вниз. Нехай A=(1,a) та B=(b,1) — початкові клітинки, а C=(c,w) та D=(h,d) — кінцеві.
Основне спостереження полягає в тому, що кожну сумісну пару маршрутів можна однозначно розділити на три частини:
Два маршрути без спільних клітинок від та до першої спільної клітинки.
Один спільний шлях, який містить щонайменше одне ребро.
Два маршрути без спільних клітинок від останньої спільної клітинки до та .
Порахуємо кількість способів побудувати кожну з цих частин за допомогою динамічного програмування.
Підрахунок шляхів
Спочатку обчислимо чотири масиви:
F_A(v) — кількість шляхів від до клітинки .
F_B(v) — кількість шляхів від до клітинки .
G_C(v) — кількість шляхів від клітинки до .
G_D(v) — кількість шляхів від клітинки до .
Усі шляхи мають проходити лише через вільні клітинки та рухатися вправо або вниз.
Перші два масиви можна обчислити, проходячи таблицю зверху вниз та зліва направо. Наприклад, для вільної клітинки :
з початковим значенням F_A(A)=1.
Аналогічно, G_C та G_D можна обчислити у зворотному порядку:
Усі значення обчислюємо за модулем 10^9+7.
Перша спільна клітинка
Нехай v=(i,j) — перша спільна клітинка двох маршрутів.
Оскільки до маршрути не перетинаються, вони повинні потрапити в цю клітинку з різних напрямків.
Позначимо клітинку над через U=(i-1,j), а клітинку ліворуч — через L=(i,j-1).
Оскільки перший маршрут починається на верхній межі таблиці, а другий — на лівій, перший маршрут повинен потрапити у через , а другий — через .
Потрібно порахувати кількість пар шляхів від до та від до , які не мають спільних клітинок.
За лемою Ліндстрема — Гесселя — В'єно ця кількість дорівнює
Віднімання прибирає пари шляхів, які перетинаються до потрапляння у . Через взаємне розташування початкових і кінцевих клітинок шляхи з протилежним поєднанням кінців не можуть бути неперетинними.
Для клітинок на верхній або лівій межі, а також для заблокованих клітинок, покладемо join(v)=0.
Отже, join(v) — кількість способів, якими два кур'єри можуть уперше зустрітися у клітинці .
Остання спільна клітинка
Тепер нехай v=(i,j) — остання спільна клітинка.
Після виходу з маршрути повинні одразу розділитися та більше не перетинатися.
Позначимо клітинку праворуч від через R=(i,j+1), а клітинку знизу — через T=(i+1,j).
Оскільки перший кур'єр завершує маршрут на правій межі, а другий — на нижній, перший маршрут повинен виходити через , а другий — через .
Використовуючи ту саму лему, отримуємо кількість способів завершити обидва маршрути без подальших перетинів:
Для клітинок на правій або нижній межі, а також для заблокованих клітинок, покладемо split(v)=0.
Спільний відрізок
Ми вже обчислили кількість способів почати та завершити спільний відрізок. Залишилося з'єднати його кінці.
Нехай — кількість способів вибрати першу спільну клітинку та побудувати спільний шлях, який закінчується у і містить щонайменше одне ребро.
Для кожної клітинки можемо почати спільний відрізок способами.
Проте не можна одразу завершити його в , оскільки спільний відрізок повинен містити щонайменше дві клітинки.
Тому такі нові відрізки враховуватимемо лише під час переходу з до однієї із сусідніх клітинок.
Для вільної клітинки v=(i,j) нехай та знову позначають її верхнього та лівого сусідів.
Тоді
Тут та враховують продовження вже побудованих спільних відрізків, а join(U) та — відрізки, які починаються у відповідних сусідніх клітинках.
Таким чином, кожен врахований спільний відрізок гарантовано містить щонайменше одне ребро.
Нарешті, якщо спільний відрізок закінчується у , існує split(v) способів завершити обидва маршрути без подальших перетинів.
Отже, відповідь дорівнює
Кожна сумісна пара маршрутів має однозначно визначені першу та останню спільні клітинки, тому буде порахована рівно один раз.
Асимптотична складність
Кожен із чотирьох масивів кількостей шляхів можна обчислити за часу.
Значення join(v), split(v) та також обчислюються за для кожної клітинки.
Отже, загальна часова складність становить , а просторова — .
Реалізація
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
void solve() {
int h, w;
cin >> h >> w;
int a, b, c, d;
cin >> a >> b >> c >> d;
--a; --b; --c; --d;
vector<string> g(h);
for (auto &s : g) cin >> s;
int n = h * w;
vector<int> fromA(n), fromB(n);
vector<int> join(n), dp(n);
vector<int> toC(n), toD(n);
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int v = i * w + j;
if (g[i][j] == '#') continue;
int up = v - w;
int left = v - 1;
ll x = (i == 0 && j == a);
ll y = (i == b && j == 0);
if (i) {
x += fromA[up];
y += fromB[up];
}
if (j) {
x += fromA[left];
y += fromB[left];
}
fromA[v] = x % mod;
fromB[v] = y % mod;
if (i && j) {
int t = 1LL * fromA[up] * fromB[left] % mod;
t -= 1LL * fromA[left] * fromB[up] % mod;
if (t < 0) t += mod;
join[v] = t;
}
ll z = 0;
if (i) {
z += dp[up] + join[up];
}
if (j) {
z += dp[left] + join[left];
}
dp[v] = z % mod;
}
}
int ans = 0;
for (int i = h - 1; i >= 0; i--) {
for (int j = w - 1; j >= 0; j--) {
int v = i * w + j;
if (g[i][j] == '#') continue;
int down = v + w;
int right = v + 1;
ll x = (i == c && j == w - 1);
ll y = (i == h - 1 && j == d);
if (i + 1 < h) {
x += toC[down];
y += toD[down];
}
if (j + 1 < w) {
x += toC[right];
y += toD[right];
}
toC[v] = x % mod;
toD[v] = y % mod;
if (i + 1 < h && j + 1 < w) {
int t = 1LL * toC[right] * toD[down] % mod;
t -= 1LL * toC[down] * toD[right] % mod;
if (t < 0) t += mod;
ans = (ans + 1LL * dp[v] * t) % mod;
}
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
}