2024西安铁一中集训DAY27 ---- 模拟赛((bfs,dp) + 整体二分 + 线段树合并 + (扫描线 + 线段树))

news2024/11/15 22:57:21

文章目录

  • 前言
  • 时间安排及成绩
  • 题解
    • A. 倒水(bfs + dp)
    • B. 让他们连通(整体二分 + 按秩合并并查集 / kruskal重构树)
    • C. 通信网络(线段树合并 + 二分)
    • D. 3SUM(扫描线 + 线段树)

前言

T1没做出来的一集。。。

时间安排及成绩

  • 7:40 开题
  • 7:40 - 7:45 T1看懂了,感觉像广搜,但是复杂度好像不对。又好像是背包,但是物品的体积和权值好像会算错。不是很会。看了一眼T2,好像是线段树分治的板子??
  • 7:45 - 8:00 T1还是不咋会,果断开T2。发现这题好像也不是线段树分治。想到答案肯定满足单调性,于是可以二分。但是不能对所有询问都二分,因此可以整体二分??中间也想过kruskal重构树,但感觉不太行。没想太多,感觉整体二分加上按秩合并并查集应该就行。直接开写。
  • 8:00 - 8:20 想了一会儿按秩合并并查集,然后写到一般发现我不会就算维护了点的连通性也没法 O ( 1 ) O(1) O(1) 判断一段区间是否联通!!!这。。。感觉好难啊。
  • 8:20 - 8:40 冷静思考,发现我们可以求出每个点和它前一个点最早的联通时间,那么一段区间联通的答案就是区间最大值。
  • 8:40 - 9:00 改完了,调了调,把样例都过了。感觉没啥问题,于是直接扔了。
  • 9:00 - 9:10 回去看T1,感觉还是没啥清晰的思路。直接开T3!!
  • 9:10 - 9:20 T2看懂了,发现明显具有单调性,可以直接二分答案 + 线段树合并。时间复杂度应该是 n l o g 2 n nlog^2n nlog2n,感觉可过就直接开写。
  • 9:20 - 10:10 写完了,但是样例答案是负数??回去重看了一遍题发现确实说明了答案有可能是负数。把二分的边界改一下就把所有样例过了??但是这个大样例跑的有点慢啊。
  • 10:10 - 10:40 放到oj上测试一下,发现确实过不去,会TLE。考虑一下卡常。最开始想的是不二分,直接用一个指针,然后每次检验。指针只会单减因此复杂度不会炸??但是被我构个菊花直接卡炸了。然后就想到可以对每个点分别二分求出答案,最后答案就是所有点的最小值。然后每个点二分时右边界可以根据其它点的答案缩小。也算卡常吧。
  • 10:40 - 11:10 改完了,大样例跑的飞快。检查一下,感觉没啥问题直接扔了。
  • 11:10 - 11:15 5min把T4看了,然后5min写了个40分跑路。回去看T1
  • 11:15 - 11:30 自认为想到一个很对的思路,但是写起来又感觉不会了。果断写暴力。
  • 11:30 - 11:55 暴力好难写,写完后编译。CE??!!感觉没问题啊。
  • 11:55 - 12:00 最后都没看出来啥问题,交了个CE的程序。

估分:0 + 100 + 100 + 40 = 240
得分:0 + 100 + 100 + 40 = 240
rk6

T3赛后卡双log好像没卡掉我。。

题解

A. 倒水(bfs + dp)

在这里插入图片描述
分析:

感觉是很好的一道题。

题意已经很清楚了,就不赘述了。

首先直接搜索需要纪录四个状态 桶里的水量和三个杯子中的水量。状态数是 1 0 5 × 10 0 3 10^5 \times 100^3 105×1003 的,显然过不去。我们想要减少状态数。

又不难发现实际上这个问题好像可以对应成背包问题:水量看作体积,操作数是代价。但是 被子倒满水后要不要倒掉 显然就成了一个问题。我们想让最后不用的那一次不倒,其他用的时候需要倒掉。这似乎没法在dp里体现。

正解是有一条性质:设操作(5) 为使用若干次操作(1),(2),(4),然后让被子中有一些水量。那么最优方案一定是进行若干次操作 (5),每个操作(5)后将所有有水的杯子中的水都倒入桶中。最后进行一次操作(5),但是可以将部分水倒入桶中。

这个打表证明是对的。但是我们也可以感性理解一下:对于一个杯子里的水而言,如果它不倒入桶中,并且以后还要使用,那么一定会在下次往里倒水前将里面的水倒掉。由于它里面的水不会倒入桶中,又因为交换操作对于操作数没有影响。因此我们可以把这个杯子中的水被倒掉的操作移到前面,形成 除了要往桶中倒水的杯子,其他杯子都是空的状态。并且这个状态不会影响最优性。如果这个杯子以后不使用了,那么可以看作使用最后一次操作(5)后只将部分水倒入桶中的状态。

