Luogu P11203 [JOIG 2024] 感染シミュレーション / Infection Simulation 题解 [ 紫 ] [ 二维偏序 ] [ 并查集 ] [ 倍增 ] [ 贪心 ]
Infection Simulation:JOI 唯一好的地方就在于样例强的一批,过了样例基本上就把题过了。
观察题目,可以发现以下性质:
- 感染者存在的时刻一定构成一个区间。因为不可能出现感染者消失后再出现的情况。
- 当 \(x\) 逐渐变小的时候,被感染的人一定不会变少,感染的限制条件一定不会更紧,感染者构成的区间一定不会更短。
- 在拓展感染的右端点的时候,不一定要选择 \(R\) 最大的人进行拓展,而是要选择 \(X\) 最大的人进行拓展。因为不需要最小化拓展右端点的次数,而选择 \(X\) 最大则让感染的限制条件尽可能松。
因此,我们可以对于当前感染者的右端点 \(R\) 找出 \(\min_{1 \le i \le n, R_i \ge R} L_i\),这样就能在拓展 \(R\) 的前提下最大化 \(X\)。假设本次拓展的代价为 \(R - \min_{1 \le i \le n, R_i \ge R} L_i\)。容易发现这样拓展形成的路径是唯一的,所以在对每个询问进行处理的时候可以直接进行倍增,遇到第一个代价小于 \(X_i\) 的位置就停下,即可求出右端点的位置。感染者的左端点显然就是 \(L_{P_i}\)。或者这个也可以按 \(X_i\) 排序后,把询问离线下来,用并查集维护这坨东西。
在求出感染区间后,剩下的就是容易的了。一个人想要被感染,必须同时满足下面两个条件:
- \(R-L + 1\ge X_i\)。
- \(L'+X_i \le R \le R'\)。
其中,\(L', R'\) 表示感染者构成的时刻区间的左右端点。
这个就是个很显然的二维偏序了。直接按查询的 \(X_i\) 排序之后,双指针加树状数组求出满足条件的 \(R\) 的数量即可。
时间复杂度 \(O(n\log n)\)。注意需要离散化,并且特判第一个被感染的人 \(R_{P_i}-L_{P_i}+1 < X_i\) 的情况。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
using pii = pair<pi, int>;
const int N = 200005, inf = 0x3f3f3f3f;
int n, q, lsh[N], cnt, idx, quep[N], quex[N], oril[N], orir[N];
struct Segment{
int l, r;
}sg[N];
struct Edge{
int u, v, w;
}e[N];
bool cmp1(Segment x, Segment y) { return (x.r < y.r); }
bool cmp2(Edge x, Edge y) { return (x.w > y.w); }
bool cmp3(pii x, pii y) { return (x.fi.fi > y.fi.fi); }
bool cmp4(Segment x, Segment y) { return (x.r - x.l > y.r - y.l); }
bool cmp5(int x, int y) { return (quex[x] > quex[y]); }
int fa[N];
void init()
{
for(int i = 1; i <= cnt; i++)
fa[i] = i;
}
int findf(int x)
{
if(fa[x] != x) fa[x] = findf(fa[x]);
return fa[x];
}
void combine(int x, int y)
{
int fx = findf(x), fy = findf(y);
if(fx == fy) return;
fa[fx] = fy;
}
int getrk(int x)
{
return (lower_bound(lsh + 1, lsh + cnt + 1, x) - lsh);
}
int ansrx[N], ans[N];
pii que1[N];
int que2[N];
int lowbit(int x)
{
return (x & (-x));
}
struct BIT{
int tr[N];
void update(int p, ll v)
{
while(p <= cnt)
{
tr[p] += v;
p += lowbit(p);
}
}
ll query(int p)
{
ll res = 0;
while(p)
{
res += tr[p];
p -= lowbit(p);
}
return res;
}
}tr1;
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> sg[i].l >> sg[i].r;
lsh[++cnt] = oril[i] = sg[i].l;
lsh[++cnt] = orir[i] = sg[i].r;
}
lsh[++cnt] = inf;
lsh[++cnt] = -inf;
sort(lsh + 1, lsh + cnt + 1);
cnt = (unique(lsh + 1, lsh + cnt + 1) - lsh) - 1;
sort(sg + 1, sg + n + 1, cmp1);
int mn = -1, p = n;
for(int i = cnt; i >= 2; i--)
{
if(mn != -1 && lsh[i] - sg[mn].l >= 1)
e[++idx] = {i, getrk(sg[mn].r), lsh[i] - sg[mn].l};
while(p >= 1 && sg[p].r >= lsh[i])
{
if(mn == -1 || sg[p].l < sg[mn].l) mn = p;
p--;
}
}
sort(e + 1, e + idx + 1, cmp2);
init();
cin >> q;
for(int i = 1; i <= q; i++)
{
cin >> quep[i] >> quex[i];
que1[i] = {{quex[i], quep[i]}, i};
}
sort(que1 + 1, que1 + q + 1, cmp3);
p = 1;
for(int i = 1; i <= q; i++)
{
while(p <= idx && e[p].w >= que1[i].fi.fi)
{
combine(e[p].u, e[p].v);
p++;
}
ansrx[que1[i].se] = lsh[findf(getrk(orir[que1[i].fi.se]))];
}
sort(sg + 1, sg + n + 1, cmp4);
int cntq = 0;
for(int i = 1; i <= q; i++)
{
if(orir[quep[i]] - oril[quep[i]] < quex[i])
{
ans[i] = 1;
continue;
}
que2[++cntq] = i;
}
sort(que2 + 1, que2 + cntq + 1, cmp5);
p = 1;
for(int i = 1; i <= cntq; i++)
{
int id = que2[i];
while(p <= n && sg[p].r - sg[p].l >= quex[id])
{
tr1.update(getrk(sg[p].r), 1);
p++;
}
int v = (upper_bound(lsh + 1, lsh + cnt + 1, ansrx[id]) - lsh - 1);
int k = getrk(oril[quep[id]] + quex[id]) - 1;
ans[id] = tr1.query(v) - tr1.query(k);
}
for(int i = 1; i <= q; i++)
cout << ans[i] << "\n";
return 0;
}