本文主要介绍用线段树来维护(最大区间和,最大子段和,最长连续上升子序列)的问题。
HDU 1540 Tunnel Warfare(最长连续区间+单点修改)
洛谷 P2894 [USACO08FEB]酒店Hotel(最长连续区间+区间修改)
吉首大学2019年程序设计竞赛-白山茶与红玫瑰(最长连续区间+区间修改)
SPOJ - GSS1 Can you answer these queries I(最大子段和)
HDU 1540 Tunnel Warfare(最长连续区间+单点修改)
题意:有三种操作:
操作一:某个村庄被毁灭。
操作二:给出一个村庄的坐标,求包含包含这个村庄的的最长未被村庄的长度。
操作三:最后一个被摧毁的村庄被修复。
题解:首先我们可以想到用线段树来维护最长连续区间长度,我们现在可以用1代表改点没有被破坏,用0表示改点被破坏,然后用一个栈或数组来装上次破坏的点。然后就是线段树维护1的最长长度了,详解请看代码。
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls[maxn << 2], rs[maxn << 2], sum[maxn << 2];
///sum表示区间最长连续序列,ls表示从左端点开始最长连续前缀,rs表示从r开始最长后缀
int n, m;
void push_up(int l, int r, int rt) {
int mid = (l + r) >> 1;
ls[rt] = ls[rt << 1];
if (ls[rt << 1] == mid - l + 1)///要是左区间最长长度等于左区间长度,那么区间最长前缀长度要加上右儿子区间的最长前缀
ls[rt] += ls[rt << 1 | 1];
rs[rt] = rs[rt << 1 | 1];
if (rs[rt << 1 | 1] == r - mid)///与上面情况一样
rs[rt] += rs[rt << 1];
sum[rt] = max(ls[rt], rs[rt]);
sum[rt] = max(sum[rt], max(sum[rt << 1], sum[rt << 1 | 1]));
sum[rt] = max(sum[rt], rs[rt << 1] + ls[rt << 1 | 1]);
///区间的最长连续长度为,左区间,右区间最长连续长度,和左区间的最长后缀+右区间的最长前缀的三者最大。(最后一种情况表示最长连续区间在中间)
}
void build(int l, int r, int rt) {
if (l == r) {
ls[rt] = rs[rt] = sum[rt] = 1;///初始化所有点都没有被破坏
return;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
push_up(l, r, rt);
}
void push_date(int pos, int x, int l, int r, int rt) {
sum[rt] = ls[rt] = rs[rt] = 0;
if (l == r) {
sum[rt] = ls[rt] = rs[rt] = x;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
push_date(pos, x, lson);
else
push_date(pos, x, rson);
push_up(l, r, rt);
}
int query(int pos, int l, int r, int rt) {
if (l == r)
return sum[rt];
int mid = (l + r) >> 1;
if (pos <= mid) {///点在左儿子区间里
if (pos >= mid - rs[rt << 1] + 1)
return ls[rt << 1 | 1] + rs[rt << 1];///要是点正好在左儿子的最长后缀中,肯定最长区间和就是左儿子的最长后缀长度+右儿子的最长前缀(左儿子的最长后缀与右儿子的最长前缀是连着的)
else
return query(pos, lson);///要是没有落在最长后缀中,就不用考虑是否加上右儿子的最长前缀,直接往下找就行了
} else {
if (pos <= mid + ls[rt << 1 | 1])///与上面情况类似
return ls[rt << 1 | 1] + rs[rt << 1];
else
return query(pos, rson);
}
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
build(1, n, 1);
stack<int> q;
while (m--) {
char str[2];
int x;
scanf("%s", str);
if (str[0] == 'D') {
scanf("%d", &x);
push_date(x, 0, 1, n, 1);
q.push(x);
} else if (str[0] == 'Q') {
scanf("%d", &x);
printf("%d\n", query(x, 1, n, 1));
} else {
if (q.size()) {
push_date(q.top(), 1, 1, n, 1);
q.pop();
}
}
}
}
return 0;
}
洛谷 P2894 [USACO08FEB]酒店Hotel(最长连续区间+区间修改)
题意:有n代表有n个房间,编号为1-n,开始都为空房,有m行操作,以下 每行先输入一个数 i ,表示一种操作:
若i为1,表示查询房间,再输入一个数x,表示在1-n 房间中找到长度为x的连续空房,输出连续x个房间中左端的房间号,尽量让这个房间号最小,若找不到长度为x的连续空房,输出0。
若i为2,表示退房,再输入两个数 x,y 代表 让房间号 x-x+y-1 的房间为空。
题解:这是一个最长连续区间,区间修改的裸题,只是注意找连续区间最左端的房间号(可以好好理解下下面代码的Find_pos函数)。
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls[maxn << 2], rs[maxn << 2], sum[maxn << 2];
int lazy[maxn << 2];
int n, m;
void push_up(int l, int r, int rt) {
int mid = (l + r) >> 1;
ls[rt] = ls[rt << 1];
if (ls[rt << 1] == mid - l + 1)
ls[rt] += ls[rt << 1 | 1];
rs[rt] = rs[rt << 1 | 1];
if (rs[rt << 1 | 1] == r - mid)
rs[rt] += rs[rt << 1];
sum[rt] = max(ls[rt], rs[rt]);
sum[rt] = max(sum[rt], max(sum[rt << 1], sum[rt << 1 | 1]));
sum[rt] = max(sum[rt], rs[rt << 1] + ls[rt << 1 | 1]);
}
void push_down(int l, int r, int rt) {
if (lazy[rt] != -1) {
int mid = (l + r) >> 1;
lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt];
sum[rt << 1] = ls[rt << 1] = rs[rt << 1] = (mid - l + 1) * lazy[rt];
sum[rt << 1 | 1] = ls[rt << 1 | 1] = rs[rt << 1 | 1] = (r - mid) * lazy[rt];
lazy[rt] = -1;
}
}
void build(int l, int r, int rt) {
sum[rt] = ls[rt] = rs[rt] = 0;
if (l == r) {
sum[rt] = ls[rt] = rs[rt] = 1;
return;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
push_up(l, r, rt);
}
void push_date(int L, int R, int opt, int l, int r, int rt) {
if (L <= l && R >= r) {
sum[rt] = ls[rt] = rs[rt] = (r - l + 1) * opt;
lazy[rt] = opt;
return;
}
push_down(l, r, rt);
int mid = (l + r) >> 1;
if (R <= mid)
push_date(L, R, opt, lson);
else if (L > mid)
push_date(L, R, opt, rson);
else {
push_date(L, mid, opt, lson);
push_date(mid + 1, R, opt, rson);
}
push_up(l, r, rt);
}
int Find_pos(int len, int l, int r, int rt) {
if(l==r)
return l;
push_down(l, r, rt);
int mid = (l + r) >> 1;
if(len<=sum[rt<<1])
return Find_pos(len,lson);
else if(rs[rt<<1]+ls[rt<<1|1]>=len)
return mid-rs[rt<<1]+1;
else
return Find_pos(len,rson);
}
int main() {
me(lazy, -1);
scanf("%d%d", &n, &m);
build(1, n, 1);
while (m--) {
int opt, l, len;
scanf("%d", &opt);
if (opt == 1) {
scanf("%d", &len);
if (len <= sum[1]) {
int pos = Find_pos(len, 1, n, 1);
printf("%d\n", pos);
push_date(pos, pos+len-1, 0, 1, n, 1);
} else
printf("0\n");
} else {
scanf("%d%d", &l, &len);
push_date(l, l + len - 1, 1, 1, n, 1);
}
}
return 0;
}
吉首大学2019年程序设计竞赛-白山茶与红玫瑰(最长连续区间+区间修改)
题意:现在给出一段序列,只有0和1,现在有两种操作:
操作一:给出[l,r],求出该区间内最长连续0区间的长度。
操作二:给出[l,r],将该区间的0全部变成1,1全部变成0。
题解:现在建立两棵线段树,一个保存最长连续1区间长度,一个保存最长连续0区间长度,在执行操作一:就是常规求法就行了,在进行操作二时,将对应区间的对应数组的值全部交换,这样两者刚好反过来,就是我们要求的值。
注意:因为翻转两次就等于没有翻转,所以lazy数组,初值为0,1表示要翻转,然后每次修改都将对应lazy数组值异或1就行了。
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson L,mid,rt<<1
#define rson mid+1,R,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls1[maxn << 2], rs1[maxn << 2], sum1[maxn << 2];
int ls2[maxn << 2], rs2[maxn << 2], sum2[maxn << 2];
int lazy[maxn << 2];
int n, m, x;
void push_up(int l, int r, int rt) {
int mid = (l + r) >> 1;
ls1[rt] = ls1[rt << 1];
if (ls1[rt << 1] == mid - l + 1)
ls1[rt] += ls1[rt << 1 | 1];
rs1[rt] = rs1[rt << 1 | 1];
if (rs1[rt << 1 | 1] == r - mid)
rs1[rt] += rs1[rt << 1];
sum1[rt] = max(sum1[rt << 1], sum1[rt << 1 | 1]);
sum1[rt] = max(sum1[rt], max(ls1[rt], rs1[rt]));
sum1[rt] = max(sum1[rt], rs1[rt << 1] + ls1[rt << 1 | 1]);
ls2[rt] = ls2[rt << 1];
if (ls2[rt << 1] == mid - l + 1)
ls2[rt] += ls2[rt << 1 | 1];
rs2[rt] = rs2[rt << 1 | 1];
if (rs2[rt << 1 | 1] == r - mid)
rs2[rt] += rs2[rt << 1];
sum2[rt] = max(sum2[rt << 1], sum2[rt << 1 | 1]);
sum2[rt] = max(sum2[rt], max(ls2[rt], rs2[rt]));
sum2[rt] = max(sum2[rt], rs2[rt << 1] + ls2[rt << 1 | 1]);
}
void push_down(int rt) {
if (lazy[rt]) {
lazy[rt << 1] ^= 1;
lazy[rt << 1 | 1] ^= 1;
swap(ls1[rt << 1], ls2[rt << 1]);
swap(rs1[rt << 1], rs2[rt << 1]);
swap(sum1[rt << 1], sum2[rt << 1]);
swap(ls1[rt << 1 | 1], ls2[rt << 1 | 1]);
swap(rs1[rt << 1 | 1], rs2[rt << 1 | 1]);
swap(sum1[rt << 1 | 1], sum2[rt << 1 | 1]);
lazy[rt] = 0;
}
}
void build(int l, int r, int rt) {
if (l == r) {
scanf("%d", &x);
ls1[rt] = rs1[rt] = sum1[rt] = x;
if (x == 1)
ls2[rt] = rs2[rt] = sum2[rt] = 0;
else
ls2[rt] = rs2[rt] = sum2[rt] = 1;
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
push_up(l, r, rt);
}
void push_date(int l, int r, int L, int R, int rt) {
if (L <= l && R >= r) {
lazy[rt] ^= 1;
swap(ls1[rt], ls2[rt]);
swap(rs1[rt], rs2[rt]);
swap(sum1[rt], sum2[rt]);
return;
}
push_down(rt);
int mid = (l + r) >> 1;
if (R <= mid)
push_date(l, mid, L, R, rt << 1);
else if (L > mid)
push_date(mid + 1, r, L, R, rt << 1 | 1);
else {
push_date(l, mid, L, mid, rt << 1);
push_date(mid + 1, r, mid + 1, R, rt << 1 | 1);
}
push_up(l, r, rt);
}
int query(int l, int r, int L, int R, int rt) {
if (L <= l && R >= r)
return sum1[rt];
push_down(rt);
int mid = (l + r) >> 1;
if (R <= mid) {
return query(l, mid, L, R, rt << 1);
} else if (L > mid) {
return query(mid + 1, r, L, R, rt << 1 | 1);
} else {
int len1 = query(l, mid, L, mid, rt << 1);
int len2 = query(mid + 1, r, mid + 1, R, rt << 1 | 1);
int len3 = min(mid - L + 1, rs1[rt << 1]) + min(R - mid, ls1[rt << 1 | 1]);
return max(len1, max(len2, len3));
}
return 0;
}
int main() {
scanf("%d", &n);
build(1, n, 1);
scanf("%d", &m);
while (m--) {
int opt, l, r;
scanf("%d%d%d", &opt, &l, &r);
if (opt == 1)
push_date(1, n, l, r, 1);
else
printf("%d\n", query(1, n, l, r, 1));
}
return 0;
}
SPOJ - GSS1 Can you answer these queries I(最大子段和)
题意:给出一段序列,给出m次询问,让你给出该区间内最大连续子段和。
题解:跟最长连续子序列差不多,这是一道模板题,直接上代码。
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int mod = 998244353;
const int maxn = 5e4 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define Lson l,mid,rt<<1
#define Rson mid+1,r,rt<<1|1
#define lson rt<<1
#define rson rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
struct node {
int val, lmax, rmax, max;
} tree[maxn << 2];
void push_up(int rt) {
tree[rt].max = max(max(tree[lson].max, tree[rson].max), tree[lson].rmax + tree[rson].lmax);
tree[rt].lmax = max(tree[lson].lmax, tree[lson].val + tree[rson].lmax);
tree[rt].rmax = max(tree[rson].rmax, tree[rson].val + tree[lson].rmax);
tree[rt].val = tree[lson].val + tree[rson].val;
}
void build(int l, int r, int rt) {
if (l == r) {
scanf("%d", &tree[rt].val);
tree[rt].lmax = tree[rt].rmax = tree[rt].max = tree[rt].val;
return;
}
int mid = (l + r) >> 1;
build(Lson);
build(Rson);
push_up(rt);
}
node query(int ql, int qr, int l, int r, int rt) {
if (ql <= l && qr >= r)
return tree[rt];
int mid = (l + r) >> 1;
if (qr <= mid)
return query(ql, qr, Lson);
else if (ql > mid)
return query(ql, qr, Rson);
else {
node L = query(ql, mid, Lson);
node R = query(mid + 1, qr, Rson);
node ans;
ans.max = max(max(L.max, R.max), L.rmax + R.lmax);
ans.lmax = max(L.lmax, L.val + R.lmax);
ans.rmax = max(R.rmax, R.val + L.rmax);
return ans;
}
}
int main() {
int n, m;
scanf("%d", &n);
build(1, n, 1);
scanf("%d", &m);
while (m--) {
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(l, r, 1, n, 1).max);
}
return 0;
}
HDU3308 LCIS(区间最长连续上升子序列)
题意:求区间最长连续上升子序列。
PS:因为要更改节点的值,所以不能用dp做。这里就可以用线段树来维护。
这就是一个裸的线段树求最长上升子序列的题,细节看代码,主要是要注意更新左子树,右子树,和区间最长子序列长度。
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int mod = 998244353;
const int maxn = 1e5 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define Lson l,mid,rt<<1
#define Rson mid+1,r,rt<<1|1
#define lson rt<<1
#define rson rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[maxn], sum[maxn << 2], ls[maxn << 2], rs[maxn << 2];
void updata(int l, int r, int rt)///更新区间最长子序列长度
{
int mid = (l + r) >> 1;
ls[rt] = ls[lson], rs[rt] = rs[rson];
sum[rt] = max(sum[lson], sum[rson]);
if (a[mid] < a[mid + 1]) {///当满足这个条件,说明可以左儿子右儿子合并
if (ls[rt] == mid - l + 1)///当左儿子区间全部都是上升子序列,又能区间合并,这时候左儿子区间加上右儿子的左区间
ls[rt] = ls[lson] + ls[rson];
if (rs[rt] == r - mid)///与上面类似
rs[rt] = rs[rson] + rs[lson];
sum[rt] = max(sum[rt], ls[rson] + rs[lson]);
}
}
void build(int l, int r, int rt)///建树
{
if (l == r) {
sum[rt] = ls[rt] = rs[rt] = 1;
return;
}
int mid = (l + r) >> 1;
build(Lson);
build(Rson);
updata(l, r, rt);
}
void pushdata(int pos, int c, int l, int r, int rt)///更改节点值
{
if (l == r) {
a[l] = c;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
pushdata(pos, c, Lson);
else
pushdata(pos, c, Rson);
updata(l, r, rt);
}
int query(int L, int R, int l, int r, int rt)///求出最长子序列长度
{
if (l >= L && R >= r)
return sum[rt];
int ret = 0;
int mid = (l + r) >> 1;
if (mid >= L)
ret = max(ret, query(L, R, Lson));
if (mid < R)
ret = max(ret, query(L, R, Rson));
if (a[mid] < a[mid + 1]) {
ret = max(ret, min(mid - L + 1, rs[lson]) + min(R - mid, ls[rson]));
///[m+1,R]与[m+1,r]相交部分的最大前缀+[L,m]与[l,m]的最大后缀.
}
return ret;
}
int main() {
int t;
cin >> t;
while (t--) {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
build(1, n, 1);
while (m--) {
char s[2];
int a, b;
scanf("%s%d%d", s, &a, &b);
if (s[0] == 'U')
pushdata(a + 1, b, 1, n, 1);
else
printf("%d\n", query(a + 1, b + 1, 1, n, 1));
}
}
return 0;
}