线段树
https://vjudge.net/contest/213797#problem/A习题集
求区间最大值
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxmnode = 1 << 18; const int maxm = 5e4 + 5; struct NODE { int value; int left, right; } node[maxmnode]; int index[maxm]; void buildtree(int i, int left, int right) { node[i].left = left; node[i].right = right; node[i].value = 0; if(left == right) { index[left] = i; return; } buildtree(i << 1, left, (int)(floor(left + right) / 2.0)); buildtree((i << 1) + 1, (int)(floor(left + right) / 2.0) + 1, right); } void updatetree(int ri) { if(ri == 1) return; int fi = ri / 2; int a = node[fi << 1].value; int b = node[ (fi << 1) + 1 ].value; node[fi].value = max(a, b); updatetree(fi); } int MAX; void query(int i, int l, int r) { if(node[i].left == l && node[i].right == r) { MAX = max(MAX, node[i].value); return; } i = i << 1; if(l <= node[i].right) { if(r <= node[i].right) query(i, l, r); else query(i, l ,node[i].right); } i++; if(r >= node[i].left) { if(l >= node[i].left) query(i, l, r); else query(i, node[i].left, r); } } int main() { int n, m, q; while(~scanf("%d%d", &n, &m)) { buildtree(1, 1, n); for(int i = 1; i <= n; i++) { scanf("%d", &q); node[ index[i] ].value = q; updatetree(index[i]); } char ch[10]; int a,b; while(m--) { scanf("%s%d%d", ch, &a, &b); if(ch[0] == 'Q') { MAX = 0; query(1, a, b); printf("%d\n", MAX); } else { node[ index[a] ].value = b;//值无所谓,b; updatetree(index[a]); } } } return 0; }
A - 敌兵布阵
求区间和。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxm = 5e4 + 5; int sum[maxm << 2]; void pushup(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; } void buildtree(int rt, int le, int ri) { if(le == ri) { scanf("%d", &sum[rt]); return; } int m = (le + ri) >> 1; buildtree(rt << 1, le, m); buildtree(rt << 1 | 1, m + 1, ri); pushup(rt); } void updatetree(int rt, int pos, int val, int le, int ri){ if(le == ri){ sum[rt] += val; return; } int m = (le + ri) >> 1; if(m >= pos) updatetree(rt << 1, pos, val, le, m); else updatetree(rt << 1 | 1, pos, val, m + 1, ri); pushup(rt); } int query(int rt, int l, int r, int le, int ri) { if(l <= le && r >= ri) { return sum[rt]; } int ans = 0; int m = (le + ri) >> 1; // if(r <= m) ans += query(rt << 1, l, r, le, m); // else if(l > m) ans += query(rt << 1 | 1, l, r, m + 1, ri); // else { // ans += query(rt << 1, l, m, le, m); // ans += query(rt << 1 | 1, m + 1, r, m + 1, ri); // } if(l <= m) ans += query(rt << 1, l, r, le, m); if(r > m) ans += query(rt << 1 | 1, l, r, m + 1, ri); return ans; } int main() { int n, t; scanf("%d", &t); for(int k = 1; k <= t; k++) { printf("Case %d:\n", k); scanf("%d", &n); buildtree(1, 1, n); char ch[10]; int a, b; while(~scanf("%s", ch)) { if(ch[0] == 'E') break; scanf("%d%d", &a, &b); if(ch[0] == 'A') { updatetree(1, a, b, 1, n); } else if(ch[0] == 'S') { updatetree(1, a, -b, 1, n); } else { printf("%d\n", query(1, a, b, 1, n)); } } } return 0; }
I Hate It
区间最大值。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 2e5 + 5; int sum[maxm << 2]; void pushup(int rt) { sum[rt] = max(sum[rt << 1], sum[rt << 1 | 1]); } void buildtree(int rt, int le, int re) { if(le == re) { scanf("%d", &sum[rt]); return; } int m = (le + re) >> 1; buildtree(rt << 1, le, m); buildtree(rt << 1 | 1, m + 1, re); pushup(rt); } void update(int rt, int pos, int val, int le, int re) { if(le == re) { sum[rt] = val; return; } int m = (le + re) >> 1; if(pos <= m) update(rt << 1, pos, val, le, m); else update(rt << 1 | 1, pos, val, m + 1, re); pushup(rt); } int query(int rt, int l ,int r, int le, int re) { if(l <= le && r >= re) { return sum[rt]; } int m = (le + re) >> 1; int ans1 = 0; // if(r <= m) ans1 = query(rt << 1, l, r, le, m); // else if(l > m) ans1 = query(rt << 1 | 1, l, r, m + 1, re); // else ans1 = max(query(rt << 1, l, m, le, m), query(rt << 1 | 1, m + 1, r, m + 1, re)); if(l <= m) ans1 = max(ans1, query(rt << 1, l, r, le, m)); if(r > m) ans1 = max(ans1, query(rt << 1 | 1, l, r, m + 1, re)); return ans1; } int n, m; int main() { while(~scanf("%d%d", &n, &m)) { buildtree(1, 1, n); char ch[3]; int x, y; while(m--) { scanf("%s", ch); if(ch[0] == 'Q') { scanf("%d%d", &x, &y); printf("%d\n", query(1, x, y, 1, n)); } else { scanf("%d%d", &x, &y); update(1, x, y, 1, n); } } } return 0; }
区间修改加区间求和
A Simple Problem with Integers
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int maxm = 1e5 + 5; ll sum[maxm << 2], add[maxm << 2]; void pushup(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; } void pushdown(int rt, int m) { if(add[rt]) { add[rt << 1] += add[rt]; add[rt << 1 | 1] += add[rt]; sum[rt << 1] += add[rt] * (m - (m >> 1)); sum[rt << 1 | 1] += add[rt] * (m >> 1); add[rt] = 0; } } void buildtree(int le, int re, int rt) { add[rt] = 0; if(le == re) { scanf("%lld", &sum[rt]); return; } int m = (le + re) >> 1; buildtree(le, m, rt << 1); buildtree(m + 1, re, rt << 1 | 1); pushup(rt); } void updatetree(int c, int l ,int r, int rt, int le, int re) { if(l <= le && r >= re) { add[rt] += c; sum[rt] += (ll) c * (r - l + 1); return; } // if(le == re) return; pushdown(rt, re - le + 1); int m = (le + re) >> 1; if(r <= m) updatetree(c, l, r, rt << 1, le, m); else if(l > m) updatetree(c, l, r, rt << 1 | 1, m + 1, re); else { updatetree(c, l, m, rt << 1, le, m); updatetree(c, m + 1, r, rt << 1 | 1, m + 1, re); } pushup(rt); } ll query(int l, int r, int rt, int le, int re) { if(l <= le && r >= re) return sum[rt]; pushdown(rt, re - le + 1); int m = (le + re) >> 1; ll res = 0; if(r <= m) res += query(l, r, rt << 1, le, m); else if(l > m) res += query(l, r, rt << 1 | 1, m + 1, re); else { res += query(l, m, rt << 1, le, m); res += query(m + 1, r, rt << 1 | 1, m + 1, re); } return res; } int main() { int n, m; while(~scanf("%d%d", &n, &m)) { buildtree(1, n, 1); while(m--) { char ch[3]; scanf("%s", ch); int a, b, c; if(ch[0] == 'Q') { scanf("%d%d", &a, &b); printf("%lld\n", query(a, b, 1, 1, n)); } else { scanf("%d%d%d", &a, &b, &c); updatetree(c, a, b, 1, 1, n); } } } return 0; }
树状数组做法
https://blog.csdn.net/SSL_ZYC/article/details/81940902
这个博客里有讲解。
可以当做树状数组区间修改,区间求和的模板。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> #define maxx 100005 using namespace std; int n,q; long long a[maxx]; long long c[2][maxx]; void change(int k,int x,int p) { for(;x<=n;x+=x&-x)c[k][x]+=p; } long long query(int k,int x) { long long ans=0; for(;x;x-=x&-x)ans+=c[k][x]; return ans; } long long query(int x) { return a[x]+(x+1)*query(0,x)-query(1,x); } int main() { cin>>n>>q; for(int i=1;i<=n;i++) { scanf("%lld",a+i); a[i]+=a[i-1]; } char s[3]; int l,r,d; while(q--) { scanf("%s",s); if(s[0]=='Q') { scanf("%d%d",&l,&r); printf("%lld\n",query(r)-query(l-1)); } else { scanf("%d%d%d",&l,&r,&d); change(0,l,d); change(0,r+1,-d); change(1,l,l*d); change(1,r+1,-(r+1)*d); } } return 0; }
Mayor's posters 更染色问题很相像。区间染色,求最终某个区间内有几个颜色。
离散化
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 2e5 + 5; typedef pair<int, int> pii; int lazy[maxm << 3], col[maxm << 3], vis[maxm << 2]; int res; int a[maxm << 3], b[maxm << 3]; void pushdown(int rt) { if(lazy[rt]) { lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt]; col[rt << 1] = col[rt << 1 | 1] = lazy[rt]; } lazy[rt] = 0; } void pushup(int rt) { if(col[rt << 1] == 0 || col[rt << 1 | 1] == 0) { col[rt] = 0; } if(col[rt << 1] != col[rt << 1 | 1]) { col[rt] = 0; } else col[rt] = col[rt << 1]; } void build(int rt, int le, int re) { col[rt] = 0; lazy[rt] = 0; if(le == re) { return; } int m = (le + re) >> 1; build(rt << 1, le, m); build(rt << 1 | 1, m + 1, re); } void update(int rt, int l, int r, int val, int le, int re) { if(l <= le && r >= re) { lazy[rt] = val; col[rt] = val; return; } pushdown(rt); int m = (le + re) >> 1; if(r <= m) { update(rt << 1, l, r, val, le, m); } else if(l > m) { update(rt << 1 | 1, l, r, val, m + 1, re); } else { update(rt << 1, l, m, val, le, m); update(rt << 1 | 1, m + 1, r, val, m + 1, re); } pushup(rt); } void query(int rt, int l ,int r, int le, int re) { if(l <= le && r >= re && col[rt] && !vis[ col[rt] ]) { res++; vis[ col[rt] ] = 1; return; }
if(l <= le && r >= re && col[rt] && vis[ col[rt] ]) return; if(le == re) { return; }//这个因为前面If判断并不完全包括这种情况,所以要特判一下,防止无限递归。可能这一整个数都没被更新过,所以col都为零,往下走。 pushdown(rt); int m = (le + re) >> 1; if(r <= m) query(rt << 1, l, r, le, m); else if(l > m) query(rt << 1 | 1, l, r, m + 1, re); else { query(rt << 1, l, m, le, m); query(rt << 1 | 1, m + 1, r, m + 1, re); } } int t, n; int main() { scanf("%d", &t); while(t--) { scanf("%d", &n); memset(vis, 0, sizeof(vis)); memset(lazy, 0, sizeof(lazy)); memset(col, 0, sizeof(col)); for(int i = 1; i <= 2 * n; i++) { scanf("%d", &a[i]); a[i + 2 * n] = a[i] + 1;
//看下面的样例。 b[i] = a[i]; } sort(a + 1, a + 4 * n + 1); int ant = unique(a + 1, a + 4 * n + 1) - a; for(int i = 1; i <= 2 * n; i++) { b[i] = lower_bound(a + 1, a + ant, b[i]) - a; } build(1, 1, ant); for(int i = 1; i <= 2 * n; i += 2) { update(1, b[i], b[i + 1], i, 1, ant); } res = 0; query(1, 1, ant, 1, ant); printf("%d\n", res); } return 0; }
/*
该题目的数据有点水,错误的离散化也可以过
这里注意在离散化的时候不仅要保留相对大小关系,还要保证数据之间的连续性
例如对于这组数据
1 3 1 10 1 4 6 10
离散化完之后是四个点,4和6之间的5在离散化的过程中消失了,但是因为第一次海报是1 10,所以5是有颜色的,
但是维护离散化后的线段树没有该点的数据,普通离散化之后认为4和6是连续的了,应该每个点都插入一个+1的点,这样离散化能够保证连续性
*/
Tunnel Warfare 题意:开始村庄是联通的,随意炸毁,或者修复刚炸毁的那个,,然后查询某个村庄的联通村庄数。村庄1-n.
HDU - 1540 线段树单点更新,求最大连续子段和。
线段树要记住pushup是从下面开始更新的
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 50005; struct NODE { int l ,r; int li, ri, mi; //从左边开始的最大连续和,右边的,最终的最大连续和(也就是最大的)。 int mid() { return (l + r) >> 1; } } node[maxm << 2]; int n, k; int ro[maxm], num; char ch[2]; void pushup(int ri) { if(node[ri << 1].li == node[ri << 1].r - node[ri << 1].l + 1) { node[ri].li = node[ri << 1].li + node[ri << 1 | 1].li; } //如果左孩子的左连续区间是满的(事实上其他的也是满的),那么父节点的左连续区间就等于左孩子的加上右孩子的(因为连在一起了)。 else { node[ri].li = node[ri << 1].li; //不是满的就等于左孩子的左连续长度 } if(node[ri << 1 | 1].ri == node[ri << 1 | 1].r - node[ri << 1 | 1].l + 1) { node[ri].ri = node[ri << 1 | 1].ri + node[ri << 1].ri; } else { node[ri].ri = node[ri << 1 | 1].ri; } node[ri].mi = max( max(node[ri << 1].mi, node[ri << 1 | 1].mi), node[ri << 1].ri + node[ri << 1 | 1].li ); //最终的连续长度等于左边的最大连续长度,右边的最大连续长度,左边的右连续长度加上右边的左连续长度。 } void build(int ri, int l, int r) { node[ri].l = l; node[ri].r = r; node[ri].li = node[ri].ri = node[ri].mi = r - l + 1; //开始时满的 if(node[ri].l == node[ri].r) { node[ri].li = node[ri].ri = node[ri].mi = 1; //叶子节点是1. return; } int m = node[ri].mid(); build(ri << 1, l, m); build(ri << 1 | 1, m + 1, r); pushup(ri); } void update(int ri, int pos, int flag) { if(node[ri].l == node[ri].r) { if(flag) { //在叶子节点更新。 node[ri].li = node[ri].mi = node[ri].ri = 1; } else { node[ri].li = node[ri].mi = node[ri].ri = 0; } return; } int m = node[ri].mid(); if(pos <= m) { update(ri << 1, pos, flag); } else { update(ri << 1 | 1, pos, flag); } pushup(ri); } int query(int ri, int pos) { if(node[ri].l == node[ri].r || node[ri].mi == node[ri].r - node[ri].l + 1 || node[ri].mi == 0) { return node[ri].mi; } //如果是满的,为零,或者到了叶子节点就返回值。 int m = node[ri].mid(); if(pos <= m) { if(pos >= node[ri << 1].r - node[ri << 1].ri + 1) { return node[ri << 1].ri + node[ri << 1 | 1].li; } //如果在自己的左孩子右最大连续区间内,那么最大的连续长度就是自己的右边加上右边兄弟的左边的。(因为如果继续往下查肯定更小,而且如果自己左边的连续区间与右边的连续区间重合,那么这个区间就算完了。)
//第一种,左孩子的左区间与右区间是分开的,那么这个点在左孩子中的最大连续字段就是右区间,然后自然可以加上右孩子的左区间。
//第二种,左孩子的左区间与右区间是连在一起的,这种情况说明整个区间都是相连的,左右连续区间都是整个左孩子的长度。
else { return query(ri << 1, pos); //继续查询左孩子。 } } else { if(pos <= node[ri << 1 | 1].l + node[ri << 1 | 1].li - 1) { return node[ri << 1].ri + node[ri << 1 | 1].li; } else { return query(ri << 1 | 1, pos); } } } int main() { while(~scanf("%d%d", &n, &k)) { build(1, 1, n); int x; while(k--) { scanf("%s", ch); if(ch[0] == 'D') { scanf("%d", &x); ro[num++] = x; //记录刚炸毁的那条。 update(1, x, 0); } else if(ch[0] == 'Q') { scanf("%d", &x); int ans = query(1, x); printf("%d\n", ans); } else { int y = ro[--num]; update(1, y, 1); } } } return 0; }
I - Infinite Inversions CodeForces - 540E
题意:一个无限的自然数序列进行n次操作,每次交换其中2个位置上的数,求逆序数个数。
题解:操作数很大,如果直接求逆序数个数会超时。而最多有1e5的操作次数,那么可以把点离散化,两个交换点之间加入新的点,该点的权值即是两个点之间的点的个数。离散化后用树状数组直接求其逆序数。
相当于把区间转化成一个点,点的权值表示该区间里面元素的个数,然后在求逆序对。
http://codeforces.com/problemset/problem/540/E
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int maxm = 5e5 + 5; struct Node { ll a,b; }node[maxm]; ll n, cnt, len, tot; ll p[maxm], x[maxm], y[maxm], w[maxm], sum[maxm]; ll lowbit(ll x) { return x & -x; } void add(ll x,ll v) { while(x <= maxm) { sum[x] += v; x += lowbit(x); } } ll getsum(ll x) { ll s = 0; while(x > 0) { s += sum[x]; x -= lowbit(x); } return s; } int main() { scanf("%lld", &n); for(int i = 1; i <= n; i++) { scanf("%lld%lld", &node[i].a, &node[i].b); x[++cnt] = node[i].a; x[++cnt] = node[i].b; } sort(x + 1, x + cnt + 1); len = unique(x + 1, x + cnt + 1) - (x + 1);
//unique函数排除重复元素,返回第一个重复元素的位置。 for(int i = 1; i <= len; i++) { y[++tot] = x[i]; w[tot] = 1; if(x[i] + 1 < x[i + 1]) { y[++tot] = x[i] + 1; w[tot] = x[i + 1] - x[i] - 1; } } for(int i = 1; i <= tot; i++) p[i] = i; for(int i = 1; i <= n; i++) { ll p1 = lower_bound(y + 1, y + tot + 1, node[i].a) - y; ll p2 = lower_bound(y + 1, y + tot + 1, node[i].b) - y;
//调换顺序。虽然是区间的那种点没有交换,但是后面求逆序数的时候会算到那些是区间的点。 swap(p[p1], p[p2]); }
//上面都是离散化操作。 ll res = 0; for(int i = tot; i >= 1; i--) { res += w[p[i]] * getsum(p[i]); add(p[i],w[p[i]]);
//从后面开始,相当于每个点都求一下逆序数。 } printf("%lld\n", res); return 0; }
M - Decomposition into Good Strings
题意:好字符串就是一个字符串正好含有k个不同的字符,求该字符串的前缀,能够最少分成几个好字符串的拼接。
解法:先求出每一位字符往前形成好字符串的范围,l[i] 到r[i],注意求左边是如果是k+1个的话要加2(手动模拟一下)其他情况只是加1.然后得到最大值之后,就一个一个遍历,同时更新该点的最小值,如果能够延伸到
1的话,那肯定最小好字符串就是1.然后注意这个线段树区间是从零开始,因为查询时端点要用到0,把他设置为inf,因为是从1开始,所以只不会变,主要是为了防止越界错误。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; const int inf = 0x3f3f3f3f; const int mod = 1e9 + 7; const int maxm = 2e5 + 5; int sum[maxm << 2]; struct Node { int l, r; int mid() { return (l + r) >> 1; } } node[maxm << 2]; int k; char ch[maxm]; int num[30]; int l[maxm], r[maxm]; int c[maxm]; void pushup(int ri) { // sum[ri] = min(sum[ri], min(sum[ri << 1], sum[ri << 1 | 1])); sum[ri] = min(sum[ri << 1], sum[ri << 1 | 1]); } void build(int ri, int ll, int rr) { // printf("sn\n"); node[ri].l = ll, node[ri].r = rr; sum[ri] = inf; if(ll == rr) return; int m = node[ri].mid(); build(ri << 1, ll, m); build(ri << 1 | 1, m + 1, rr); } void update(int ri, int ind, int val) { // printf("zx\n"); if(node[ri].l == node[ri].r) { sum[ri] = val; return; } int m = node[ri].mid(); if(ind <= m) { update(ri << 1, ind, val); } else if(ind > m) { update(ri << 1 | 1, ind, val); } pushup(ri); } int query(int ri, int ll, int rr) { // printf("dc\n"); if(node[ri].l == ll && node[ri].r == rr) { return sum[ri]; } int m = node[ri].mid(); int ans = inf; if(rr <= m) { ans = min(ans, query(ri << 1, ll, rr)); } else if(ll > m) { ans = min(ans, query(ri << 1 | 1, ll, rr)); } else { ans = min(ans, query(ri << 1, ll, m)); ans = min(ans, query(ri << 1 | 1, m + 1, rr)); } return ans; } int main() { scanf("%d", &k); scanf("%s", ch + 1); int len = strlen(ch + 1); int val = 0, t = len; memset(l, -1, sizeof(l)); memset(r, -1, sizeof(r)); for(int i = len; i >= 1; i--) { while(val <= k && t > 0) { if(num[ch[t] - 'a' ] == 0) val++; num[ch[t] - 'a' ]++; t--; } if(val == k + 1) { l[i] = t + 2; } else if(val == k && t == 0) { l[i] = t + 1; } if(num[ch[i] - 'a' ] == 1) val--; num[ch[i] - 'a' ]--; } memset(num, 0, sizeof(num)); val = 0, t = len; for(int i = len; i >= 1; i--) { while(val < k && t > 0) { if(num[ch[t] - 'a' ] == 0) val++; num[ch[t] - 'a' ]++; t--; } if(val == k) { r[i] = t + 1; } if(num[ch[i] - 'a' ] == 1) val--; num[ch[i] - 'a' ]--; } //for(int i = 1; i <= len; i++) { // printf("%d %d\n", l[i], r[i]); //} build(1, 0, len); int mi = 0; for(int i = 1; i <= len; i++) { //printf("cac\n"); if(l[i] == -1) { c[i] = -1; } else if(l[i] == 1) { c[i] = 1; update(1, i, 1); } else { // printf("buics\n"); // int ll ,rr; mi = query(1, l[i] - 1, r[i] - 1); if(mi != inf) { c[i] = mi + 1; update(1, i, mi + 1); } else c[i] = -1; } } for(int i = 1; i <= len; i++) { printf("%d%c", c[i], i == len ? '\n': ' '); } return 0; }
然后这一题还可以用dp做,
相当于还是尺取去寻找“好字符串”,然后如果i - 1这个点与前面的字符可以组成好字符串的话,就可以用它来更新t这个点一级t后面可以组成好字符串的点,标准的dp思想,用前面的点去更新后面的点。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; const int inf = 0x3f3f3f3f; const int mod = 1e9 + 7; const int maxm = 2e5 + 5; int k; char ch[maxm]; int dp[maxm]; int c[maxm]; int num[30]; int main() { scanf("%d", &k); scanf("%s", ch + 1); int n = strlen(ch + 1); int t = 1, cnt = 0; memset(dp, inf, sizeof(dp)); memset(num, 0, sizeof(num)); dp[0] = 0; for(int i = 1; i <= n; i++) { while(t <= n && cnt < k) { if(num[ch[t] - 'a' ] == 0) cnt++; num[ch[t] - 'a' ]++; t++; } if(cnt == k) { if(dp[i - 1] != inf){ dp[t - 1] = min(dp[t - 1], dp[i - 1] + 1); // if(t == 6) printf("%d %d %d\n", dp[t], i - 1, dp[i - 1]) ; while(t <= n) { if(num[ch[t] - 'a' ] == 0) break; dp[t] = min(dp[i - 1] + 1, dp[t]); num[ch[t] - 'a']++; t++; } } } if(num[ch[i] - 'a' ] == 1) cnt--; num[ch[i] - 'a' ]--; } for(int i = 1; i <= n; i++) { printf("%d%c", dp[i] == inf ? -1 : dp[i], i == n ? '\n' : ' '); } return 0; }
http://acm.hdu.edu.cn/showproblem.php?pid=6514 吉司机线段树
https://blog.csdn.net/LSD20164388/article/details/89413657
题意:
给你n,m(n,m<=5e5),表示有编号1~n的人,m个区间
对于每个区间[l,r],表示编号为[l,r]的人之间任意两两配对的方法总数
要求对于每个区间,输出其任意两两配对的方法总数减去前面的区间已经有的选法总数
例如[2,5] 可以有 23 24 25 34 35 45 6种
再给你[4,6] 则只有 46 56 2种(45在上面出现过了)
思路:
把这题当图论来想。。。一段区间一定是连续的
令a[i]=k表示(k,i)(k+1,i)...,(i-1,i)已经被算入答案
初始化a[i]=i
对于每次给定的区间[ql,qr],如果max{a[ql],a[ql+1],...,a[qr]}中的最大值<=ql,那么表示区间i属于[ql,qr]中所有(ql,i)的方法总数已经计算过了
如果max{a[ql],a[ql+1],...,a[qr]}中的最大值>ql 那么就说明至少存在一个i(a[i]>ql)属于[ql,qr],使(ql,i)(ql+1,i),...,(a[i]-1,i)可以被算入答案,然后更新a[i]=ql
发现上述过程可以用一个线段树维护区间最大值。。。但是因为要计算答案,每次我们必须更新到底。。。(max{a[ql],a[ql+1],...,a[qr]}中的最大值<=ql,那么就可以直接return 0 了,否则继续往下更新)
在更新的同时统计答案
这就是传说中的吉司机线段树。。。
#include<bits/stdc++.h> #define ll long long #define inf 0x3f3f3f3f #define lson (i<<1) #define rson (i<<1|1) #define mid ((l+r)>>1) #define rep(i,a,b) for(register int i=(a);i<=(b);i++) #define dep(i,a,b) for(register int i=(a);i>=(b);i--) using namespace std; const int maxn=500010; int n,m,k; struct node { int ma,v; }a[maxn<<2]; void build(int i,int l,int r) { if(l==r) { a[i].ma=a[i].v=l; return ; } build(lson,l,mid); build(rson,mid+1,r); a[i].ma=max(a[lson].ma,a[rson].ma); } ll update(int i,int l,int r,int ql,int qr,int x) { ll res=0; if(x>=a[i].ma) return res; if(l==r) { res+=a[i].v-x; //cout<<i<<" "<<ql<<" "<<qr<<" "<<res<<endl; a[i].ma=a[i].v=x; return res; } if(ql<=l&&r<=qr) { res+=update(lson,l,mid,ql,mid,x); res+=update(rson,mid+1,r,mid+1,qr,x); a[i].ma=max(a[lson].ma,a[rson].ma); return res; } if(ql<=mid) res+=update(lson,l,mid,ql,min(qr,mid),x); if(qr>mid) res+=update(rson,mid+1,r,max(mid+1,ql),qr,x); a[i].ma=max(a[lson].ma,a[rson].ma); return res; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { build(1,1,n); rep(i,1,m) { int x,y; scanf("%d%d",&x,&y); printf("%lld\n",update(1,1,n,x,y,x)); } } return 0; }
https://blog.csdn.net/MrBird_to_fly/article/details/77141881 吉司机线段树题目
http://acm.hdu.edu.cn/showproblem.php?pid=5306 HDU 5306
0 x y t: For every x≤i≤y, we use min(ai,t) to replace the original ai's value.
1 x y: Print the maximum value of ai that x≤i≤y.
2 x y: Print the sum of ai that x≤i≤y.
case 1:t大于最大值,此时区间不变;
case 2:t小于严格次大值,此时至少把最大值和次大值变得相同,即使得区间变得相同,允许暴力更新;
case 3:t大于严格次大值,小于最大值,这里可以打懒标记。
考虑查询,只需维护最大值,最大值个数,严格次大值即可。
复杂度为nlogn或nlogn2.
//吉司机宇宙线段树之王! #include <bits/stdc++.h> using namespace std; const int maxn=1000005; typedef long long ll; int mx[maxn<<2];//区间最大值 int cnt[maxn<<2];//最大值个数 int se[maxn<<2];//区间严格次小值,就是必须小于最大值 int lazy[maxn<<2]; ll sum[maxn<<2];//区间和 int a[maxn]; int n, m; void putlazy(int u, int t){ sum[u]-=1LL*cnt[u]*(mx[u]-t); mx[u]=t; lazy[u]=t; } void pushdown(int u){ if(lazy[u]==-1)return; if(mx[2*u]>lazy[u]){ sum[2*u]-=1LL*cnt[2*u]*(mx[2*u]-lazy[u]); mx[2*u]=lazy[u]; lazy[2*u]=lazy[u]; } if(mx[2*u+1]>lazy[u]){ sum[2*u+1]-=1LL*cnt[2*u+1]*(mx[2*u+1]-lazy[u]); mx[2*u+1]=lazy[u]; lazy[2*u+1]=lazy[u]; } lazy[u]=-1; } void pushup(int u){ if(mx[2*u]==mx[2*u+1]){ mx[u]=mx[2*u]; cnt[u]=cnt[2*u]+cnt[2*u+1]; se[u]=max(se[2*u], se[2*u+1]); sum[u]=sum[2*u]+sum[2*u+1]; } else if(mx[2*u]>mx[2*u+1]){ mx[u]=mx[2*u]; cnt[u]=cnt[2*u]; se[u]=max(se[2*u], mx[2*u+1]); sum[u]=sum[2*u]+sum[2*u+1]; } else { mx[u]=mx[2*u+1]; cnt[u]=cnt[2*u+1]; se[u]=max(mx[2*u], se[2*u+1]); sum[u]=sum[2*u]+sum[2*u+1]; } } void build(int u, int l, int r){ lazy[u]=-1; if(l==r){ mx[u]=sum[u]=a[l]; cnt[u]=1; se[u]=-1; return; } int mid=(l+r)>>1; build(2*u, l, mid); build(2*u+1, mid+1, r); pushup(u); } void update(int u, int ql, int qr, int t, int l, int r){ if(mx[u]<=t)return; if(ql<=l && r<=qr && se[u] < t){ putlazy(u, t); return; } if(l == r) return; pushdown(u); int mid=(l+r)>>1; if(qr <= mid) update(u << 1, ql, qr, t, l, mid); else if(ql > mid) update(u << 1 | 1, ql, qr, t, mid + 1, r); else update(u << 1, ql, mid, t, l, mid), update(u << 1 | 1, mid + 1, qr, t, mid + 1, r); // update(2*u, ql, qr, t, l, mid); // update(2*u+1, ql, qr, t, mid+1, r); pushup(u); } int getmx(int u, int ql, int qr, int l, int r){ // if(ql>r||qr<l)return 0; if(ql<=l&&r<=qr)return mx[u]; pushdown(u); int mid=(l+r)>>1; int ans=0; if(qr <= mid) ans = max(ans, getmx(u << 1, ql, qr, l, mid)); else if(ql > mid) ans = max(ans, getmx(u << 1 | 1, ql, qr, mid + 1, r)); else { ans = max(ans, getmx(u << 1, ql, mid, l, mid)); ans = max(ans, getmx(u << 1 | 1, mid + 1, qr, mid + 1, r)); } // ans=max(ans, getmx(2*u, ql, qr, l, mid)); // ans=max(ans, getmx(2*u+1, ql, qr, mid+1, r)); return ans; } ll getsum(int u, int ql, int qr, int l, int r){ // if(ql>r||qr<l)return 0; if(ql<=l&&r<=qr)return sum[u]; pushdown(u); int mid=(l+r)>>1; ll ans=0; if(qr <= mid) ans += getsum(u << 1, ql, qr, l, mid); else if(ql > mid) ans += getsum(u << 1 | 1, ql, qr, mid + 1, r); else { ans += getsum(u << 1, ql, mid, l, mid); ans += getsum(u << 1 | 1, mid + 1, qr, mid + 1, r); } // ans+=getsum(2*u, ql, qr, l, mid); // ans+=getsum(2*u+1, ql, qr, mid+1, r); return ans; } int main(){ int T; scanf("%d", &T); while(T--){ scanf("%d%d", &n, &m); for(int i=1;i<=n;i++)scanf("%d", &a[i]); build(1, 1, n); for(int i=1;i<=m;i++){ int tag; scanf("%d", &tag); if(tag==0){ int x, y, t; scanf("%d%d%d", &x, &y, &t); update(1, x, y, t, 1, n); } else if(tag==1){ int x, y; scanf("%d%d", &x, &y); printf("%d\n", getmx(1, x, y, 1, n)); } else { int x, y; scanf("%d%d", &x, &y); printf("%lld\n", getsum(1, x, y, 1, n)); } } } }
https://www.cnblogs.com/GXZlegend/p/8349722.html
https://www.lydsy.com/JudgeOnline/status.php?user_id=downrainsun 提交的地方。
Input
//吉司机线段树 //牛逼啊 #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=300005; int n, m; ll mn[maxn<<2]; int cnt[maxn<<2]; ll se[maxn<<2]; ll lzcut[maxn<<2]; ll lzadd[maxn<<2]; int a[maxn]; void putcut(int u, ll c){ mn[u]=c; lzcut[u]=c; } void putadd(int u, ll c){ lzadd[u]+=c; if(lzcut[u]!=-1)lzcut[u]+=c; mn[u]+=c; if(se[u]!=-1)se[u]+=c; } void pushdown(int u){ if(lzadd[u]){ putadd(2*u, lzadd[u]); putadd(2*u+1, lzadd[u]); lzadd[u]=0; } if(lzcut[u]!=-1){ if(mn[2*u]<lzcut[u]){ mn[2*u]=lzcut[u]; lzcut[2*u]=lzcut[u]; } if(mn[2*u+1]<lzcut[u]){ mn[2*u+1]=lzcut[u]; lzcut[2*u+1]=lzcut[u]; } lzcut[u]=-1; } } void pushup(int u){ if(mn[2*u]==mn[2*u+1]){ mn[u]=mn[2*u]; cnt[u]=cnt[2*u]+cnt[2*u+1]; if(se[2*u]==-1&&se[2*u+1]==-1)se[u]=-1; else if(se[2*u]==-1)se[u]=se[2*u+1]; else if(se[2*u+1]==-1)se[u]=se[2*u]; else se[u]=min(se[2*u], se[2*u+1]); } else if(mn[2*u]<mn[2*u+1]){ mn[u]=mn[2*u]; cnt[u]=cnt[2*u]; if(se[2*u]==-1){ se[u]=mn[2*u+1]; } else { se[u]=min(mn[2*u+1], se[2*u]); } } else { mn[u]=mn[2*u+1]; cnt[u]=cnt[2*u+1]; if(se[2*u+1]==-1){ se[u]=mn[2*u]; } else { se[u]=min(mn[2*u], se[2*u+1]); } } } void build(int u, int l, int r){ lzcut[u]=-1; lzadd[u]=0; if(l==r){ mn[u]=a[l]; cnt[u]=1; se[u]=-1; return; } int mid=(l+r)/2; build(2*u, l, mid); build(2*u+1, mid+1, r); pushup(u); } void cover(int u, int ql, int qr, int c, int l, int r){ if(r<ql||qr<l)return; if(ql<=l&&r<=qr&&se[u]==-1){ putadd(u, (ll)c-mn[u]); return; } int mid=(l+r)/2; pushdown(u); cover(2*u, ql, qr, c, l, mid); cover(2*u+1, ql, qr, c, mid+1, r); pushup(u); } void add(int u, int ql, int qr, int c, int l, int r){ if(r<ql||qr<l||(se[u]==-1&&mn[u]==0&&c<=0))return; if(ql<=l&&r<=qr){ if(mn[u]+c>=0){ putadd(u, c); return; } else if(se[u]==-1||se[u]+c>0){ putadd(u, c); putcut(u, 0); return; } } int mid=(l+r)/2; pushdown(u); add(2*u, ql, qr, c, l, mid); add(2*u+1, ql, qr, c, mid+1, r); pushup(u); } int query(int u, int ql, int qr, int l, int r){ if(r<ql||qr<l)return 0; if(ql<=l&&r<=qr){ if(mn[u]==0)return cnt[u]; return 0; } int mid=(l+r)/2; int ret=0; pushdown(u); ret+=query(2*u, ql, qr, l, mid); ret+=query(2*u+1, ql, qr, mid+1, r); return ret; } int main(){ scanf("%d%d", &n, &m); for(int i=1;i<=n;i++){ scanf("%d", &a[i]); } build(1, 1, n); for(int i=1;i<=m;i++){ int tag; scanf("%d", &tag); if(tag==1){ int l, r, c; scanf("%d%d%d", &l, &r, &c); cover(1, l, r, c, 1, n); } else if(tag==2){ int l, r, c; scanf("%d%d%d", &l, &r, &c); add(1, l, r, c, 1, n); } else { int l, r; scanf("%d%d", &l, &r); printf("%d\n", query(1, l, r, 1, n)); } } }
http://codeforces.com/contest/1199/problem/D
一串序列,两种操作,第一种把某个点的值改为某个值,第二种:把一个区间内小于x的数全部改为x.
这一题正解并不是线段树。
线段树做法:lazy标记,mi最小值数组,cnt表示值。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 5; ll cnt[N << 2]; ll lazy[N << 2]; ll mi[N << 2]; void push_down(int rt) { if(lazy[rt] != -1) { if(mi[rt << 1] < mi[rt]) { mi[rt << 1] = mi[rt]; lazy[rt << 1] = lazy[rt]; } if(mi[rt << 1 | 1] < mi[rt]) { mi[rt << 1 | 1] = mi[rt]; lazy[rt << 1 | 1] = lazy[rt]; } lazy[rt] = -1; } } void pushup(int rt) { mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]); } int n; void print(int rt, int l, int r) { if(l == r) { printf("%I64d%c", max(cnt[rt], mi[rt]), l == n ? '\n' : ' '); return; } push_down(rt); int m = (l + r) >> 1; print(rt << 1, l, m); print(rt << 1 | 1, m + 1, r); } void build(int rt, int l, int r) { if(l == r) { scanf("%I64d", &cnt[rt]), lazy[rt] = -1, mi[rt] = cnt[rt]; return; } // push_down(rt); int m = (l + r) >> 1; build(rt << 1, l, m); build(rt << 1 | 1, m + 1, r); pushup(rt); } void update1(int rt, int l ,int r, int pos, ll val) { if(l == r) { cnt[rt] = val; mi[rt] = val; return; } push_down(rt); int m = (l + r) >> 1; if(pos <= m) update1(rt << 1, l, m, pos, val); else update1(rt << 1 | 1, m + 1, r, pos, val); pushup(rt); } //void update2(int rt, int l, int r, ll val) { // if(mi[rt] >= val) return; // else { // mi[rt] = val; // lazy[rt] = val; // return; // } //// push_down(rt); //// int m = (l + r) >> 1; //// update2(rt << 1, l, m, val); //// update2(rt << 1 | 1, m + 1, r, val); //// pushup(rt); //} int main() { scanf("%d", &n); build(1, 1, n); int q; scanf("%d", &q); int opt, x; ll y; while(q--) { scanf("%d", &opt); if(opt == 1) { scanf("%d%I64d", &x, &y); update1(1, 1, n, x, y); } else { scanf("%I64d", &y); if(mi[1] < y) { mi[1] = y; lazy[1] = y; } // update2(1, 1, n, y); } } print(1, 1, n); return 0; }
https://ac.nowcoder.com/acm/contest/888/E
线段树,叶子节点表示区间。
这个题是给你一个图,每条边有一个允许通过的体型范围,然后要从一号节点到n号节点,要求可以通过的体型值有多少个。
线段树+按秩合并并查集
https://blog.csdn.net/qq_41117236/article/details/99179573
对于每个原始区间的l与r,存为l与r + 1,并放入一个数组中去重排序,最后找区间的时候起点lower_bound找起点区间,重点lower_bound找到后还要减一,因为终点所代表的区间并不在这个范围内,
那么然后叶子节点代表的区间就是q[l + 1] - q[l]。
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> P; const int N=1e5+10; struct edge{ //边 int u,v,l,r; }f[N]; int pre[N],rk[N]; //pre[]表示父节点,rk[]表示并查集的秩 int q[N<<1]; vector <P> a[N<<3]; //线段树 vector <int> t[30]; P V; int n,m,ans; void build(int x,int l,int r,int fl,int fr) { if(l==fl&&r==fr){ // printf("%d %d\n", l, r); a[x].push_back(V); return; } int mid=(l+r)>>1; if(fr<=mid) build(x<<1,l,mid,fl,fr); else if(fl>mid) build(x<<1|1,mid + 1,r,fl,fr); else build(x<<1,l,mid,fl,mid),build(x<<1|1,mid + 1,r,mid + 1,fr); } int Find(int x) { return pre[x]==x?x:Find(pre[x]); } void join(int rt,int x,int y,int dep) { x=Find(x),y=Find(y); if(x!=y){ if(rk[x]<=rk[y]){ //按秩合并 pre[x]=y,t[dep].push_back(x); if(rk[x]==rk[y]) rk[y]++; } else{ pre[y]=x,t[dep].push_back(y); } } } int cnt; void dfs(int x,int l,int r,int dep) //x表示节点编号,dep表示节点深度 { t[dep].clear(); for(int i = 0; i < a[x].size(); i++) join(x,a[x][i].first,a[x][i].second,dep); // printf("%d\n", dep); if(l==r){ // printf("%d %d\n", l, r); if(Find(1)==Find(n)) ans+=q[r + 1]-q[l]; //更新贡献 // for(auto i:t[dep]) pre[i]=i; for(int i = 0; i < t[dep].size(); i++) pre[t[dep][i] ] = t[dep][i]; return; } int mid=(l+r)>>1; dfs(x<<1,l,mid,dep+1); dfs(x<<1|1,mid + 1,r,dep+1); for(int i = 0; i < t[dep].size(); i++) pre[t[dep][i] ] = t[dep][i]; // for(auto i:t[dep]) pre[i]=i; } int main() { scanf("%d%d",&n,&m); cnt=0; for(int i=1;i<=n;i++) pre[i]=i; for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&f[i].u,&f[i].v,&f[i].l,&f[i].r); q[++cnt]=f[i].l; q[++cnt]=f[i].r + 1; } sort(q+1,q+cnt+1); //把区间排序进行离散化 cnt=unique(q+1,q+cnt+1)-q-1; for(int i=1;i<=m;i++){ V=P(f[i].u,f[i].v); int p1=lower_bound(q+1,q+cnt+1,f[i].l)-q; int p2=lower_bound(q+1,q+cnt+1,f[i].r+1)-q - 1; build(1,1,cnt - 1,p1,p2); //以区间为关键字建树 } ans=0; // printf("%d\n", cnt); dfs(1,1,cnt - 1,1); //从根1向下dfs printf("%d\n",ans); return 0; }