那么就可以 b f s bfs bfs O ( 10 0 3 ) O(100^3) O(1003) 搜出所有状态,然后计算出 c o s t 1 cost_1 cost1 c o s t 2 cost_2 cost2 数组。接着用 c o s t 1 cost_1 cost1 c o s t 2 cost_2 cost2 转移 d p dp dp 即可。

复杂度 O ( 300 × n ) O(300 \times n) O(300×n)

CODE:

#include<bits/stdc++.h>
using namespace std;
typedef pair< int, int > PII;
const int N = 105;
const int M = 1e5 + 10;
int n, num[4], dis[N][N][N];
int cost1[N * 3], cost2[N * 3]; // 全部到入,部分倒入 
int dp[M], f[M];
bool vis[N][N][N];
struct state {
	int x, y, z; // 三杯水的质量分别为 x, y, z 
};
queue< state > qq;
PII pour(int x, int y, int lx, int ly) {
	return make_pair(x - min(x, ly - y), y + min(x, ly - y));
}
void ins(int x, int y, int z) {
	cost1[x + y + z] = min(cost1[x + y + z], dis[x][y][z] + (x > 0) + (y > 0) + (z > 0));
	if(x > 0) cost2[x] = min(cost2[x], dis[x][y][z] + 1);
	if(y > 0) cost2[y] = min(cost2[y], dis[x][y][z] + 1);
	if(z > 0) cost2[z] = min(cost2[z], dis[x][y][z] + 1);
	if(x + y > 0) cost2[x + y] = min(cost2[x + y], dis[x][y][z] + 2);
	if(x + z > 0) cost2[x + z] = min(cost2[x + z], dis[x][y][z] + 2);
	if(y + z > 0) cost2[y + z] = min(cost2[y + z], dis[x][y][z] + 2);
	if(x + y + z > 0) cost2[x + y + z] = min(cost2[x + y + z], dis[x][y][z] + 3);
}
int main() {
	cin >> num[1] >> num[2] >> num[3] >> n;
	sort(num + 1, num + 3 + 1);
	memset(cost1, 0x3f, sizeof cost1);
	memset(cost2, 0x3f, sizeof cost2);
	memset(dis, 0x3f, sizeof dis);
    vis[0][0][0] = 1; qq.push((state) {0, 0, 0});
    dis[0][0][0] = 0;
    while(!qq.empty()) {
    	state u = qq.front(); qq.pop();
    	int x = u.x, y = u.y, z = u.z;
    	ins(x, y, z); // 更新答案 
    	if(!vis[num[1]][y][z]) { // 1操作 
    		vis[num[1]][y][z] = 1;
    		dis[num[1]][y][z] = dis[x][y][z] + 1;
    		qq.push((state) {num[1], y, z});
		}
		if(!vis[x][num[2]][z]) {
			vis[x][num[2]][z] = 1;
			dis[x][num[2]][z] = dis[x][y][z] + 1;
			qq.push((state) {x, num[2], z});
		}
		if(!vis[x][y][num[3]]) {
			vis[x][y][num[3]] = 1;
			dis[x][y][num[3]] = dis[x][y][z] + 1;
			qq.push((state) {x, y, num[3]});
		}
		if(!vis[0][y][z]) { // 2操作 
			vis[0][y][z] = 1;
			dis[0][y][z] = dis[x][y][z] + 1;
			qq.push((state) {0, y, z});
		}
		if(!vis[x][0][z]) {
			vis[x][0][z] = 1;
			dis[x][0][z] = dis[x][y][z] + 1;
			qq.push((state) {x, 0, z});
		}
		if(!vis[x][y][0]) { 
			vis[x][y][0] = 1;
			dis[x][y][0] = dis[x][y][z] + 1;
			qq.push((state) {x, y, 0});
		}
		PII g; int p, q;
		g = pour(x, y, num[1], num[2]); // x -> y
		p = g.first, q = g.second;
		if(!vis[p][q][z]) {
			vis[p][q][z] = 1;
			dis[p][q][z] = dis[x][y][z] + 1;
			qq.push((state) {p, q, z});
		}
		g = pour(y, x, num[2], num[1]); // y -> x
		p = g.first, q = g.second;
		if(!vis[q][p][z]) {
			vis[q][p][z] = 1;
			dis[q][p][z] = dis[x][y][z] + 1;
			qq.push((state) {q, p, z});
		}
		g = pour(x, z, num[1], num[3]); // x -> z
		p = g.first, q = g.second;
		if(!vis[p][y][q]) {
			vis[p][y][q] = 1;
			dis[p][y][q] = dis[x][y][z] + 1;
			qq.push((state) {p, y, q});
		}
		g = pour(z, x, num[3], num[1]); // z -> x
		p = g.first, q = g.second;
		if(!vis[q][y][p]) {
			vis[q][y][p] = 1;
			dis[q][y][p] = dis[x][y][z] + 1;
			qq.push((state) {q, y, p});
		}
		g = pour(y, z, num[2], num[3]); // y -> z
		p = g.first, q = g.second;
		if(!vis[x][p][q]) {
			vis[x][p][q] = 1;
			dis[x][p][q] = dis[x][y][z] + 1;
			qq.push((state) {x, p, q});
		}
		g = pour(z, y, num[3], num[2]); // z -> y
		p = g.first, q = g.second;
		if(!vis[x][q][p]) {
			vis[x][q][p] = 1;
			dis[x][q][p] = dis[x][y][z] + 1;
			qq.push((state) {x, q, p});
		}		
	}
	memset(dp, 0x3f, sizeof dp);
	memset(f, 0x3f, sizeof f);
	dp[0] = 0;
	for(int i = 0; i <= n; i ++ ) {
		f[i] = min(f[i], dp[i]);
		for(int j = 1; j <= num[1] + num[2] + num[3]; j ++ ) {
			if(i + j <= n) dp[i + j] = min(dp[i + j], dp[i] + cost1[j]);
			if(i + j <= n) f[i + j] = min(f[i + j], dp[i] + cost2[j]);
		}
	}
	for(int i = 1; i <= n; i ++ ) {
		if(f[i] > 20000000) printf("-1 ");
		else printf("%d ", f[i]);
	}
	return 0;
}
/*
20 5 15 10 13 13 7 19 2 22 3 17 8 15 11 9 17 4 22 2 19 6 17 9 11 15 6 20 4 21 4 19 7 13 13 8 18 6 21 3 21 5 15 11 10 16 8 19 5 23 3 17 9 12 14 10 17 7 23 2

*/

