[算法] 一些分治
普通分治
其实没啥,每次只计算跨越分治中心的区间的贡献,剩下的递归到左右两边进行分治;
时间复杂度:分治树高度为
例题一:现在有一个
其中
题意简述:找一个给定的序列的所有子区间的最小值的和;
可以线性做,对于每一个
这里讲一下分治的做法;
其实对于这种求所有区间中符合条件的区间的题目,一般都可以分治做;
考虑跨过分治中心的区间,设分治中心为
处理完上述步骤后,我们开始从分治中心向右遍历右端点,每遍历到一个右端点
-
是区间
中的值; -
是
左边的值;
对于第一种情况,我们每次遍历时维护一个最小值
对于第二种情况,暴力做法肯定不行,那怎么办呢?
挖掘一下性质,我们发现
所以,我们可以每次遍历右端点时用二分查找找出
为了避免手写二分(懒,不想写),我们可以将
当然,还要维护一个前缀和(注意是
时间复杂度:分治 + 遍历
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
long long n;
long long a[500005];
long long b[500005], sum[500005];
long long c[500005];
long long ans;
void solve(long long l, long long r) {
if (l >= r) return;
if (r - l + 1 == 2) {
ans += min(a[l], a[r]);
return;
}
long long mid = (l + r) >> 1;
long long o = 0;
for (int i = 0; i <= mid - l; i++) b[i] = 0x3f3f3f3f;
for (long long i = mid - 1; i >= l; i--) {
o++;
b[o] = min(b[o - 1], a[i]);
}
long long cnt = 0;
for (long long i = o; i >= 1; i--) {
c[++cnt] = b[i];
}
for (long long i = 0; i <= cnt; i++) {
sum[i] = 0;
}
for (long long i = 1; i <= cnt; i++) {
sum[i] = sum[i - 1] + c[i];
}
long long mi = a[mid];
for (long long j = mid + 1; j <= r; j++) {
mi = min(mi, a[j]);
long long pos = upper_bound(c + 1, c + 1 + cnt, mi) - c;
if (pos <= cnt) ans += (cnt - pos + 1) * mi;
ans += sum[pos - 1];
}
solve(l, mid);
solve(mid, r);
}
int main() {
cin >> n;
for (long long i = 1; i <= n; i++) {
cin >> a[i];
}
solve(1, n);
for (long long i = 1; i <= n; i++) ans += a[i];
cout << ans;
return 0;
}
其实很多分治的套路是维护前缀和 + 发现性质,做的时候可以注意一下;
例题二: Luogu P4062 [Code+#1] Yazid 的新生舞会;
这题貌似题解中的主流做法是用数据结构维护高阶前缀和,这里讲一下分治做法;
还是求所有区间中符合条件的区间,可以考虑分治;
找区间中的绝对众数,我们可以借鉴一下摩尔投票法,设现在我们考虑的众数为
首先,对于一个区间
所以可以令
分治时,还是先从分治中心向左找符合条件的绝对众数以及区间和所出现的次数,向右遍历时统计一下区间和是否
由于我们要遍历绝对众数,而绝对众数最多变化
写的比较粗略,可以参考一下原题解区的题解;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, ddd;
int a[1000005];
long long ans;
int pos[1000005], vis[1000005], num[1000005], cnt[1000005];
void solve(int l, int r) {
if (l == r) {
ans++;
return;
}
int mid = (l + r) >> 1;
num[0] = 0;
for (int i = mid; i >= l; i--) {
if (++cnt[a[i]] > (mid - i + 1) / 2) {
if (!pos[a[i]]) {
pos[a[i]] = ++num[0];
num[pos[a[i]]] = a[i];
}
}
}
for (int i = mid + 1; i <= r; i++) {
if (++cnt[a[i]] > (i - mid) / 2) {
if (!pos[a[i]]) {
pos[a[i]] = ++num[0];
num[pos[a[i]]] = a[i];
}
}
}
for (int i = l; i <= r; i++) {
pos[a[i]] = 0;
cnt[a[i]] = 0;
}
for (int i = 1; i <= num[0]; i++) {
int sum = r - l + 1;
int ma = sum;
int mi = sum;
cnt[sum] = 1;
for (int j = l; j < mid; j++) {
if (a[j] == num[i]) {
sum++;
} else {
sum--;
}
ma = max(ma, sum);
mi = min(mi, sum);
cnt[sum]++;
}
if (a[mid] == num[i]) {
sum++;
} else {
sum--;
}
for (int j = mi; j <= ma; j++) {
cnt[j] += cnt[j - 1];
}
for (int j = mid + 1; j <= r; j++) {
if (a[j] == num[i]) {
sum++;
} else {
sum--;
}
ans += cnt[min(sum - 1, ma)];
}
for (int j = mi; j <= ma; j++) {
cnt[j] = 0;
}
}
solve(l, mid);
solve(mid + 1, r);
}
int main() {
cin >> n >> ddd;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
solve(1, n);
cout << ans;
return 0;
}
猫树分治
实话说,我就做过一个关于这个的题,感觉和普通分治没有什么区别;
直接粘我以前写的题解了(出处):
暴力一:每次跑一边
暴力二:使用背包的合并操作,时间复杂度
正解:猫树分治;
这玩意听着这么像数据结构,其实就是一个套路;
好像它的发明者受到了线段树分治的启发?
和普通的分治没什么区别,难的是想到分治(所以才给它起了个名字嘛);
每次只计算跨过分治中心的区间,首先预处理出从分治中心向左和向右的每个点到终点这段区间的所有
所以我们需要四个指针,两个记录现在处理的序列上的左右端点,另外两个记录现在处理的问题的区间(这里的 “区间” 并不绝对,只要是没处理的,都能出现在这一段区间),然后正常递归即可;
时间复杂度:
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m;
int h[500005], w[500005];
struct sss{
int l, r, t;
}b[500005];
int ans[500005], p[500005], s[500005], ncnt;
int f[50005][205];
void solve(int l, int r, int L, int R) {
if (L > R) return;
int mid = (l + r) >> 1;
int Mid = L - 1;
for (int i = 0; i <= 200; i++) f[mid][i] = 0;
for (int i = mid + 1; i <= r; i++) {
for (int j = 0; j < h[i]; j++) f[i][j] = f[i - 1][j];
for (int j = h[i]; j <= 200; j++) {
f[i][j] = max(f[i - 1][j], f[i - 1][j - h[i]] + w[i]);
}
}
for (int i = h[mid]; i <= 200; i++) f[mid][i] = w[mid];
for (int i = mid - 1; i >= l; i--) {
for (int j = 0; j < h[i]; j++) f[i][j] = f[i + 1][j];
for (int j = h[i]; j <= 200; j++) {
f[i][j] = max(f[i + 1][j], f[i + 1][j - h[i]] + w[i]);
}
}
ncnt = 0;
int u = 0;
for (int i = L; i <= R; i++) {
u = p[i];
if (b[u].r <= mid) p[++Mid] = u;
else if (mid < b[u].l) s[++ncnt] = u;
else {
int ret = 0;
for (int i = 0; i <= b[u].t; i++) {
ret = max(ret, f[b[u].l][i] + f[b[u].r][b[u].t - i]);
}
ans[u] = ret;
}
}
for (int i = 1; i <= ncnt; i++) {
p[Mid + i] = s[i];
}
R = ncnt + Mid;
solve(l, mid, L, Mid);
solve(mid + 1, r, Mid + 1, R);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (register int i = 1; i <= n; i++) {
cin >> h[i];
}
for (register int i = 1; i <= n; i++) {
cin >> w[i];
}
for (register int i = 1; i <= m; i++) {
cin >> b[i].l >> b[i].r >> b[i].t;
if (b[i].l == b[i].r) {
if (b[i].t >= h[b[i].l]) ans[i] = w[b[i].l];
} else {
p[++ncnt] = i;
}
}
solve(1, n, 1, ncnt);
for (int i = 1; i <= m; i++) {
cout << ans[i] << endl;
}
return 0;
}
线段树分治
在区间上的操作,线段树好像都能干,并且它长得就很能分治,所以用它也并不奇怪;
看见这种在时间线上的题,一般好像可以用线段树分治来做;
以前好像还有一道,但是忘了;
首先判断二分图,我们使用可撤销的扩展域并查集,具体可以看看原题解区;
具体的,我们对于这一条时间线开一个线段树,每个节点开一个动态数组存这个点所管辖的时间段内所加边的下标,最后从根开始
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
using namespace std;
int n, m, k;
stack<pair<int, pair<int, int> > > s;
int fa[500005];
int u[500005], t[500005];
int siz[500005];
int find(int x) {
return (x == fa[x]) ? x : find(fa[x]);
}
namespace seg{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r;
vector<int> v;
}tr[500005];
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int id, int l, int r, int d) {
if (tr[id].l >= l && tr[id].r <= r) {
tr[id].v.push_back(d);
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (l <= mid) add(ls(id), l, r, d);
if (r > mid) add(rs(id), l, r, d);
}
}
void merge(int x, int y) {
if (x == y) return;
if (siz[x] > siz[y]) swap(x, y);
s.push({y, {siz[x], x}});
fa[x] = y;
siz[y] += siz[x];
}
void dfs(int id) {
bool vis = true;
int o = s.size();
for (int i = 0; i < seg::tr[id].v.size(); i++) {
int x = seg::tr[id].v[i];
int uu = find(u[x]);
int tt = find(t[x]);
if (uu == tt) {
for (int j = seg::tr[id].l; j <= seg::tr[id].r; j++) cout << "No" << endl;
vis = false;
break;
}
merge(find(u[x] + n), tt);
merge(find(t[x] + n), uu);
}
if (vis) {
if (seg::tr[id].l == seg::tr[id].r) {
cout << "Yes" << endl;
} else {
dfs(seg::ls(id));
dfs(seg::rs(id));
}
}
while(s.size() > o) {
siz[s.top().first] -= s.top().second.first;
fa[s.top().second.second] = s.top().second.second;
s.pop();
}
}
int main() {
cin >> n >> m >> k;
seg::bt(1, 1, k);
int l, r;
for (int i = 1; i <= m; i++) {
cin >> u[i] >> t[i] >> l >> r;
if (l != r) {
seg::add(1, l + 1, r, i);
}
}
for (int i = 1; i <= 2 * n; i++) {
fa[i] = i;
siz[i] = 1;
}
dfs(1);
return 0;
}
点分治
静态点分治
考虑此题,暴力非常好想,对于每个询问,枚举起点及终点即可;
时间复杂度:
引入点分治:
对于两点
-
跨越根节点;
-
不跨越根节点;
对于第一类路径,不妨设一点
对于第二类路径,其
这样很明显要用到分治;
所以我们就有了思路:对于每一次询问,对其子树单独求解,最后得到答案;
但是,时间复杂度仍然不能保证。考虑一条链,这样我们需要对
所以,我们有了一种新的方法:找树的重心;
树的重心,即最大的子树大小不超过
每次进行分治时,我们找子树的重心作为根节点进行分治,这样时间复杂度就变为了:整体点分治
对于证明(我不会),可以参考一篇dalao的证明;
其实可以感性理解一下,我们每次找重心时,都会把现在树的大小除以
大体思路已经清楚,现在看代码实现:
对于找重心的实现,设
所以我们就可以对现有子树进行
找重心的代码:
void get_rt(int x, int fa) {
siz[x] = 1;
maxp[x] = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa || vis[u]) continue;
get_rt(u, x);
siz[x] += siz[u];
maxp[x] = max(maxp[x], siz[u]);
}
maxp[x] = max(maxp[x], sum - siz[x]);
if (maxp[x] < maxp[rt]) rt = x;
}
其实对于大部分的点分治题目来说,所需要的一共有三个函数:
-
函数:用于构建分治框架; -
函数:用于计算当前答案; -
函数:用于找重心;
对于第一种函数,各个题目基本都是相同的,对于本题大致如下:
void solve(int x) {
vis[x] = true;
judge[0] = 1;
calc(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
sum = siz[u];
rt = 0;
maxp[0] = 0x3f3f3f3f;
get_rt(u, 0);
solve(rt);
}
}
对于第二种函数,随题目的不同而不同,对于本题大致如下:
void get_dis(int x, int fa) {
if (dis[x] > 1e7) return; //防炸数组;
rem[++rem[0]] = dis[x];
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa || vis[u]) continue;
dis[u] = dis[x] + e[i].w;
get_dis(u, x);
}
}
void calc(int x) {
int o = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
rem[0] = 0;
dis[u] = e[i].w;
get_dis(u, x);
for (int j = rem[0]; j >= 1; j--) {
for (int k = 1; k <= m; k++) {
if (ask[k] >= rem[j]) {
if (ask[k] - rem[j] > 1e7) continue; //防炸数组;
test[k] |= judge[ask[k] - rem[j]];
}
}
}
for (int j = rem[0]; j >= 1; j--) {
s[++o] = rem[j];
judge[rem[j]] = true;
}
}
for (int i = 1; i <= o; i++) {
judge[s[i]] = false;
}
}
如上,我们对于本题,在线做法不能忍受,所以要离线,
这样,本题的做法就明朗了;
代码:
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
struct sss{
int t, ne, w;
}e[1000005];
int h[1000005], cnt;
int sum, rt;
int ask[1000005];
int maxp[1000005], siz[1000005];
bool vis[1000005];
bool judge[1000005], test[1000005];
int rem[1000005], dis[1000005];
int s[1000005];
void add(int u, int v, int ww) {
e[++cnt].t = v;
e[cnt].w = ww;
e[cnt].ne = h[u];
h[u] = cnt;
}
void get_rt(int x, int fa) {
siz[x] = 1;
maxp[x] = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa || vis[u]) continue;
get_rt(u, x);
siz[x] += siz[u];
maxp[x] = max(maxp[x], siz[u]);
}
maxp[x] = max(maxp[x], sum - siz[x]);
if (maxp[x] < maxp[rt]) rt = x;
}
void get_dis(int x, int fa) {
if (dis[x] > 1e7) return; //防炸数组;
rem[++rem[0]] = dis[x];
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa || vis[u]) continue;
dis[u] = dis[x] + e[i].w;
get_dis(u, x);
}
}
void calc(int x) {
int o = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
rem[0] = 0;
dis[u] = e[i].w;
get_dis(u, x);
for (int j = rem[0]; j >= 1; j--) {
for (int k = 1; k <= m; k++) {
if (ask[k] >= rem[j]) {
if (ask[k] - rem[j] > 1e7) continue; //防炸数组;
test[k] |= judge[ask[k] - rem[j]];
}
}
}
for (int j = rem[0]; j >= 1; j--) {
s[++o] = rem[j];
judge[rem[j]] = true;
}
}
for (int i = 1; i <= o; i++) {
judge[s[i]] = false;
}
}
void solve(int x) {
vis[x] = true;
judge[0] = 1;
calc(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
sum = siz[u];
rt = 0;
maxp[0] = 0x3f3f3f3f;
get_rt(u, 0);
solve(rt);
}
}
int main() {
cin >> n >> m;
int u, v, w;
for (int i = 1; i < n; i++) {
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
for (int i = 1; i <= m; i++) {
cin >> ask[i];
}
maxp[rt] = 0x3f3f3f3f;
sum = n;
get_rt(1, 0);
solve(rt);
for (int i = 1; i <= m; i++) {
if (test[i]) {
cout << "AYE" << endl;
} else {
cout << "NAY" << endl;
}
}
return 0;
}
还有一些题,这里大体整一下思路;
板子题,记一下
板子题,开个二元组记录一下权值和边数即可;
板子题,和第一题类似,只不过开个树状数组记录一下前缀和,然后就解决了;
其实思路不难,但细节太多了。。。
首先,路径还是能拆分成两类:经过根的和不经过根的;
所以可以点分治;
首先将每个点的儿子按大小排序,因为这样我们就可以比较方便的处理到根的路径颜色相同的子树们;
然后进行点分治,我们开两个线段树,把与当前路径颜色相同的放进一个线段树,不同的放进一个线段树,然后正常跑就行;
注意线段树的清空,可以直接在根节点上打懒标记;
然后就是一些细节,不说了,可以看代码;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int n, m, l, r;
int c[500005];
int rt, sum;
struct sss{
int t, ne, w;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v, int ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
e[cnt].w = ww;
h[u] = cnt;
}
vector<pair<int, int> > v[200005];
struct sas{
int dis, sum;
}dis[200005], rem[200005], po[200005];
int maxp[1000005], siz[1000005], dep[1000005];
bool vis[1000005];
int ans;
namespace seg{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct asa{
int l, r, ma, lz;
}tr[2][900005];
inline void push_up(int s, int id) {
tr[s][id].ma = max(tr[s][ls(id)].ma, tr[s][rs(id)].ma);
}
inline void push_down(int s, int id) {
if(tr[s][id].lz != 0) {
tr[s][ls(id)].lz = tr[s][id].lz;
tr[s][rs(id)].lz = tr[s][id].lz;
tr[s][ls(id)].ma = tr[s][id].lz;
tr[s][rs(id)].ma = tr[s][id].lz;
tr[s][id].lz = 0;
}
}
void bt(int s, int id, int l, int r) {
tr[s][id].l = l;
tr[s][id].r = r;
if (l == r) {
tr[s][id].ma = -0x3f3f3f3f;
tr[s][id].lz = 0;
return;
}
int mid = (l + r) >> 1;
bt(s, ls(id), l, mid);
bt(s, rs(id), mid + 1, r);
push_up(s, id);
}
inline void clear(int s) {
tr[s][1].lz = -0x3f3f3f3f;
tr[s][1].ma = -0x3f3f3f3f;
}
int ask(int s, int id, int l, int r) {
if (tr[s][id].l >= l && tr[s][id].r <= r) {
return tr[s][id].ma;
}
push_down(s, id);
int mid = (tr[s][id].l + tr[s][id].r) >> 1;
if (r <= mid) return ask(s, ls(id), l, r);
else if (l > mid) return ask(s, rs(id), l, r);
else return max(ask(s, ls(id), l, mid), ask(s, rs(id), mid + 1, r));
}
void add(int s, int id, int pos, int d) {
if (tr[s][id].l == tr[s][id].r) {
tr[s][id].ma = max(tr[s][id].ma, d);
tr[s][id].lz = 0;
return;
}
push_down(s, id);
int mid = (tr[s][id].l + tr[s][id].r) >> 1;
if (pos <= mid) add(s, ls(id), pos, d);
else add(s, rs(id), pos, d);
push_up(s, id);
}
}
void get_rt(int x, int f) {
siz[x] = 1;
maxp[x] = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == f || vis[u]) continue;
get_rt(u, x);
siz[x] += siz[u];
maxp[x] = max(maxp[x], siz[u]);
}
maxp[x] = max(maxp[x], sum - siz[x]);
if (maxp[rt] > maxp[x]) rt = x;
}
void get_dis(int x, int f, int pre) {
dep[x] = dep[f] + 1;
if (dep[x] > r) return;
dis[x].dis = dep[x];
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u] || u == f) continue;
dis[u].sum = dis[x].sum; //注意继承的问题;
if (e[i].w != pre && e[i].w) dis[u].sum += c[e[i].w]; //注意判断;
get_dis(u, x, e[i].w);
}
}
void dfs(int x, int f) {
if (dis[x].dis == 0) return;
rem[++rem[0].sum] = sas{dis[x].dis, dis[x].sum};
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == f || vis[u]) continue;
dfs(u, x);
}
}
void calc(int x) {
int color = 0;
int o = 0;
dep[x] = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
if (color == 0) {
color = e[i].w;
} else if (color != e[i].w) {
color = e[i].w;
seg::clear(1);
for (int j = 1; j <= o; j++) {
seg::add(0, 1, po[j].dis, po[j].sum);
}
o = 0;
}
rem[0].sum = 0;
dis[u].sum = c[e[i].w];
get_dis(u, x, e[i].w);
dfs(u, x);
for (int j = 1; j <= rem[0].sum; j++) {
if (rem[j].dis > r) continue;
if (rem[j].dis >= l && rem[j].dis <= r) {
ans = max(ans, rem[j].sum);
}
if (rem[j].dis == r) continue;
if (rem[j].dis == 0) continue;
int aa = seg::ask(0, 1, max(0, l - rem[j].dis), r - rem[j].dis);
int bb = seg::ask(1, 1, max(0, l - rem[j].dis), r - rem[j].dis);
bb -= c[e[i].w];
ans = max(ans, max(rem[j].sum + aa, rem[j].sum + bb));
}
for (int j = 1; j <= rem[0].sum; j++) {
if (rem[j].dis == 0) continue;
o++;
po[o].dis = rem[j].dis;
po[o].sum = rem[j].sum;
}
for (int j = 1; j <= rem[0].sum; j++) {
if (rem[j].dis == 0) continue;
seg::add(1, 1, rem[j].dis, rem[j].sum);
}
}
seg::clear(0);
seg::clear(1);
}
void solve(int x) {
vis[x] = true;
calc(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
rt = 0;
maxp[rt] = 0x3f3f3f3f;
sum = siz[u];
get_rt(u, 0);
solve(rt);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> l >> r;
for (int i = 1; i <= m; i++) {
cin >> c[i];
}
int x, y, w;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y >> w;
v[x].push_back({w, y});
v[y].push_back({w, x});
}
for (int i = 1; i <= n; i++) {
sort(v[i].begin(), v[i].end());
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < v[i].size(); j++) {
add(i, v[i][j].second, v[i][j].first);
}
}
seg::bt(0, 1, 0, n);
seg::bt(1, 1, 0, n);
ans = -0x3f3f3f3f;
rt = 0;
maxp[rt] = 0x3f3f3f3f;
sum = n;
get_rt(1, 0);
solve(rt);
cout << ans;
return 0;
}
貌似题解有单调队列的优秀做法,但我不会,有兴趣的可以去看看;
可能是我目光短浅,感觉点分治这玩意就是模板 + 数据结构维护,没啥的;
当然前提是得看出来是点分治。。。
动态点分治,点分树
一般我们遇到的问题不单单只是查询,还有修改,如果此时每个修改都进行一边点分治的话,时间复杂度是不允许的,这时候,我们就需要用到点分树;
如题,加了一个单点修改的操作;
既然我们每次都要找重心,那么不妨将子树的重心与当前考虑的树的重心连边,这样我们就得到了一棵点分树;
这样做有什么优点呢?不难发现,这棵树高是
这样,我们只需要记录点分树上的父子关系,每次询问时,从询问的点(设为
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
int n, m;
int a[100005];
inline int lowbit(int x) {
return x & (-x);
}
struct sss{
int t, ne;
}e[500005];
int h[500005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int dep[100005], f[100005][35];
int fa[100005], maxp[100005], siz[100005];
int dfn[100005];
int dcnt;
int sum, rt;
bool vis[100005];
vector<int> tr[2][100005];
void init_dfs(int x, int ff) {
dep[x] = dep[ff] + 1;
dfn[x] = ++dcnt;
f[x][0] = ff;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == ff) continue;
init_dfs(u, x);
}
}
void get_rt(int x, int ff) {
maxp[x] = 0;
siz[x] = 1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u] || u == ff) continue;
get_rt(u, x);
siz[x] += siz[u];
maxp[x] = max(maxp[x], siz[u]);
}
maxp[x] = max(maxp[x], sum - siz[x]);
if (maxp[x] < maxp[rt]) rt = x;
}
void dfs(int x) {
siz[x] = sum + 1;
vis[x] = true;
tr[0][x].resize(siz[x] + 1);
tr[1][x].resize(siz[x] + 1);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u]) continue;
sum = siz[u];
rt = 0;
maxp[rt] = 0x3f3f3f3f;
get_rt(u, 0);
fa[rt] = x;
dfs(rt);
}
}
int mi(int x, int y) {
return dfn[x] < dfn[y] ? x : y;
}
int lca(int x, int y) {
if (x == y) return x;
// x = dfn[x];
// y = dfn[y];
// if (x > y) swap(x, y);
// x++;
// int d = __lg(y - x + 1);
// return mi(f[x][d], f[y - (1 << d) + 1][d]);
if (dep[x] < dep[y]) swap(x, y);
for (int i = 30; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 30; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int get_dis(int x, int y) {
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
}
void add_tr(int s, int x, int dis, int d) {
dis++;
for (int i = dis; i <= siz[x]; i += lowbit(i)) tr[s][x][i] += d;
}
void def(int x, int d) {
for (int i = x; i; i = fa[i]) {
add_tr(0, i, get_dis(i, x), d);
}
for (int i = x; fa[i]; i = fa[i]) {
add_tr(1, i, get_dis(fa[i], x), d);
}
}
int ask(int s, int x, int d) {
int ans = 0;
d++;
d = min(d, siz[x]);
for (int i = d; i; i -= lowbit(i)) {
ans += tr[s][x][i];
}
return ans;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int x, y;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
init_dfs(1, 0);
for (int j = 1; j <= 33; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
sum = n;
rt = 0;
maxp[rt] = 0x3f3f3f3f;
get_rt(1, 0);
dfs(rt); // establish a dian fen tree;
for (int i = 1; i <= n; i++) {
def(i, a[i]);
}
int ans = 0;
int s;
for (int i = 1; i <= m; i++) {
cin >> s >> x >> y;
x ^= ans;
y ^= ans;
if (s == 1) {
def(x, y - a[x]);
a[x] = y;
}
if (s == 0) {
ans = 0;
ans += ask(0, x, y);
for (int i = x; fa[i]; i = fa[i]) {
int dis = get_dis(x, fa[i]);
if (y >= dis) {
ans += ask(0, fa[i], y - dis) - ask(1, i, y - dis);
}
}
cout << ans << endl;
}
}
return 0;
}
CDQ分治
CDQ的题,一般都能用线段树做,一个不行,那就树套树。 ---学长-Houraisan-Kaguya
确实,CDQ好像就是模拟了一下线段树的递归操作,但说实话,它确实比树套树好写;
应用范围
CDQ分治通常用来解决高维偏序问题;
一个
这时候,通常解法是树套树套树套。。。,每层树维护一个偏序关系,最后查询一下即可;
但毕竟大多数人不想写这样的数据结构,因为常数大,空间大,而且还难调。。。
所以,CDQ分治被发明出来了;
具体实现
来一道例题:Luogu P3810 【模板】三维偏序(陌上花开)
这是三维偏序;
首先,我们以
当然我们不能用正常的二位偏序的方法去做,因为现在排完序的顺序不能改变了,那应该怎么做呢?
考虑分治,因为我们发现,当我们把一段区间分成左右两个子区间时,左子区间的某些值可以对右子区间的某些值做出贡献;
这就是CDQ分治的思想;
具体地,我们分治时将左子区间和右子区间分别按
这样一来,我们就把问题转成了一维偏序问题了,所以对于
我们用指针
注意最后要清空树状数组;
这样就做完了;
时间复杂度:
可以发现,和树套树的复杂度基本一样,但常数少很多;
又可以发现,对于一个
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, k, m;
inline int lowbit(int x) {
return x & (-x);
}
struct sss{
int a, b, c, ans, w;
}a[500005], b[500005];
int tr[500005];
int ans[500005];
void add(int pos, int d) {
for (int i = pos; i <= k; i += lowbit(i)) tr[i] += d;
}
int ask(int pos) {
int ans = 0;
for (int i = pos; i; i -= lowbit(i)) ans += tr[i];
return ans;
}
bool cmpa(sss x, sss y) {
if (x.a == y.a) {
if (x.b == y.b) {
return x.c < y.c;
} else {
return x.b < y.b;
}
} else {
return x.a < y.a;
}
}
bool cmpb(sss x, sss y) {
if (x.b == y.b) {
return x.c < y.c;
} else {
return x.b < y.b;
}
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(b + l, b + mid + 1, cmpb);
sort(b + mid + 1, b + r + 1, cmpb);
int i = mid + 1;
int j = l;
while(i <= r) {
while(j <= mid && b[j].b <= b[i].b) {
add(b[j].c, b[j].w);
j++;
}
b[i].ans += ask(b[i].c);
i++;
}
for (int i = l; i < j; i++) {
add(b[i].c, -b[i].w);
}
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i].a >> a[i].b >> a[i].c;
}
sort(a + 1, a + 1 + n, cmpa);
int c = 0;
for (int i = 1; i <= n; i++) {
c++;
if (a[i].a != a[i + 1].a || a[i].b != a[i + 1].b || a[i].c != a[i + 1].c) {
b[++m] = a[i];
b[m].w = c;
c = 0;
}
}
cdq(1, m);
for (int i = 1; i <= m; i++) {
ans[b[i].ans + b[i].w - 1] += b[i].w;
}
for (int i = 0; i < n; i++) {
cout << ans[i] << endl;
}
return 0;
}
例题
把时间
同时利用一下二维差分,把一次查询看成四个小查询,但要注意可能会减成
时间复杂度同模板;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int s, w, x, y, xx, yy;
inline int lowbit(int x) {
return x & (-x);
}
struct sss{
int t, x, y, w;
bool s;
}e[500005];
int n;
bool cmpx(sss x, sss y) {
if (x.x == y.x) return x.y < y.y;
else return x.x < y.x;
}
bool cmp(sss x, sss y) {
return x.t < y.t;
}
int tr[3000005];
void add(int pos, int d) {
for (int i = pos; i <= w; i += lowbit(i)) tr[i] += d;
}
int ask(int pos) {
int ans = 0;
for (int i = pos; i; i -= lowbit(i)) ans += tr[i];
return ans;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(e + l, e + mid + 1, cmpx);
sort(e + mid + 1, e + r + 1, cmpx);
int i = mid + 1;
int j = l;
while(i <= r) {
while(e[i].x >= e[j].x && j <= mid) {
if (e[j].s == 0) add(e[j].y, e[j].w);
j++;
}
if (e[i].s == 1) {
e[i].w += ask(e[i].y);
}
i++;
}
for (int i = l; i < j; i++) {
if (e[i].s == 0) add(e[i].y, -e[i].w);
}
}
int main() {
while(cin >> s) {
if (s == 3) break;
if (s == 0) {
cin >> w;
w++;
}
if (s == 1) {
cin >> x >> y >> xx;
x++;
y++;
e[++n] = {n, x, y, xx, 0};
}
if (s == 2) {
cin >> x >> y >> xx >> yy;
x++;
y++;
xx++;
yy++;
e[++n] = {n, xx, yy, 0, 1};
e[++n] = {n, x - 1, y - 1, 0, 1};
e[++n] = {n, xx, y - 1, 0, 1};
e[++n] = {n, x - 1, yy, 0, 1};
}
}
cdq(1, n);
sort(e + 1, e + 1 + n, cmp);
int i = 1;
while(i <= n) {
if (e[i].s == 1) {
cout << e[i].w + e[i + 1].w - e[i + 2].w - e[i + 3].w << endl;
i += 4;
} else {
i++;
}
}
return 0;
}
对于每一个询问
其中,
依据以往的套路,我们想着要去掉绝对值,于是对于每一个
那么我们要求的就变成了:
当然,只有时间在 询问
那么这就是四个三维偏序问题,用四次CDQ求解即可;
当然,你也可以进行一些变换,使其变成三个相同条件(但也是要用四次CDQ);
注意每次CDQ前都要按时间重新排一次序;
时间复杂度:
但常数很大,注意卡常;
加了一点树状数组的剪枝;
注意树状数组中
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define FI(n) FastIO::read(n)
#define FO(n) FastIO::write(n)
#define Flush FastIO::Fflush()
namespace FastIO {
const int SIZE=1<<16;
char buf[SIZE],obuf[SIZE],str[60];
int bi=SIZE,bn=SIZE,opt;
inline int read(register char *s) {
while(bn){
for(;bi<bn&&buf[bi]<=' ';bi=-~bi);
if(bi<bn)break;
bn=fread(buf,1,SIZE,stdin);
bi&=0;
}
register int sn=0;
while(bn){
for(;bi<bn&&buf[bi]>' ';bi=-~bi)s[sn++]=buf[bi];
if(bi<bn)break;
bn=fread(buf,1,SIZE,stdin);
bi&=0;
}
s[sn]&=0;
return sn;
}
inline bool read(register int &x){
int n=read(str),bf=0;
if(!n)return 0;
register int i=0;
(str[i]=='-')&&(bf=1,i=-~i);
(str[i]=='+')&&(i=-~i);
for(x=0;i<n;i=-~i)x=(x<<3)+(x<<1)+(str[i]^48);
bf&&(x=~x+1);
return 1;
}
inline bool read(register long long &x) {
int n=read(str),bf=1;
if(!n)return 0;
register int i=0;
(str[i]=='-')&&(bf=-1,i=-~i);
for(x=0;i<n;i=-~i)x=(x<<3)+(x<<1)+(str[i]^48);
(bf<0)&&(x=~x+1);
return 1;
}
inline void write(register int x) {
if(!x)obuf[opt++]='0';
else{
(x<0)&&(obuf[opt++]='-',x=~x+1);
register int sn=0;
while(x)str[sn++]=x%10+'0',x/=10;
for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
}
(opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
}
inline void write(register long long x) {
if(!x)obuf[opt++]='0';
else{
(x<0)&&(obuf[opt++]='-',x=~x+1);
register int sn=0;
while(x)str[sn++]=x%10+'0',x/=10;
for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
}
(opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
}
inline void write(register unsigned long long x){
if(!x)obuf[opt++]='0';
else{
register int sn=0;
while(x)str[sn++]=x%10+'0',x/=10;
for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
}
(opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
}
inline void write(register char x) {
obuf[opt++]=x;
(opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
}
inline void Fflush(){
opt&&fwrite(obuf,1,opt,stdout);
opt&=0;
}
};
int n, m;
int ma;
namespace BIT{
int tr[2000005];
inline int lowbit(int x) {
return x & (-x);
}
inline void clear() {
for (int i = 1; i <= ma + 1; i++) tr[i] = -0x3f3f3f3f;
}
void add(int s, int pos, int d) {
pos++;
if (s == 1) for (register int i = pos; i <= ma + 1; i += lowbit(i)) {
if (tr[i] >= d && d != -0x3f3f3f3f) return;
else tr[i] = (d == -0x3f3f3f3f ? d : max(tr[i], d));
} else {
for (register int i = pos; i; i -= lowbit(i)){
if (tr[i] >= d && d != -0x3f3f3f3f) return;
else tr[i] = (d == -0x3f3f3f3f ? d : max(tr[i], d));
}
}
}
int ask(int s, int d) {
d++;
int ans = -0x3f3f3f3f;
if (s == 1) for (register int i = d; i; i -= lowbit(i)) ans = max(ans, tr[i]);
else for (register int i = d; i <= ma + 1; i += lowbit(i)) ans = max(ans, tr[i]);
return ans;
}
}
using namespace BIT;
int tim, cnt;
struct sss{
int t, x, y, s, ans;
}e[5000005], t[5000005];
bool cmpx(sss x, sss y) {
if (x.x == y.x) return x.y < y.y;
else return x.x < y.x;
}
bool cmpxx(sss x, sss y) {
if (x.x == y.x) return x.y > y.y;
else return x.x > y.x;
}
bool cmp(sss x, sss y) {
return x.t < y.t;
}
void cdq(int o, int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(o, l, mid);
cdq(o, mid + 1, r);
int i = mid + 1;
int j = l;
while(i <= r) {
if (o <= 2) {
while(j <= mid && e[j].x <= e[i].x) {
if (e[j].s == 1) {
if (o == 1) {
add(1, e[j].y, e[j].x + e[j].y);
} else if (o == 2) {
add(2, e[j].y, e[j].x - e[j].y);
}
}
j++;
}
} else {
while(j <= mid && e[j].x >= e[i].x) {
if (e[j].s == 1) {
if (o == 3) {
add(2, e[j].y, -(e[j].x + e[j].y));
} else if (o == 4) {
add(1, e[j].y, e[j].y - e[j].x);
}
}
j++;
}
}
if (e[i].s == 2) {
if (o == 1) {
e[i].ans = min(e[i].ans, e[i].x + e[i].y - ask(1, e[i].y));
} else if (o == 2) {
e[i].ans = min(e[i].ans, e[i].x - e[i].y - ask(2, e[i].y));
} else if (o == 3) {
e[i].ans = min(e[i].ans, -e[i].x - e[i].y - ask(2, e[i].y));
} else if (o == 4) {
e[i].ans = min(e[i].ans, e[i].y - e[i].x - ask(1, e[i].y));
}
}
i++;
}
for (register int i = l; i < j; i++) {
if (o == 1 || o == 4) add(1, e[i].y, -0x3f3f3f3f);
else add(2, e[i].y, -0x3f3f3f3f);
}
i = mid + 1;
j = l;
int k = l - 1;
if (o <= 2) {
while(i <= r) {
while(j <= mid && cmpx(e[j], e[i])) {
t[++k] = e[j];
j++;
}
t[++k] = e[i];
i++;
}
} else {
while(i <= r) {
while(j <= mid && cmpxx(e[j], e[i])) {
t[++k] = e[j];
j++;
}
t[++k] = e[i];
i++;
}
}
while(i <= r) {
t[++k] = e[i];
i++;
}
while(j <= mid) {
t[++k] = e[j];
j++;
}
for (register int i = l; i <= r; i++) e[i] = t[i];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
FI(n);
FI(m);
int x, y;
for (register int i = 1; i <= n; i++) {
tim++;
FI(x);
FI(y);
e[++cnt] = {tim, x, y, 1, 0x3f3f3f3f};
ma = max(ma, y);
}
int ss;
for (register int i = 1; i <= m; i++) {
FI(ss);
FI(x);
FI(y);
ma = max(ma, y);
tim++;
e[++cnt] = {tim, x, y, ss, 0x3f3f3f3f};
}
clear();
cdq(1, 1, cnt);
sort(e + 1, e + 1 + cnt, cmp);
clear();
cdq(2, 1, cnt);
sort(e + 1, e + 1 + cnt, cmp);
clear();
cdq(3, 1, cnt);
sort(e + 1, e + 1 + cnt, cmp);
clear();
cdq(4, 1, cnt);
sort(e + 1, e + 1 + cnt, cmp);
for (register int i = 1; i <= cnt; i++) {
if (e[i].s == 2) {
FO(e[i].ans);
FO('\n');
}
}
Flush;
return 0;
}
之前在CSDN上看见一篇博客说划分树能解决动态逆序对,但他既没给讲解,也没给实现,我也没找到用划分树解决的题解,貌似不行吧(毕竟划分树是静态的。。。);
考虑能产生贡献的一对点对
或:
此时
我们将初始序列看作全
跑两遍三维偏序即可,时间复杂度:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m;
struct sss{
long long t, pos, val, s, id;
}e[500005], t[500005];
long long pos[500005];
long long ans[500005];
long long cnt, tim;
namespace BIT{
long long tr[500005];
inline long long lowbit(long long x) {
return x & (-x);
}
void add(long long p, long long d) {
for (long long i = p; i <= n; i += lowbit(i)) tr[i] += d;
}
long long ask(long long p) {
long long ans = 0;
for (long long i = p; i; i -= lowbit(i)) ans += tr[i];
return ans;
}
}
using namespace BIT;
void cdq1(long long l, long long r) {
if (l == r) return;
long long mid = (l + r) >> 1;
cdq1(l, mid);
cdq1(mid + 1, r);
long long i = mid + 1;
long long j = l;
while(i <= r) {
while(j <= mid && e[j].pos <= e[i].pos) {
add(e[j].val, e[j].s);
j++;
}
ans[e[i].id] += e[i].s * (ask(n) - ask(e[i].val));
i++;
}
for (long long i = l; i < j; i++) {
add(e[i].val, -e[i].s);
}
i = mid + 1;
j = l;
long long k = l - 1;
while(i <= r) {
while(j <= mid && e[j].pos <= e[i].pos) {
t[++k] = e[j];
j++;
}
t[++k] = e[i];
i++;
}
while(i <= r) {
t[++k] = e[i];
i++;
}
while(j <= mid) {
t[++k] = e[j];
j++;
}
for (long long i = l; i <= r; i++) {
e[i] = t[i];
}
}
void cdq2(long long l, long long r) {
if (l == r) return;
long long mid = (l + r) >> 1;
cdq2(l, mid);
cdq2(mid + 1, r);
long long i = mid + 1;
long long j = l;
while(i <= r) {
while(j <= mid && e[j].pos >= e[i].pos) {
add(e[j].val, e[j].s);
j++;
}
ans[e[i].id] += e[i].s * ask(e[i].val - 1);
i++;
}
for (long long i = l; i < j; i++) {
add(e[i].val, -e[i].s);
}
i = mid + 1;
j = l;
long long k = l - 1;
while(i <= r) {
while(j <= mid && e[j].pos >= e[i].pos) {
t[++k] = e[j];
j++;
}
t[++k] = e[i];
i++;
}
while(i <= r) {
t[++k] = e[i];
i++;
}
while(j <= mid) {
t[++k] = e[j];
j++;
}
for (long long i = l; i <= r; i++) {
e[i] = t[i];
}
}
inline bool cmp(sss x, sss y) {
return x.t < y.t;
}
int main() {
cin >> n >> m;
long long x;
for (long long i = 1; i <= n; i++) {
cin >> x;
pos[x] = i;
e[++cnt] = {++tim, i, x, 1, 0};
}
for (long long i = 1; i <= m; i++) {
cin >> x;
e[++cnt] = {++tim, pos[x], x, -1, i};
}
cdq1(1, cnt);
sort(e + 1, e + 1 + cnt, cmp);
cdq2(1, cnt);
long long an = 0;
for (long long i = 0; i < m; i++) {
an += ans[i];
cout << an << endl;
}
return 0;
}
注意:变换序列可以是原序列;
这道题有一个新的套路:用CDQ搞DP;
我们设
其中要满足:
或
(因为只能有一个变的,且要所有可能的序列都满足,所以使条件最苛刻即可);
我们把转移顺序看作第一维,和剩下两维构成了一个三维偏序问题,所以用CDQ搞它即可;
注意这种题和普通的CDQ不同,我们分治时必须要把左边区间全部更新完在更新右边区间,并且每次回溯时要按转移顺序重新排序,因为回溯的时候还要继续递归;
这里应该就不能用归并排序了,因为没有统一的
具体细节看代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
struct sss{
int a, mi, ma, id;
}e[500005];
inline bool cmp1(sss x, sss y) {
return x.a < y.a;
}
inline bool cmp2(sss x, sss y) {
return x.ma < y.ma;
}
inline bool cmp3(sss x, sss y) {
return x.id < y.id;
}
int f[500005];
int ans;
namespace BIT{
int tr[500005];
inline int lowbit(int x) {
return x & (-x);
}
void add(int pos, int d) {
for (int i = pos; i <= 100000; i += lowbit(i)) {
if (d == 0) tr[i] = d;
else tr[i] = max(tr[i], d);
}
}
int ask(int pos) {
int an = 0;
for (int i = pos; i; i -= lowbit(i)) {
an = max(an, tr[i]);
}
return an;
}
}
using namespace BIT;
void cdq(int l, int r) {
if (l == r) {
f[l] = max(f[l], 1);
return;
}
int mid = (l + r) >> 1;
cdq(l, mid);
sort(e + l, e + mid + 1, cmp2);
sort(e + mid + 1, e + r + 1, cmp1);
int i = mid + 1;
int j = l;
while(i <= r) {
while(j <= mid && e[i].a >= e[j].ma) {
add(e[j].a, f[e[j].id]);
j++;
}
f[e[i].id] = max(f[e[i].id], ask(e[i].mi) + 1);
i++;
}
for (int i = l; i < j; i++) {
add(e[i].a, 0);
}
sort(e + l, e + r + 1, cmp3); //注意排序;
cdq(mid + 1, r); //先把左区间处理完再处理右区间;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
e[i].mi = e[i].a;
e[i].ma = e[i].a;
e[i].id = i;
}
int x, y;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
e[x].mi = min(e[x].mi, y);
e[x].ma = max(e[x].ma, y);
}
cdq(1, n);
for (int i = 1; i <= n; i++) ans = max(ans, f[i]);
cout << ans;
return 0;
}
To be continued...
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· 深度对比:PostgreSQL 和 SQL Server 在统计信息维护中的关键差异