最近等对
(线段树+离线+双指针)
题意:
有一个序列
a
1
a1
a1 ,
a
2
a2
a2 ,
a
3
a3
a3 ,…,
a
n
an
an , 还有
m
m
m 次查询
l
,
r
l , r
l,r,对于每一个查询,找出距离最近的
x
x
x 和
y
y
y,且满足
a
[
x
]
=
=
a
[
y
]
a[x]==a[y]
a[x]==a[y] , 两个数字的距离是他们下标之差的绝对值。
这里的序列的长度是5e5的,里面的查询数量也是到5e5量级的,那显然复杂度最高就到 n l o g n n logn nlogn的量级,而每次查询区间的时候最多只能有一个log的操作,那么如果不对给定的区间进行预处理的话,确实没有办法来实现刚好完全在这个区间的最小值。想到离线操作,离线完了之后就要一个挑让我们最舒服的对查询区间的 l , r l , r l,r 进行排序。然后这里就要把最小值存起来,因为每个值必定是和自己左边和右边的相同的值是最近的,那就统一在相同的数的左边里记他们之间的距离,这样把查询区间按右端点从小到大进行排序。同时开双指针进行查找就可以解决了,里面的查询区间最小值就可以用线段树来完成
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
int n, m;
const int maxn = 5e5 + 100;
int a[maxn], b[maxn];
int last[maxn], lef[maxn];
int ans[maxn];
struct node {
int id, l, r;
bool operator<(const node& rhs) const {
return r < rhs.r;
}
}q[maxn];
int read() {
int x = 0, f = 1;
char c = getchar();
while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0'&&c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int INF = 0x3f3f3f3f;
int tree[maxn << 2];
void build(int p, int l, int r) {
if (l == r) tree[p] = INF;
else {
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}
}
int query_min(int p, int l, int r, int x, int y) {
if (l >= x && r <= y) return tree[p];
int mid = (l + r) >> 1;
int minn = INF;
if (x <= mid) minn = min(minn, query_min(p * 2, l, mid, x, y));
if (y > mid) minn = min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
return minn;
}
void update(int p, int l, int r, int x, int k) {
if (l == r) {
tree[p] = k;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) update(p << 1, l, mid, x, k);
else update(p << 1 | 1, mid + 1, r, x, k);
tree[p] = min(tree[p << 1] , tree[p << 1 | 1]);
}
void solve() {
sort(b + 1, b + 1 + n);
int cnt1 = unique(b + 1, b + 1 + n) - b - 1;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + 1 + cnt1, a[i]) - b;
lef[i] = last[a[i]];
last[a[i]] = i;
}
int pos = 1; int cnt = 1;
build(1, 1, n);
while (pos <= n && cnt <= q[m].r) {
if (lef[cnt]) {
//cout << " " << lef[cnt] << " " << cnt - lef[cnt] << endl;
update(1, 1, n, lef[cnt], cnt - lef[cnt]);
}
while (q[pos].r == cnt) {
//cout << " " << "** " <<cnt<<" "<<pos<<" "<< q[pos].l << " " << q[pos].r << endl;
int ans1 = query_min(1, 1, n, q[pos].l, q[pos].r);
if (ans1 == INF) ans[q[pos].id] = -1;
else ans[q[pos].id] = ans1;
pos++;
}
cnt++;
}
for (int i = 1; i <= m; i++) {
write(ans[i]);
printf("\n");
}
}
signed main() {
ios::sync_with_stdio; cin.tie(0); cout.tie(0);
n = read(); m = read();
for (int i = 1; i <= n; i++) a[i]=read(), b[i] = a[i];
for (int i = 1; i <= m; i++) q[i].l=read(),q[i].r=read(), q[i].id = i;
sort(q + 1, q + 1 + m);
solve();
return 0;
}
公园晨跑
线段树+思维
题意:有
n
n
n颗树,第
i
i
i棵树和第
i
+
1
i+1
i+1之间的距离是
d
[
i
]
d [ i ]
d[i] ,而第
n
n
n 棵树和第一棵树的距离是
d
[
n
]
d [ n ]
d[n] 。第
i
i
i棵的高度是
d
[
i
]
d [ i ]
d[i]。
猴子每天要选择两棵树,然后再第一棵树下来,接着围绕公园跑,但是会有小孩在
a
[
i
]
a [ i ]
a[i]到
b
[
i
]
b [ i ]
b[i], 在这里的范围里面猴子是不能经过的,需要帮猴子找到两棵树,使他晨跑时能够消耗最大的能量。
首先把环先拆成2*n的线性图,已知要求的是
2
h
x
+
2
h
y
+
s
u
m
y
−
s
u
m
x
(
x
<
y
)
2hx+2hy+sumy-sumx (x<y)
2hx+2hy+sumy−sumx(x<y)
s
u
m
x
sumx
sumx代表x到0前缀和,相减的话就是y到x的距离,而要求这段距离最大,这里的未知量有两个,有x和y,所以不能这样去求最大值,把这段距离分成求
2
h
y
+
s
u
m
y
2hy+sumy
2hy+sumy的最大值和求
s
u
m
x
−
2
h
y
sumx-2hy
sumx−2hy的最小值。给定范围,然后用线段树区间查询最大值和最小值,这里还要考虑一个问题,那就是x==y的情况,这样的话,把树的节点值打真正的值的下标,这样的话就能快速比较出是否是相同的值,然后缩小区间进行求值,求出答案。
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define int long long
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
const int maxn = 2e5 + 10;
int n, m;
int d[maxn], h[maxn];
int a[maxn], b[maxn];
void init() {
int sum = 0;
a[0] = -1e9; b[0] = -1e9;
for (int i = 1; i <= n; i++) {
sum += d[i - 1];
a[i] = sum + 2 * h[i];
b[i] = sum - 2 * h[i];
//cout << i << " " << a[i] << " " << b[i] << endl;
}
}
const int INF = 0x3f3f3f3f;
int treemin[maxn << 2];
int treemax[maxn << 2];
int Max(int a1, int b1) { return a[a1] > a[b1] ? a1 : b1; }
int Min(int a1, int b1) { return b[a1] < b[b1] ? a1 : b1; }
void build(int p, int l, int r) {
if (l == r) treemin[p] = l, treemax[p] = l;
else {
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
treemin[p] = Min(treemin[p << 1], treemin[p << 1 | 1]);
treemax[p] = Max(treemax[p << 1], treemax[p << 1 | 1]);
}
}
int query_min(int p, int l, int r, int x, int y) {
if (l >= x && r <= y) return treemin[p];
int mid = (l + r) >> 1;
int minn = x;
if (x <= mid) minn = Min(minn, query_min(p * 2, l, mid, x, y));
if (y > mid) minn = Min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
return minn;
}
int query_max(int p, int l, int r, int x, int y) {
if (l >= x && r <= y) return treemax[p];
int mid = (l + r) >> 1;
int maxx = x;
//cout << "maxx = " << maxx << endl;
if (x <= mid) maxx = Max(maxx, query_max(p * 2, l, mid, x, y));
if (y > mid) maxx = Max(maxx, query_max(p * 2 | 1, mid + 1, r, x, y));
return maxx;
}
int solve(int l, int r) {
//cout << "***" << l << " " << r << endl;
int x = query_max(1, 1, n, l, r);
int y = query_min(1, 1, n, l, r);
// cout << x << " " << y << endl;
if (x != y) return a[x] - b[y];
int another_x, another_y;
if (l > x - 1 && r < x + 1) another_x = x;
else if (l > x - 1) another_x = query_max(1, 1, n, x + 1, r);
else if (r < x + 1) another_x = query_max(1, 1, n, l, x - 1);
else another_x = Max(query_max(1, 1, n, l, x - 1), query_max(1, 1, n, x + 1, r));
if (l > y - 1 && r < y + 1) another_y = y;
else if (l > y - 1) another_y = query_min(1, 1, n, y + 1, r);
else if (r < y + 1) another_y = query_min(1, 1, n, l, y - 1);
else another_y = Min(query_min(1, 1, n, l, y - 1), query_min(1, 1, n, y + 1, r));
//cout <<"*****" << another_x << " " << another_y << endl;
return max(a[another_x] - b[y], a[x] - b[another_y]);
}
signed main() {
n = read(); m = read();
n = n * 2;
for (int i = 1; i <= n / 2; i++) d[i] = read(), d[i + n / 2] = d[i];
for (int i = 1; i <= n / 2; i++) h[i] = read(), h[i + n / 2] = h[i];
init();
build(1, 1, n);
while (m--) {
int aa = read(), bb = read();
if (aa <= bb) cout << solve(bb + 1, n / 2 + aa - 1) << endl;
else cout << solve(bb + 1, aa - 1) << endl;
}
return 0;
}
Pinball
线段树+dp
题意:Pinball的游戏界面由m+2行、n列组成。第一行在顶端。一个球会从第一行的某一列出发,开始垂直下落,界面上有一些漏斗,一共有m个漏斗分别放在第2m+1行,第i个漏斗的作用是把经过第i+1行且列数在Ai~Bi之间的球,将其移到下一行的第Ci列。 使用第i个漏斗需要支付Di的价钱,你需要保留一些漏斗使得球无论从第一行的哪一列开始放,都只可能到达第m+2行的唯一 一列,求花费的最少代价。
n的范围到达1e9,那就要考虑离散化,保留需要的点,m的范围到1e5,且要求花费最小,考虑dp去写,以
d
p
1
[
i
]
dp1[ i ]
dp1[i]来表示以
i
i
i块为从1到
d
p
1
[
i
]
dp1[ i ]
dp1[i]的最小花费,同理以
d
p
2
[
i
]
dp2[ i ]
dp2[i]来表示以
i
i
i块为从n到
d
p
2
[
i
]
dp2[ i ]
dp2[i]的最小花费,但是维护
d
p
1
[
i
]
dp1[ i ]
dp1[i]就需要从第
i
i
i块的
l
到
r
l 到 r
l到r开始寻找最小值,这样的话,直接用线段树进行维护,不断单点更新第
i
i
i 的
m
m
m 点,然后区间查询最小值,进行dp;最后答案为
d
p
1
[
i
]
+
d
p
2
[
i
]
−
a
[
i
]
.
v
a
l
dp1[ i ]+dp2[ i ]-a[ i ].val
dp1[i]+dp2[i]−a[i].val取最小的即可
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 3e5 + 100;
struct node {
int l, r, m, val;
}a[maxn];
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int b[maxn];
const int INF = 1e18;
int tree[maxn << 2];
void build(int p, int l, int r) {
if (l == r) tree[p] = INF;
else {
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}
}
void update(int p, int l, int r, int x, int k) {
if (l == r) {
tree[p] = min(k, tree[p]);
return;
}
int mid = (l + r) >> 1;
if (x <= mid) update(p << 1, l, mid, x, k);
else update(p << 1 | 1, mid + 1, r, x, k);
tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}
int query_min(int p, int l, int r, int x, int y) {
if (l >= x && r <= y) return tree[p];
int mid = (l + r) >> 1;
int minn = INF;
if (x <= mid) minn = min(minn, query_min(p * 2, l, mid, x, y));
if (y > mid) minn = min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
return minn;
}
int dpl[maxn], dpr[maxn];
signed main() {
int m, n;
m = read(); n = read();
int cnt = 0;
for (int i = 1; i <= m; i++) {
a[i].l = read(); a[i].r = read(); a[i].m = read(); a[i].val = read();
b[++cnt] = a[i].l; b[++cnt] = a[i].r; b[++cnt] = a[i].m;
}
b[++cnt] = 1;
b[++cnt] = n;
sort(b + 1, b + 1 + cnt);
int cnt1 = unique(b + 1, b + 1 + cnt) - b - 1;
for (int i = 1; i <= m; i++) {
a[i].l = lower_bound(b + 1, b + 1 + cnt1, a[i].l) - b;
a[i].r = lower_bound(b + 1, b + 1 + cnt1, a[i].r) - b;
a[i].m = lower_bound(b + 1, b + 1 + cnt1, a[i].m) - b;
}
//从第1列丢出的和从第m列丢出的最后在同一个漏斗里面即可
//
//cout << cnt1 << endl;
build(1, 1, cnt1);
update(1, 1, cnt1, 1, 0);
for (int i = 1; i <= m; i++) {
int minn = query_min(1, 1, cnt1, a[i].l, a[i].r);
if (minn < INF) update(1, 1, cnt1, a[i].m, minn + a[i].val);
dpl[i] = minn + a[i].val;
}
build(1, 1, cnt1);
update(1, 1, cnt1, cnt1, 0);
//update(1, 1, n, m, 0);
for (int i = 1; i <= m; i++) {
int minn = query_min(1, 1, cnt1, a[i].l, a[i].r);
if (minn < INF) update(1, 1, cnt1, a[i].m, minn + a[i].val);
dpr[i] = minn + a[i].val;
}
int ans = INF;
for (int i = 1; i <= m; i++) {
// cout << dpl[i] << " " << dpr[i] << endl;
ans = min(dpl[i] + dpr[i] - a[i].val, ans);
}
if (ans < INF/2) printf("%lld\n", ans);
else printf("-1\n");
return 0;
}
/*
10 1000
230 675 581 645789359
92 380 153 694212514
496 696 685 668220566
676 1000 907 347463815
477 667 491 576572818
8 910 698 896379238
355 618 380 374691592
450 800 700 588527055
408 557 488 805749148
2 715 128 830530803
430
*/