B. 让他们连通(整体二分 + 按秩合并并查集 / kruskal重构树)

原题链接

在这里插入图片描述

分析:

S o l 1 : Sol1: Sol1
一个关键想法是我们可以 只关心一个点 i i i 和点 i − 1 i - 1 i1最早的连通时间 t i t_i ti即可

那么对于区间 [ l , r ] [l, r] [l,r] 而言,它最早的连通的时间就是 m a x i = l + 1 r t i max_{i = l + 1}^{r}t_i maxi=l+1rti

对于 t i t_i ti 的求法,我们可以整体二分 + 可撤销并查集 即可。我们使用 按秩合并 并查集就行。

时间复杂度 O ( q × l o g 2 q × l o g 2 n ) O(q \times log_2q \times log_2n) O(q×log2q×log2n)

S o l 2 : Sol2: Sol2

考虑维护两个点的最早连通时间,也可以使用 Kruskal重构树。然后查询只需要求 l c a lca lca 即可。

时间复杂度 O ( n × l o g 2 n ) O(n \times log_2n) O(n×log2n)

下面的代码是第一种解法的代码。
CODE:

#include<bits/stdc++.h> // 只需求出每个点和它前面的点最早的联通时间 
using namespace std;
const int N = 2e5 + 10;
int n, m, qc, u[N], v[N], o;
int g[N], lt[N], rt[N], Link[N];
int mx[N][21];
struct Q {
	int l, r;
}q[N];
struct inform { // 合并信息 
	int x, y, hx, hy; // x 合到 y 上,  x原来的树高是 hx, y原来的树高是 hy 
};
int bin[N], h[N]; // 父亲, 树高 
stack< inform > s;
int Find(int x) {
	return x == bin[x] ? x : Find(bin[x]);
}
void Merge(int x, int y) { // 合并 x, y 
	int f1 = Find(x), f2 = Find(y);
	if(f1 != f2) {
		if(h[f1] > h[f2]) swap(f1, f2);
		s.push((inform) {f1, f2, h[f1], h[f2]});
		o ++;
		bin[f1] = f2;
		if(h[f1] == h[f2]) h[f2] ++;
	}
}
void back(int cnt) { // 撤回 cnt 次 
	while(cnt --) {
		inform t = s.top(); s.pop();
		int x = t.x, y = t.y, hx = t.hx, hy = t.hy;
		bin[x] = x;
		h[x] = hx; h[y] = hy;
	}
}
void solve(int l, int r, int ql, int qr) {
	if(l == r) {
		Merge(u[l], v[l]); // 合并 
		for(int i = ql; i <= qr; i ++ ) Link[g[i]] = l;
		return ;
	}
	int mid = (l + r >> 1);
	o = 0;
	for(int i = l; i <= mid; i ++ ) {
		int U = u[i], V = v[i];
		Merge(U, V);
	}
	int lc = 0, rc = 0; 
    for(int i = ql; i <= qr; i ++ ) {
    	int p = g[i];
    	if(Find(p - 1) == Find(p)) lt[++ lc] = p;
		else rt[++ rc] = p;
	}
	for(int i = 1; i <= lc; i ++ ) g[i + ql - 1] = lt[i];
	for(int i = 1; i <= rc; i ++ ) g[lc + ql + i - 1] = rt[i];
	back(o); // 撤回操作 
	solve(l, mid, ql, ql + lc - 1); // 左 
	solve(mid + 1, r, ql + lc, qr);// 右 
}
int query(int l, int r) {
	int k = log2(r - l + 1);
	return max(mx[l][k], mx[r - (1 << k) + 1][k]);
}
void build_st() {
	for(int i = 1; i <= n; i ++ ) mx[i][0] = Link[i];
	for(int i = 1; (1 << i) <= n; i ++ ) {
		for(int j = 1; j + (1 << i) - 1 <= n; j ++ ) {
			mx[j][i] = max(mx[j][i - 1], mx[j + (1 << (i - 1))][i - 1]);
		}
	}
}
int main() {
	scanf("%d%d%d", &n, &m, &qc);
	for(int i = 1; i <= n; i ++ ) bin[i] = i, h[i] = 1;
	for(int i = 1; i <= m; i ++ ) {
		scanf("%d%d", &u[i], &v[i]);
	}
	for(int i = 1; i <= qc; i ++ ) {
		scanf("%d%d", &q[i].l, &q[i].r);
	}
	for(int i = 1; i <= n; i ++ ) g[i] = i;
	solve(1, m, 1, n);
    build_st();
    for(int i = 1; i <= qc; i ++ ) {
    	int ans;
    	if(q[i].l == q[i].r) ans = 0;
		else ans = query(q[i].l + 1, q[i].r);
    	printf("%d ", ans);
	}
	return 0;
}

