F - Robots and Exits
\(n\) 個人,\(m\) 個出口在一條數軸上,座標是正整數。你每次可以讓所有人向左或向右一步,人在某個出口上後就離開。求多少種操作的方案使得人全部走光。兩個方案相同當且僅當存在至少一個人在兩次操作序列進行完成後從不同的出口消失。對 \(10^9+7\) 取模。
\(1\le n,m\le 10^5.\)
妙妙題。把每個點抽象成一個二維點 \((a_i,b_i)\),橫座標是到左邊出口的距離,縱座標是到右邊出口的距離。當 \(a_i<a_j,b_i>b_j\) 的時候,假設 \(i\) 從右邊出去,則 \(j\) 也一定從右邊出去,左側同理。把左右走抽象成一條折線,在 \((x,y)\) 這個位置代表從起點出發,最遠的左側點距離爲 \(x\) 右側爲 \(y\)。把座標向右上平移 \(0.5\) 個單位,那麼折線下方的點都去右邊,上方都去左邊。假設我們已經確定了一個去右邊的集合且滿足上述性質,則一定可以找到一條滿足條件的斜線。故我們可以直接dp,\(f_j=\sum\limits_{a_i<a_j,b_i<b_j}f_i+1\),這是個二維偏序問題,樹狀數組優化即可。
#include <bits/stdc++.h>
const int maxn = 1e5 + 5, mod = 1e9 + 7;
int qmod(int x) {
if (x >= mod) {
x -= mod;
}
return x;
}
int n, m, x[maxn], y[maxn], tot, tmp[maxn], cnt, f[maxn], c[maxn], ans;
std :: pair<int, int> p[maxn];
void add(int x, int v) {
while (x <= cnt) {
c[x] = qmod(c[x] + v);
x += x & -x;
}
}
int ask(int x) {
int ret = 0;
while (x) {
ret = qmod(ret + c[x]);
x -= x & -x;
}
return ret;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", x + i);
for (int i = 1; i <= m; i++) scanf("%d", y + i);
for (int i = 1; i <= n; i++) {
int r = std :: lower_bound(y + 1, y + 1 + m, x[i]) - y;
if (r > 1 && r <= m) p[++tot] = {x[i] - y[r - 1], y[r] - x[i]};
}
for (int i = 1; i <= tot; i++) tmp[i] = p[i].second;
std :: sort(tmp + 1, tmp + 1 + tot);
cnt = std :: unique(tmp + 1, tmp + 1 + tot) - tmp - 1;
for (int i = 1; i <= tot; i++) {
p[i].second = std :: lower_bound(tmp + 1, tmp + 1 + cnt, p[i].second) - tmp;
}
std :: sort(p + 1, p + 1 + tot, [&](std :: pair<int, int> a, std :: pair<int, int> b){
if (a.first != b.first) return a.first < b.first;
return a.second > b.second;
});
for (int i = 1; i <= tot; i++) {
if (p[i].first == p[i - 1].first && p[i].second == p[i - 1].second) continue;
f[i] = qmod(ask(p[i].second - 1) + 1);
ans = qmod(ans + f[i]);
add(p[i].second, f[i]);
}
printf("%d\n", qmod(ans + 1));
return 0;
}