区间-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;
}
posted @ 2026-08-14 08:10  So_noSlack  阅读(3)  评论(0)    收藏  举报