C. 通信网络(线段树合并 + 二分)

在这里插入图片描述

分析:

题意:
给你一个 n n n 个点的树,点有点权 w i w_i wi。若现在有一个数 P P P,那么对于一个点 x x x,它的负荷就是所有满足一下两个条件的简单路径条数:

  1. u , v u,v uv 是路径的两个端点,并且满足 w u ≤ w x + P w_u \leq w_x + P wuwx+P w v ≤ w x + P w_v \leq w_x + P wvwx+P
  2. x x x 在路径 u → v u \to v uv 上。

现在想让所有点的负荷都小于 K K K,求最大的 P P P


不难看出, P P P 越大,每个点的负荷只会增大,不会减小。具有单调性,因此可以 二分答案。 我们考虑如何检验:

若给定一个 P P P,那么每个点的负荷计算就很简单了。对于一个点 x x x,只需要能快速查询它的子树里小于等于 w x + P w_x + P wx+P 的数的个数即可。那么可以做 线段树合并,并在 值域线段树 里二分。这样可以计算出端点都在 x x x 子树里的路径个数。对于一个端点在子树里,一个端点在子树外的路径,显然可以拿所有小于等于 w x + P w_x + P wx+P 的数的个数减去 x x x 的子树里的个数,这样能算出来 x x x 子树外能成为端点的点数,然后和子树里的相乘即可。这样时间复杂度就是 O ( n × l o g 2 2 w ) O(n \times log^2_2w) O(n×log22w) 的。

我们发现这样过不去,还要卡一下常:
由于 K K K 给定,因此可以对每个点求出满足条件的最大的 P P P,然后最终答案就是所有点的答案取 m i n min min。对每个点求这样的 P P P 可以二分,这样时间复杂度也是 O ( n × l o g 2 2 w ) O(n \times log^2_2w) O(n×log22w) 的。但是注意到每个点二分时右端点不必开满,可以令当前求出的答案的最小值作为端点。这样也是一个剪枝。这样做之后就跑的特别快了,最后没有被卡掉。

