题目链接:here
原题目描述在最下面
先更新E题
CF 1136E 线段树
题意:
给你两个序列
a
1
,
a
2
.
.
.
a
n
a_1,a_2...a_n
a1,a2...an和
k
1
,
k
2
.
.
.
k
n
−
1
k_1,k_2...k_{n-1}
k1,k2...kn−1。输入保证
a
i
+
k
i
≤
a
i
+
1
a_i+k_i\le a_{i+1}
ai+ki≤ai+1。
两种操作:区间求
∑
i
=
l
r
a
i
\sum_{i=l}^ra_i
∑i=lrai和给
a
p
a_p
ap单点加
x
x
x。
第二个操作有连锁反映,若
a
p
+
k
p
>
a
p
+
1
a_p+k_p\gt a_{p+1}
ap+kp>ap+1,则
a
p
+
1
a_{p+1}
ap+1更新为
a
p
+
k
p
a_p+k_p
ap+kp,同理更新
a
p
+
2
a_{p+2}
ap+2直到不能更新为止。
思路:
在纸上画画,你会发现:
a
1
≤
a
2
−
k
1
≤
a
3
−
k
2
−
k
1
.
.
.
≤
a
n
−
∑
i
=
1
n
−
1
k
i
a_1\le a_2-k_1\le a_3-k_2-k_1...\le a_n-\sum_{i=1}^{n-1}k_i
a1≤a2−k1≤a3−k2−k1...≤an−∑i=1n−1ki。
给
p
p
p位置加上值
x
x
x之后,
a
p
−
∑
i
=
1
p
−
1
k
i
a_p-\sum_{i=1}^{p-1}k_i
ap−∑i=1p−1ki也增加了
x
x
x。更新之前已知:
a
p
−
∑
i
=
1
p
−
1
k
i
≤
a
p
+
1
−
∑
i
=
1
p
k
i
a_p-\sum_{i=1}^{p-1}k_i\le a_{p+1}-\sum_{i=1}^pk_i
ap−∑i=1p−1ki≤ap+1−∑i=1pki,如果
a
p
+
k
p
+
x
>
a
p
+
1
a_p+k_p+x\gt a_{p+1}
ap+kp+x>ap+1,那么
a
p
+
1
=
a
p
+
k
p
+
x
a_{p+1}=a_p+k_p+x
ap+1=ap+kp+x,并且
a
p
+
1
−
∑
i
=
1
p
k
i
=
a
p
−
∑
i
=
1
p
−
1
k
i
+
x
a_{p+1}-\sum_{i=1}^pk_i=a_p-\sum_{i=1}^{p-1}k_i+x
ap+1−∑i=1pki=ap−∑i=1p−1ki+x。
a
p
+
2
a_{p+2}
ap+2同理更新。
你发现了什么?
我们令 b x = a x − ∑ i = 1 x − 1 k i b_x=a_x-\sum_{i=1}^{x-1}k_i bx=ax−∑i=1x−1ki,我们给 p p p单点加上 x x x之后的效果是:把 i ∈ [ p + 1 , n ] i\in[p+1,n] i∈[p+1,n]间所有小于 b p + x b_p+x bp+x的 b i b_i bi全部赋值为 b p + x b_p+x bp+x。
到这里思路就很明显了。我们用线段树维护 b x b_x bx即可,求和就是 ∑ i = l r b i + ∑ i = l r ∑ j = 1 i − 1 k j \sum_{i=l}^rb_i+\sum_{i=l}^r\sum_{j=1}^{i-1}k_j ∑i=lrbi+∑i=lr∑j=1i−1kj;更新就是区间赋值操作,因为 b x b_x bx满足单调不递减,所以你可以二分出右端点来。
本题结束。
AC_code
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const LL lt = -1e18;
const int INF = 0x3f3f3f3f;
const int MXN = 1e5 + 7;
int n, m;
LL ar[MXN], kr[MXN], kk[MXN];
LL sum[MXN<<2], flag[MXN<<2], Max[MXN<<2];
void build(int l, int r, int rt) {
flag[rt] = lt;
if(l == r) {
sum[rt] = ar[l] - kr[l-1];
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt<<1); build(mid+1, r, rt<<1|1);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void push_down(int l, int mid, int r, int rt) {
if(flag[rt] == lt) return;
flag[rt<<1] = flag[rt];
flag[rt<<1|1] = flag[rt];
sum[rt<<1] = flag[rt] * (mid-l+1);
sum[rt<<1|1] = flag[rt] * (r-mid);
flag[rt] = lt;
}
void update(int L, int R, LL v, int l, int r, int rt) {
if(L <= l && r <= R) {
flag[rt] = v;
sum[rt] = v * (r-l+1);
return;
}
int mid = (l + r) >> 1;
push_down(l, mid, r, rt);
if(L > mid) update(L,R,v,mid+1,r,rt<<1|1);
else if(R <= mid) update(L,R,v,l,mid,rt<<1);
else {
update(L,mid,v,l,mid,rt<<1);
update(mid+1,R,v,mid+1,r,rt<<1|1);
}
sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
LL query(int L, int R, int l, int r, int rt) {
if(L > R) return 0;
if(L <= l && r <= R) return sum[rt];
int mid = (l+r)>>1;
push_down(l, mid, r, rt);
if(L > mid) return query(L,R,mid+1,r,rt<<1|1);
else if(R <= mid) return query(L,R,l,mid,rt<<1);
else {
return query(L,mid,l,mid,rt<<1)+query(mid+1,R,mid+1,r,rt<<1|1);
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
for(int i = 1; i < n; ++i) scanf("%lld", &kr[i]);
for(int i = 2; i < n; ++i) kr[i] += kr[i-1];
for(int i = 1; i < n; ++i) kk[i] = kk[i-1] + kr[i];
build(1, n, 1);
int Q; scanf("%d", &Q);
char s[2]; int l, r;
while(Q --) {
scanf("%s%d%d", s, &l, &r);
if(s[0] == 's') {
printf("%lld\n", query(l,r,1,n,1)+kk[r-1]-(l>=2?kk[l-2]:0));
}else {
int L = l, R = n, mid, ans = l;
ar[l] = query(l,l,1,n,1) + r;
//printf("*%lld\n", ar[l]);
while(L <= R) {
mid = (L+R) >> 1;
if(ar[l] > query(mid,mid,1,n,1)) {
ans = mid;
L = mid+1;
}else R = mid-1;
}
//printf("%d\n", ans);
update(l, ans, ar[l], 1, n, 1);
}
}
return 0;
}
CF 1136D 贪心
题意:
一个排队游戏,
n
(
1
e
5
)
n(1e5)
n(1e5)个人,
m
(
1
e
5
)
m(1e5)
m(1e5)对关系
p
a
i
r
(
x
,
y
)
pair(x,y)
pair(x,y)表示如果编号为
x
x
x的人恰好排在编号为
y
y
y的人前面,那么
(
x
,
y
)
(x,y)
(x,y)可以互换位置。
给你初始这
n
n
n个人的排队顺序,问你
N
a
s
t
y
a
Nastya
Nastya最多能往前移动多少格位置,这人初始在最后面。
思路:
记得当时看到这题想了各种图论的方法,或者xjb搜索,感觉好像都不太行得通?
这个题走的顺序很有特点。
记录答案
a
n
s
ans
ans为当前排在最后一个人后面的人的个数(就是换位置换到后面去的)。
r
s
[
i
]
rs[i]
rs[i]记录为第
i
i
i个人后面有多少个人可以和你交换位置。
显然
i
f
(
r
s
[
i
]
=
=
n
−
i
−
a
n
s
)
if(rs[i] == n - i - ans)
if(rs[i]==n−i−ans)表示第
i
i
i个人和
N
a
s
t
y
a
Nastya
Nastya间的人都可以和第i个人换位置,那么他就可以直接排到最后面去;
否则更新可以和他交换位置的人的
r
s
[
]
rs[]
rs[]。
正确性是显然的,因为所有排到
N
a
s
t
y
a
Nastya
Nastya后面的人都不会对
r
s
[
]
rs[]
rs[]造成贡献,这个判断是无误的。
AC_code
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int MXN = 5e5 + 7;
int n, m;
int is[MXN];
int ls[MXN], rs[MXN];
std::vector<int> mp[MXN];
int main() {
#ifndef ONLINE_JUDGE
freopen("E://ADpan//in.in", "r", stdin);
//freopen("E://ADpan//out.out", "w", stdout);
#endif
scanf("%d%d", &n, &m);
for(int i = 1, x; i <= n; ++i) scanf("%d", &x), is[x] = i;
for(int i = 0, a, b; i < m; ++i) {
scanf("%d%d", &a, &b); a = is[a], b = is[b];
mp[b].push_back(a);
if(b == n) ++ rs[a];
}
int ans = 0;
for(int i = n - 1; i >= 1; --i) {
//printf("%d %d\n", i, rs[i]);
if(rs[i] == n - i - ans) {
++ ans;
}else {
for(int x: mp[i]) ++ rs[x];
}
}
printf("%d\n", ans);
#ifndef ONLINE_JUDGE
cout << "time cost:" << clock() << "ms" << "\n";
#endif
return 0;
}
原题目描述