区间-LIS
转自:hyf719346 大佬,代码是我的。
神题。
注意以下是排列的 LIS。
做普通的 LIS 时 dp 做是 \(O(n^2)\),如果加上 \(q\) 次询问,很明显这个复杂度无法接受,就算想到动态 dp,这个实际上也无法刻画。
事实上 LIS 有单次查询 \(O(n\log n)\) 的做法,大概是这样的。
维护一个集合,依次插入序列元素 \(a_i\),进行如下操作。
- 找到集合中第一个大于 \(a_i\) 的元素,替换掉。
- 若找不到,则直接插入到集合末尾。
最后集合的 size 就是答案,很明显这个是可以用 set 来维护的。
rep(i, 1, n) {
int x; cin >> x;
if(s.empty() || x > *prev(s.end())) s.insert(x);
else {
auto p = s.lower_bound(x);
s.erase(p), s.insert(x);
}
ans = max(ans, (int)s.size());
}
从上面的做法我们能得到一些启发,为什么这么做是对的?
感性理解,我们发现对于替换操作,让集合后面比 \(a_i\) 大的换成了更小的那个,这样使得后面直接拼接的数可以更小,也就是让集合大小尽可能大了。
我们有了一些思路,记 \(S_{l,r}\) 表示对排列下标 \(l\) 到 \(r\) 进行如上操作之后得到的集合。假如我们枚举 \(r\),能够得到所有 \(S_{k,r} (k \le r)\) 的信息,那么答案就出来了,问题是如何记录这个东西。
我们手玩可以找到一些性质。
- \(|S_{l,r}| - |S_{l - 1,r}| \le 1\)
- \(S_{l,r} \subseteq S_{l - 1,r}\)
记 $S_1 = \varnothing \(,\)S_2 = p_{l-1}$。我们对两个集合依次分别插入 \(p_i (i \in [l,r])\)。
若插入始终有 \(p_{l-1} < p_i\),则始终有 \(S_2=S_1 \cup p_{l-1}\)。
否则,此次插入后,\(S_2\) 的 \(p_{l-1}\) 会被替换为 \(p_i\):
比如 \(p_{l-1} = 2\),某个时刻 \(S_1 = \{3,4\}\),\(S_2 = \{2,3,4\}\)
现在我插入了 \(1\)。\(S_1 = \{1,4\}\),\(S_2 = \{1,3,4\}\)。
我们发现此后再插入 \(< 1\) 的数只会影响集合小于 \(1\) 的部分,对后面的部分没有影响,此时又是一个形如 “若插入始终有 \(p_{l-1} < p_i\),则始终有 \(S_2=S_1 \cup p_{l-1}\)” 之前的这个东西,此时我们可以把 \(3\) 理解成 \(p_{l-1}\)。此时两个集合有一个元素不同,就是假设新的那个 \(p_{l-1}\)。一直重复上述逻辑即可。
什么时候两个集合相等?我们发现这种传递关系传递到尾部时就相等了。
再插入 \(2\)。\(S_1 = \{1,2\}\),\(S_2 = \{1,2,4\}\)。
再插入 \(3\)。\(S_1 = \{1,2,3\}\),\(S_2 = \{1,2,3\}\)。
通过如上操作,所以我们说如上性质是成立的。
所以我们就有了一个思路,同样是对于当前 \(r\),对于数 \(x\) 用 \(a_x\) 表示出现的最靠后的 \(l\) 使得 \(x \in S_{l,r}\) 。此时可以保证 \(x \in S_{k,r}(k\le l)\)。那么此时对于询问 \((L,r)\) 的答案就是统计有多少 \(a_x \ge L\) 了。
问题是 \(r\gets r+ 1\) 之后 \(a_x\) 的变化如何维护。
以下的 \(r\) 指赋值后的 \(r\)。
记 \(v = p_r\),\(a_x=0\) 表示不存在这样的 \(l\)。
对于 \(a_x(x<v)\) 会变化吗?实则不会。因为相当于是在前面所有集合都插入一个 \(v\)。而 \(v\) 插入不可能会影响到原集合中小于它的元素。因此 \(a_x(x<v)\) 不变。
然后我们发现,此时一定有 \(a_v \gets r\)。因为此时 \(S_{r,r} = \{v \}\)。
\(a_{v+1}\) 呢?因为 \(v + 1\) 肯定是第一个大于 \(v\) 的,假如 \(a_{v+1} > 0\),意味之前有些集合是有 \(v+1\),加入了 \(v\) 使得 所有 \(v+1\) 最先全部被替换。因此 \(a_{v+1} \gets 0\)。
\(a_{v+2}\) 呢?发现只有当 \(v+1\) 出现过的时候才能帮它挡掉这一次操作,否则它也会被替换掉。于是,如果原本序列中 \(a_{v+1} > a_{v+2}\),则 \(a_{v+2} \gets a_{v+1}\),否则不变。
以此类推,我们可以写出如下代码。请读者自行思考这部分。
r ++; int v = p[r];
a[v] = r; int tmp = 0;
for(int i = v + 1; i <= n; i ++) if(a[i] > tmp) swap(a[i], tmp);
这里可以给出一张图帮助理解。黑线指原来 \(a_x\) 的管辖范围。红色指删除的地方,剩下的黑线指赋值之后的。