CODE:

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 3e5 + 10;
const int M = 3e6 + 10;
typedef long long LL;
int n, w[N], u, v;
int pre[M], tot, rt[N];
int ans[N], res = 1e8, now;
vector< int > E[N];
LL k;
struct SegmentTree {
	int ls, rs, cnt;
	#define ls(x) t[x].ls
	#define rs(x) t[x].rs
	#define cnt(x) t[x].cnt
}t[N * 25];
void update(int p) {
	cnt(p) = cnt(ls(p)) + cnt(rs(p));
}
void ins(int p, int lp, int rp, int pos) {
	if(lp == rp) {
		cnt(p) ++;
		return ;
	}
	int mid = (lp + rp >> 1);
	if(pos <= mid) {
		if(!ls(p)) ls(p) = ++ tot;
		ins(ls(p), lp, mid, pos);
	}
	else {
		if(!rs(p)) rs(p) = ++ tot;
		ins(rs(p), mid + 1, rp, pos);
	}
	update(p);
}
int query(int p, int lp, int rp, int pos) {
	if(lp == rp) return (lp == pos ? cnt(p) : 0);
	int mid = (lp + rp >> 1);
	if(pos <= mid) return query(ls(p), lp, mid, pos);
	else return cnt(ls(p)) + query(rs(p), mid + 1, rp, pos);
}
int Merge(int p, int q, int lp, int rp) {
	if(!p || !q) return (p ^ q);
	if(lp == rp) {
		cnt(p) += cnt(q);
		return p;
	}
	int mid = (lp + rp >> 1);
	ls(p) = Merge(ls(p), ls(q), lp, mid);
	rs(p) = Merge(rs(p), rs(q), mid + 1, rp);
	update(p);
	return p;
}
bool check(int x, int fa, int p) {
	int o = (p + w[x] >= w[x] ? 1 : 0); LL res = 0;
	for(auto v : E[x]) {
		if(v == fa) continue;
		int c = query(rt[v], 1, 2e6, p + w[x]);
		res += 1LL * o * c;
		o += c; 
	}
	res += 1LL * o * ((p + w[x] >= 0 ? pre[w[x] + p] : 0) - o);
	return res < k;
}
void dfs(int x, int fa) { // x 作为中转站的答案 
	for(auto v : E[x]) {
		if(v == fa) continue;
		dfs(v, x);
	}
    int l = -1e6, r = now, mid, res = -1;
    while(l <= r) {
    	mid = (l + r >> 1);
    	if(check(x, fa, mid)) res = mid, l = mid + 1;
    	else r = mid - 1;
	}
	ans[x] = res; now = res;
	for(auto v : E[x]) {
		if(v == fa) continue;
		rt[x] = Merge(rt[x], rt[v], 1, 2e6);
	}
}
void solve() { // 检验 p 是否合法   弄完再清空 
	for(int i = 1; i <= n; i ++ ) {
		rt[i] = ++ tot;
		ins(rt[i], 1, 2e6, w[i]);
	}
	dfs(1, 0);
    for(int i = 1; i <= n; i ++ ) {
    	res = min(res, ans[i]);
	}
}
int main() {
	scanf("%d%lld", &n, &k);
	int maxw = 0;
	for(int i = 1; i <= n; i ++ ) {
		scanf("%d", &w[i]);
		pre[w[i]] ++;
		maxw = max(maxw, w[i]);
	}
	for(int i = 1; i < n; i ++ ) {
		scanf("%d%d", &u, &v);
		E[u].pb(v); E[v].pb(u);
	}
	for(int i = 1; i < M; i ++ ) pre[i] += pre[i - 1];
    now = 1e6; 
	solve();
	printf("%d\n", res);
	return 0;
}

正解是单 l o g log log 的。首先对于它没有使用线段树合并,而是 按照 d f s dfs dfs 序由小到大建主席树。那么一个点的子树信息也可以在 d f s dfs dfs 序列中的一段区间中查询。正解也是按照上面这种对每个点求最大的 P P P,然后所有点取 m i n min min 得到答案。考虑如果二分答案然后用主席树检验,时间复杂度也是 O ( n × l o g 2 2 w ) O(n \times log^2_2w) O(n×log22w) 的。然后它就是把所有区间都拿出来,一起在线段树上二分减去一个 l o g log log。具体写法没仔细看,但是大概思路就是这。

D. 3SUM(扫描线 + 线段树)

原题链接
在这里插入图片描述

分析:

题意说的很清楚了,不在赘述。

直接说正解吧:

