线段树第一集(单点更新)
代码风格:notonlysuccess。
单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来。
很基础的,notonlysuccess里面有的, 先学学它里面的吧。
网站: http://www.notonlysuccess.com/index.php/segment-tree-complete/
以下是notonlysuccess里没有的
题意:插队问题.
关键:如何将这些人插入队伍中去。
类型:单点更新
思路:人要逆着插入队伍, 最先插入的一个数据的位置明显是题目给定的位置,可以确定,然后插入的几个人的位置前面插入的数据来决定,
用sum[]数组表示改线段空位置的个数,满足 pos<=sum[rt<<1](即左儿子的空位多于插入数的位置序号)就访问左儿子,否则访问右儿子
(访问右节点的时候注意pos要修改,改为pos-sum[rt],即整个线段的第pos个空位,在下一个右儿子那的第pos-sum[rt]个空位)。
代码1:
View Code
#include<stdio.h> #include<string.h> #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 200000 int sum[maxn<<2],pos[maxn],val[maxn],ans[maxn<<2];//ans储存插入的值; void push_up(int rt) { sum [rt] = sum[rt<<1]+sum[rt<<1|1]; } void build(int l, int r, int rt) { sum[rt]=r-l+1; if(l == r)return; mid; build(lson); build(rson); } void update(int pos, int val, int l, int r, int rt) { if(l == r){ans[rt]=val;sum[rt]--;return;} mid; if(pos <= sum[rt<<1])update(pos, val, lson); //根据左右儿子空位的多少和插入数据的位置比较来确定插入哪个儿子 else update(pos -sum[rt<<1], val, rson); push_up(rt); } void print(int l, int r, int rt) { if(l == r){printf("%d ",ans[rt]);return;} mid; print(lson); print(rson); } int main() { int n,i; while(~scanf("%d",&n)) { for(i=0;i<n;i++) scanf("%d%d",&pos[i],&val[i]); build(1, n ,1); for(i=n-1;i>=0;i--) update(pos[i]+1, val[i], 1, n, 1); print(1, n, 1); printf("\n"); } return 0; }
还有一种是节点记录已占有的位置(即非空位),cnt[rt]+pos<=m访问左节点,否则就访问右儿子(访问右儿子时节点修改为cnt[rt]+pos);
代码2:
View Code
#include<stdio.h> #include<string.h> #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 200003 int cnt[maxn<<2], ans[maxn<<2], pos[maxn], val[maxn]; int n; void update(int pos, int val, int l, int r, int rt) { cnt[rt]++; if(l == r) { ans[rt]=val; return; } mid; if(pos + cnt[rt<<1] <= m) update(pos, val, lson); else update(pos+cnt[rt<<1], val, rson); } void print(int l, int r, int rt) { if(l == r) { printf("%d ",ans[rt]); return; } mid; print(lson); print(rson); } int main() { int i; while(~scanf("%d",&n)) { memset(cnt, 0, sizeof(cnt));//相当于build建树 for(i=0;i<n;i++) scanf("%d%d", &pos[i], &val[i]); for(i = n-1; i>= 0; i--) update(pos[i]+1, val[i], 1, n, 1); print(1, n, 1); printf("\n"); } return 0; }
题意:给定n,每次从输入的a[i]位置上插入i,并求出每次的最长递增子序列(不用连续)
类型:单点更新
思路:
1. 先把整体的插入树中(为了获得每个人名次,保存在pos数组里面),方法跟上面的一题POJ 2828 Buy Tickets一样(这里的保存跟上一题一些不一样,节省了内存),
2. 求pos数组的最长上升序列, 用nlog(n)算法(不会的可以点进去学一下), 也可以在用另一棵线段树插入求最长上升序列(也是nlog(n),相比前一次更好,这里不提供后一种代码)。
View Code
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define maxn 100003 #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 int sum[maxn<<2]; int pos[maxn], a[maxn], tmp[maxn]; int n; void pushup(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void update(int p, int v, int l, int r, int rt) { if(l == r) { pos[v] = l; sum[rt]++; return; } mid; if( p + sum[rt<<1] <= m)update(p, v, lson); else update(sum[rt<<1] + p, v, rson); pushup(rt); } int main() { int i, j, ca, cas; scanf("%d", &cas); for( ca = 1; ca <= cas; ca++) { printf("Case #%d:\n", ca); scanf("%d", &n); for(i = 1; i <= n; i++) scanf("%d", &a[i]); memset(sum, 0, sizeof(sum));//建树 for(i = n; i >= 1; i--) update(a[i]+1, i, 1, n, 1); //nlog(n)最长上升序列 int cnt = 0;//cnt是长度 for(i = 1; i <= n; i++) { int l = 1, r = cnt; while(l <= r) { int m = (l + r)>>1; if(tmp[m] < pos[i])l = m + 1; else r = m - 1; } tmp[l] = pos[i]; if(l == cnt + 1)cnt++; printf("%d\n",cnt); } printf("\n"); } return 0; }
题意:输入整数n,下面紧接着n-1个数,分别代表在其前面有多少个数比其小(第一个数不存在所有没有输出)。
类型:单点更新
思路:类似以上两题, 逆着插入,请读者自行思考
View Code
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 8003 int sum[maxn<<2]; int a[maxn], ans[maxn]; int n; void pushup(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } int update(int p, int l, int r, int rt) { if(l == r) { sum[rt]++; return l; } mid; int ret = 0; if(p + sum[rt<<1] <= m) ret = update(p, lson); else ret = update(p + sum[rt<<1], rson); pushup(rt); return ret; } int main() { int i, j; while( ~scanf("%d", &n) ) { a[1] = 0; for(i = 2; i <= n; i++) scanf("%d", &a[i]); memset(sum, 0, sizeof(sum));//建树 for(i = n; i >= 1; i--) ans[i] = update(a[i] + 1, 1, n, 1); for(i = 1; i <= n; i++) printf("%d\n", ans[i]); } return 0; }
题意:描述一个容器有三种操作,压入a,删除a,查询容器中比a大的第k个数。
类型:单点更新
思路:本题难度主要在“查询容器中比a大的第k个数”,这个位置很不确定,所以我们必须让这个位置确定下来
首先找a的位置,找到后设为t,则我们只需要找第t+k个数就是题目要求的数了。
View Code
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 100003 int sum[maxn<<2]; int m; void pushup(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void update(int p, bool flag,int l, int r, int rt) { if(l == r) { if(flag) { if(sum[rt] >= 1) sum[rt]--; else printf("No Elment!\n"); } else sum[rt]++; return; } mid; if(p <= m ) update(p, flag, lson); else update(p, flag, rson); pushup(rt); } int cal(int a, int b, int l, int r, int rt)//寻找题目中的a的位置 { if(a <= l && r <= b) return sum[rt]; mid; int ret = 0; if( a <= m ) ret += cal(a, b, lson); if( b > m ) ret += cal(a, b, rson); return ret; } void query(int p, int l, int r, int rt ) { if(l == r) { printf("%d\n",l); return; } mid; if( p <= sum[rt<<1]) query(p, lson); else query(p - sum[rt<<1], rson); } int main() { int i, j; int op, a, k; while( ~scanf("%d", &m)) { memset(sum, 0, sizeof(sum)); while(m -- ) { scanf("%d", &op); if( op == 2) { scanf("%d%d", &a, &k); int t = cal(1, a, 1, maxn - 1, 1); if(k + t <= sum[1]) query(k + t, 1, maxn - 1, 1); else printf("Not Find!\n"); } else { scanf("%d", &a); update(a, op, 1, maxn - 1, 1); } } } return 0; }
5. HDU 4027 Can you answer these queries?
题意:
给你一个区间,两种操作:
1. 将区间[a,b]内的数开平方根向下取整。
2. 计算区间[a,b]的和。
类型:单点更新(看似成段更新)
思路:如果直接对每个数开根号(裸的单点更新),建树复杂度:nlog(n), 查询复杂度: mlog(n) (m为查询次数), 更新复杂度:m*n*log(n),
更新复杂度:m*n*log(n)= 1e5 * 1e5 * 5 明显TLE了,可见需要必要的优化,我们必须降低更新复杂度, 即我们要让开根号的次数尽量的少,题目中给出的数都是 <2的63次 - 1,我们发现当它开平方根一定次数时就变为1了, 是1就不要把时间浪费在开根号上了。
所以我们用col数组标记区间是否需要开根号,大大减少了计算时间。
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 100004 #define lld __int64 lld sum[maxn << 2]; bool col[maxn << 2]; //标记它以下的数都是否需要开根号 int n, m; void pushup(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; col[rt] = ( col[rt<<1] & col[rt<<1|1] ); } void build(int l, int r, int rt) { if(l == r) { scanf("%I64d", &sum[rt]); col[rt] = 0; return; } mid; build(lson); build(rson); pushup(rt); } void update(int a, int b, int l, int r, int rt) { if(col[rt])return; if(l == r && a <= l && l <= b) { if(sum[rt] == 1 || sum[rt] == 0) col[rt] = 1; // 1,0开根值不变,无需开根 sum[rt] = (lld) ( sqrt(sum[rt] *1.0) ); return; } mid; if( a <= m) update(a, b, lson); if( b > m ) update(a, b, rson); pushup(rt); } lld query(int a, int b, int l, int r, int rt) { if( a <= l && r <= b) return sum[rt]; mid; lld ret = 0; if(a <= m) ret += query(a, b, lson); if(b > m) ret += query(a, b, rson); return ret; } int main() { int op, a, b, ca = 1; while( ~scanf("%d", &n) ) { printf("Case #%d:\n", ca++); build(1, n, 1); scanf("%d", &m); while(m--) { scanf("%d%d%d", &op, &a, &b); if(a > b){ int tmp = a; a = b; b = tmp; }//坑爹的情况 a>b if(!op) update(a, b, 1, n, 1); else printf("%I64d\n", query(a, b, 1, n, 1)); } printf("\n"); } return 0; }
题意:求n个点的最大公约数,有两种操作,增加一个数,删去一个数,删去的数肯定是已经存在的。
类型:单点更新 + 离散化
思路:一看题目数据,就知道一定先要离散化,这题离散化跟notonlysuccess里的hdu2795 Billboard是同一种方法。题目中有相同的数,相同的数在树中的位置也是相同的,中间有删除操作,所以我们必须记录每一个数插入的次数,这里我用的是map。
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<map> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 100005 int sum[maxn<<2]; int x[maxn], num[maxn]; bool op[maxn]; int n, nn, mm; int gcd(int a, int b)//最大公约数 { return a == 0 ? b : gcd(b%a, a); } void pushup(int rt) { sum[rt] = gcd(sum[rt<<1], sum[rt<<1|1]); } void update(int p, int v, int l, int r, int rt) { if(l == r) { sum[rt] = v; return; } mid; if(p <= m) update(p, v, lson); else update(p, v, rson); pushup(rt); } int find(int key)//二分查找 { int l = 0, r = nn - 1; while(l <= r) { mid; if(key == x[m]) return m; else if(key < x[m]) r = m - 1; else l = m + 1; } return -1; } map<int, int>mp; int main() { int i, j; char s[3]; int a; while( ~scanf("%d", &n)) { //离散化 mm = 0; for(i = 1; i <= n; i++) { scanf("%s%d", s, &num[i]); if(s[0] == '-') op[i] = 0; else op[i] = 1; x[mm++] = num[i]; } sort(x, x + mm); nn = 1; for(i = 1; i < mm; i++) if(x[i] != x[i-1])x[nn++] = x[i]; mp.clear();//map清空 memset(sum, 0, sizeof(sum));//建树 for(i = 1; i <= n; i++) { int pos = find(num[i]); if(op[i]) { mp[ num[i] ]++; if(mp[ num[i] ] == 1) update(pos, num[i], 0, nn, 1); } else { mp[ num[i] ]--; if(mp[ num[i] ] == 0) update(pos, 0, 0, nn, 1); } if(!sum[1])printf("%d\n", 1);//里面没有数的时候最大公约数为1 else printf("%d\n", sum[1]); } } return 0; }
学长的做法:用了强大的vector离散化,学习了。
部分vector的应用,可以去这看一下http://www.cnblogs.com/ACMan/archive/2012/08/24/2653976.html
View Code
#include<cstdio> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 #define mid int m = (l + r)>>1 #define maxn 100005 int sum[maxn<<2]; int vis[maxn], num[maxn]; bool flag[maxn]; inline int gcd(int a,int b) { return a==0 ? b : gcd(b%a,a); } void pushup(int rt) { sum[rt] = gcd(sum[rt<<1], sum[rt<<1|1]); } void update(int p, int v, int l, int r, int rt) { if(l==r) { sum[rt]=v; return; } mid; if(p <= m) update(p, v, lson); else update(p, v, rson); pushup(rt); } char op[5]; vector<int> val; int main(){ int n,i,j; while( ~scanf("%d",&n) ) { memset(sum,0,sizeof(sum)); for(i=1;i<=n;i++) { scanf("%s%d", op, &num[i]); val.push_back(num[i]); flag[i] = (op[0]=='+'); } sort(val.begin(),val.end()); int tot = unique(val.begin(),val.end()) - val.begin(); //容器的总容量 int cnt=0; memset(vis,0,sizeof(vis)); for(i=1;i<=n;i++) { int pos = lower_bound(val.begin(), val.begin() + tot, num[i]) - val.begin() + 1; //返回大于等于关键字的第一个位置,注意val是有序的 if(flag[i]) { cnt++; vis[pos]++; if(vis[pos]==1) update(pos, val[pos-1], 1, tot, 1);//val[]的下标从0开始 } else{ cnt--; vis[pos]--; if(vis[pos]==0) update(pos, 0, 1, tot, 1); } if(cnt==0) printf("1\n"); else printf("%d\n",sum[1]); } } return 0; }
第一集更新完毕,日后做到了再补充,第二集准备中.......


浙公网安备 33010602011771号