至此,我们完成了 \(r\gets r+ 1\) 的所有 \(a_x\) 的变化。
如果你做过回转寿司,这个操作是可以在 \(n\sqrt n \log n\) 实现的。这里不再叙述。至此,这个题目完成了 \(99 \%\)。
我们只剩下统计有多少 \(a_x \ge L\) 的问题了,很简单,我们在更新 \(a_x\) 的时候使用树状数组即可。记得离线把询问挂到 \(r\) 上。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define ll int
#define pii pair<ll, ll>
#define fi first
#define se second
#define pb push_back
using namespace std;
#define N 100005
#define M 505
ll n, m, Q, B, p[N], a[N], pos[N], L[M], R[M], ans[N];
priority_queue<ll, vector<ll>, greater<ll>> lz[M];
priority_queue<ll> q[M];
vector<pii> qy[N];
void pushdown(ll x) {
if(lz[x].empty()) return;
rep(i, L[x], R[x]) if(a[i] > lz[x].top())
lz[x].push(a[i]), a[i] = lz[x].top(), lz[x].pop();
priority_queue<ll, vector<ll>, greater<ll>> ().swap(lz[x]);
}
void pushup(ll x) {
priority_queue<ll> (a + L[x], a + R[x] + 1).swap(q[x]);
}
ll upd(ll l, ll r, ll x) {
ll pl = pos[l], pr = pos[r];
if(pl == pr) {
pushdown(pl);
rep(i, l, r) if(a[i] > x) swap(a[i], x);
pushup(pl);
}
else {
pushdown(pl), pushdown(pr);
rep(i, l, R[pl]) if(a[i] > x) swap(a[i], x);
rep(i, pl + 1, pr - 1)
lz[i].push(x), q[i].push(x), x = q[i].top(), q[i].pop();
rep(i, L[pr], r) if(a[i] > x) swap(a[i], x);
pushup(pl), pushup(pr);
}
return x;
}
struct BIT {
#define lowbit(x) (x & -x)
ll t[N];
void add(ll x, ll k) { if(!x) return; while(x < N) t[x] += k, x += lowbit(x); }
ll qry(ll x) { ll res = 0; while(x) res += t[x], x -= lowbit(x); return res; }
#undef lowbit
} T;
void solution(ll tas) {
cin >> n >> Q; B = sqrt(n), m = (n - 1) / B + 1;
rep(i, 1, n) cin >> p[i], pos[i] = (i - 1) / B + 1, R[pos[i]] = i;
rep(i, 1, m) L[i] = (i - 1) * B + 1;
rep(i, 1, m) rep(j, L[i], R[i]) q[i].push(a[j]);
rep(i, 1, Q) { ll l, r; cin >> l >> r; qy[r].pb({l - 1, i}); }
rep(i, 1, n) {
ll v = p[i], x; T.add(a[v], -1), T.add(i, 1);
pushdown(pos[v]), a[v] = i, pushup(pos[v]);
if(v < n) x = upd(v + 1, n, 0), T.add(x, -1);
for(auto it : qy[i]) ans[it.se] = T.qry(n) - T.qry(it.fi);
}
rep(i, 1, Q) cout << ans[i] << "\n";
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
ll tas = 1; // cin >> tas;
rep(i, 1, tas) solution(i);
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/22461250

浙公网安备 33010602011771号