我们对最大值在那一段进行分讨,然后答案取最优:

  1. 最大值在中间一段。那么可以把中间一段往两边延伸,发现中间那段的答案不变,两边段的答案单调不增。因此这种情况下最优值就是 [ l , l ] [l, l] [l,l] [ l + 1 , r − 1 ] [l + 1, r - 1] [l+1,r1] [ r , r ] [r, r] [r,r] 的最大值之和。
  2. 最大值在左边段。如果我们求出 [ l , r ] [l, r] [l,r] 中最大值的最左边位置 x x x,那么第一段的右端点应该大于等于 x x x。我们考虑如果左段的右端点是 r t rt rt,那么要把 [ r t + 1 , r ] [rt + 1, r] [rt+1,r] 分成两端。如果右段的左端点为 l t lt lt,那么三段就是 [ l , r t ] [l, rt] [l,rt] [ r t + 1 , l t − 1 ] [rt + 1, lt - 1] [rt+1,lt1] [ l t , r ] [lt, r] [lt,r]。不难发现,这时可以将左段向右延伸直到中间段的长度为 1 1 1。这样左段,右段的答案不变,中间段的答案只会变得更优。因此我们得出结论:对于最大值在左段的情况,中间段的长度一定为 1 1 1。因此如果中间段为 [ p , p ] [p, p] [p,p],那么中间段和右段的答案就是 h p = a p + m a x i = p + 1 r a i h_p =a_p + max_{i = p + 1}^{r}a_i hp=ap+maxi=p+1rai,我们需要维护 区间中 h h h 的最小值。这个可以用 线段树 + 单调栈维护 维护。具体来讲,我们对 r r r 扫描线,然后维护一个单调栈。如果 a r > a s t o p a_r > a_{s_{top}} ar>astop,就将栈顶弹出。那么 [ s t o p − 1 , s t o p ) [s_{top - 1},s_{top}) [stop1,stop) 区间的 h h h 值将会加上 a r − a s t o p a_r - a_{s_{top}} arastop 。特别的,由于每个数刚入栈时后边没有数,因此它的 h h h 值是正无穷。那么每次对于第一个栈顶我们将它的 h h h 值改为 a s t o p + a r a_{s_{top}} + a_r astop+ar 即可。然后对于所有右端点为当前 r r r 的区间 [ l , r ] [l, r] [l,r],我们在栈中二分出第一个大于 l l l 的位置 p o s pos pos,这是 [ l , r ] [l, r] [l,r] 区间里最靠左的最大值的位置。在线段树上查询 [ p o s + 1 , r ] [pos + 1, r] [pos+1,r] 的最小值并加上 a p o s a_{pos} apos更新这个询问的答案即可。
  3. 最大值在右边段。将序列和询问区间反转一下就能变成上一种情况。

时间复杂度 O ( n × l o g 2 n ) O(n \times log_2n) O(n×log2n)

CODE:

#include<bits/stdc++.h> // 扫描线 + 线段树 + 单调栈 
#define pb push_back
#define MP make_pair 
using namespace std;
typedef pair< int, int > PII;
const int N = 3e5 + 10;
const int INF = 4e8;
int n, qc, a[N], ans[N], mx[N][21];
vector< PII > vec[N];
struct Q {
	int l, r, id;
}q[N];
void build_st() {
	for(int i = 1; i <= n; i ++ ) mx[i][0] = a[i];
	for(int i = 1; (1 << i) <= n; i ++ ) 
		for(int j = 1; j + (1 << i) - 1 <= n; j ++ ) 
			mx[j][i] = max(mx[j][i - 1], mx[j + (1 << (i - 1))][i - 1]);
}
int query(int l, int r) {
	int k = log2(r - l + 1);
	return max(mx[l][k], mx[r - (1 << k) + 1][k]);
}
struct SegmentTree {
	int l, r, mn, tag;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define mn(x) t[x].mn
	#define tag(x) t[x].tag
}t[N * 4];
void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	mn(p) = INF; tag(p) = 0;
	if(l == r) return ;
	int mid = (l + r >> 1);
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
}
void update(int p) {
	mn(p) = min(mn(p << 1), mn(p << 1 | 1));
}
void spread(int p) {
	if(tag(p)) {
		tag(p << 1) += tag(p); tag(p << 1 | 1) += tag(p);
		mn(p << 1) += tag(p); mn(p << 1 | 1) += tag(p); 
		tag(p) = 0;
	}
}
void change(int p, int l, int r, int c) {
	if(l <= l(p) && r >= r(p)) {
		mn(p) += c; tag(p) += c;
		return ;
	}
	spread(p);
	int mid = (l(p) + r(p) >> 1);
	if(l <= mid) change(p << 1, l, r, c);
	if(r > mid) change(p << 1 | 1, l, r, c);
	update(p);
}
void ins(int p, int pos, int c) {
	if(l(p) == r(p)) {
		mn(p) = c;
		return ;
	}
	int mid = (l(p) + r(p) >> 1);
	if(pos <= mid) ins(p << 1, pos, c);
	else ins(p << 1 | 1, pos, c);
	update(p);
}
int ask(int p, int l, int r) {
	if(l <= l(p) && r >= r(p)) return mn(p);
	spread(p);
	int mid = (l(p) + r(p) >> 1);
	if(r <= mid) return ask(p << 1, l, r);
	else if(l > mid) return ask(p << 1 | 1, l, r);
	else return min(ask(p << 1, l, r), ask(p << 1 | 1, l, r));
}
void solve() { // 对 r 扫描线 
    int stk[N], Top = 0; // 单调栈 
	for(int r = 1; r <= n; r ++ ) {
		if(r != 1) ins(1, r - 1, a[r - 1] + a[r]);// 先把 r - 1修改了 
		while(Top > 0 && a[r] > a[stk[Top]]) {
			int x = stk[Top]; Top --;
			int l = (Top == 0 ? 1 : stk[Top]);
			if(x - 1 >= l) change(1, l, x - 1, a[r] - a[x]);
		}
		stk[++ Top] = r;
		for(auto qy : vec[r]) {
			int l = qy.first, idx = qy.second;
			int p = lower_bound(stk + 1, stk + Top + 1, l) - (stk); // 第一个大于等于l的位置 
			int x = stk[p];
			if(x + 1 <= r) ans[idx] = min(ans[idx], a[x] + ask(1, x + 1, r));
		} 
	}
}
int main() {
	scanf("%d%d", &n, &qc);
	for(int i = 1; i <= n; i ++ ) {
		scanf("%d", &a[i]);
	}
	for(int i = 1; i <= qc; i ++ ) {
		scanf("%d%d", &q[i].l, &q[i].r);
		q[i].id= i;
	}
	build_st();
	for(int i = 1; i <= qc; i ++ ) 
		ans[i] = a[q[i].l] + a[q[i].r] + query(q[i].l + 1, q[i].r - 1);
	for(int i = 1; i <= qc; i ++ ) {
		vec[q[i].r].pb(MP(q[i].l, q[i].id));
	}
	build(1, 1, n);
	solve();
	reverse(a + 1, a + n + 1);
	for(int i = 1; i <= n; i ++ ) {
		vector< PII > tmp;
		swap(tmp, vec[i]);
	}
	for(int i = 1; i <= qc; i ++ ) {
		vec[n - q[i].l + 1].pb(MP(n - q[i].r + 1, q[i].id));
	}
	build(1, 1, n);
	solve();
	for(int i = 1; i <= qc; i ++ ) printf("%d\n", ans[i]);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1961121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6万字,让你轻松上手的大模型 LangChain 框架

本文为我学习 LangChain 时对官方文档以及一系列资料进行一些总结&#xff5e;覆盖对Langchain的核心六大模块的理解与核心使用方法&#xff0c;全文篇幅较长&#xff0c;共计50000字&#xff0c;可先码住辅助用于学习Langchain。** 一、Langchain是什么&#xff1f; 如今各类…

FPGA实现LCD12864控制

目录 注意&#xff01; a) 本工程采用野火征途PRO开发板&#xff0c;外接LCD12864部件进行测试。 b) 有偿提供代码&#xff01;&#xff01;&#xff01;可以定制功能&#xff01;&#xff01;&#xff01;有需要私信&#xff01;&#xff01;&#xff01; c) 本文测试采用…

操作系统02

文章目录 Linux 内核 vs Windows 内核内核Linux 的设计MultiTaskSMPELFMonolithic Kernel **Windows 设计** 内存管理虚拟内存内存分段内存分页多级页表TLB 段页式内存管理Linux 内存布局内存分配的过程是怎样的&#xff1f;哪些内存可以被回收&#xff1f;回收内存带来的性能影…

中国RoHS新增4项邻苯二甲酸酯管控,电子电气产品GB/T 26572-2011测试

中国RoHS 新增4项邻苯类物质 01 资讯内容 2024年6月29日&#xff0c;国家市场监督管理总局&#xff08;国家标准化管理委员会&#xff09;发布了2024年第14号中国国家标准公告&#xff0c;批准了109项国家标准和4项国家标准修改单。 其中&#xff0c;中国RoHS配套的标准GB/T 2…

人工智能和机器学习2 (复旦大学计算机科学与技术实践工作站)python调用百度AI、获取token,并利用opencv绘制分析图,做简单判断

前言 在现代科技的推动下&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;逐渐成为各行各业的重要工具。百度AI开放平台作为全球领先的人工智能服务平台&#xff0c;为开发者提供了包括语音、图像、自然语言处理&#xff08;NLP&#xff…

Python常用内置库介绍

Python作为一门强大且易学的编程语言&#xff0c;内置了许多功能强大的库&#xff0c;让开发者能够更加便捷地完成各种任务。本文中&#xff0c;我将详细介绍Python中常用的内置库。 math&#xff1a;提供数学函数&#xff0c;如三角函数、对数函数等。 示例&#xff1a;计算平…

Pycharm conda 虚拟环境添加失败---windows

版本&#xff1a; conda&#xff1a;23.5.2 pycharm:2023.1.3 解决方案&#xff1a; 已验证&#xff1a; 使用系统解释器选择python.exe进行本地添加&#xff08;ps:该方式不会显示conda名称&#xff09; conda路径使用conda info查询 还有一个是在查找解决方法的时候看到比…

05-ArcGIS For JavaScript-RenderNode后处理效果

05-ArcGIS For JavaScript-RenderNode后处理效果 综述代码解析代码实现颜色混合完整代码结果高亮处理完整代码结果 结语 综述 ArcGIS For JavaScript 4.9版本提供了很多优秀的功能&#xff0c;其中提供了RenderNode类&#xff0c;既可以支持第三方渲染引擎的植入&#xff0c;例…

PowerShell报错 about_Execution_Policies 解决方法

在用express创建项目中显示项目创建失败&#xff0c;报错如图所示&#xff0c;显示无法加载文件&#xff0c;按照提示地址https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies看到页面显示是PowerShell执行策略的问题。有问题评论区留言&#xff0c;…

前端构建工具Vite

前端主流框架Vue大家应该都耳熟能详&#xff0c;很多的公司和项目都在使用&#xff0c;以前前端构建工具用的比较多的是webpack&#xff0c;后面渐渐地出现了Vite&#xff0c;它受到大家的喜爱和使用&#xff0c;那大家是否也很想知道它的由来以及优势&#xff0c;为什么越来越…

用户提交订单业务

文章目录 概要整体架构流程技术细节小结 概要 我们通常指的是在电子商务或在线零售环境中&#xff0c;顾客通过互联网完成商品或服务购买的过程。随着互联网技术的发展和普及&#xff0c;越来越多的消费者选择在线购物&#xff0c;这不仅因为其便捷性&#xff0c;还因为它提供…

Linux网络-小结

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

MySQL基础练习题14-产品销售分析1

题目&#xff1a;获取 Sales 表中所有 sale_id 对应的 product_name 以及该产品的所有 year 和 price 。 准备数据 分析数据 题目&#xff1a;获取 Sales 表中所有 sale_id 对应的 product_name 以及该产品的所有 year 和 price 。 准备数据 ## 创建库 create database db;…

实战:深入了解JDBC和分享JDBCUtil

Java 数据库连接 (JDBC) 是一个功能强大的 API&#xff0c;它弥补了 Java 应用程序与关系数据库之间的差距。通过利用 JDBC&#xff0c;您可以无缝地与数据库交互以存储、检索和操作数据。但是&#xff0c;要有效使用 JDBC&#xff0c;需要遵循最佳实践&#xff0c;以确保代码的…

GitHub Revert Merge Commit的现象观察和对PR的思考

文章目录 前言Pull Request 为什么会是这样&#xff1f;Pull Request Branch的差异 ?Two Dot Diff和Three Dot Diff 老生常谈&#xff1a; Merge 和 Rebasegit mergegit rebase Revert Main分支中的一个Merge Commit现象描述解决方案: Revert Feature分支中的一个Merge Commi…

RocketMQ入门到精通

RocketMQ入门到精通 一、介绍1.对比2.基础概念 二、环境搭建1.下载rocket2.新增系统变量&#xff1a;ROCKETMQ_HOME3.启动命名服务 nameserver4.启动broker服务器5.安装可视面板6.手动创建Topic7.手动创建消费者组 三、使用Springboot实现消息的收发1.引入jar包2.配置yml文件3.…

【Python机器学习】朴素贝叶斯——使用朴素贝叶斯过滤垃圾邮件

使用朴素贝叶斯解决一些现实生活中的问题时&#xff0c;需要先从文本内容中得到字符串列表&#xff0c;然后生成词向量。 使用朴素贝叶斯对电子邮件进行分类的过程&#xff1a; 1、收集数据&#xff1a;提供文本文件 2、准备数据&#xff1a;将文本文件解析成词条向量 3、分析…

推荐5款好用的将pdf翻译成中文的工具。

像word&#xff0c;PPT,Excel等这些文档如果要翻译的话&#xff0c;即使没有合适的工具也可以复制粘贴内容。可PDF有的时候是不可以编辑的&#xff0c;很难用这种方法实现翻译。但是这5款翻译工具就可以做到直接将PDF文件进行翻译。 1、365pdf在线翻译 直达&#xff1a;https:…

力扣Hot100-543二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5] 输出&a…