线段树专题
1.hud 1166 http://acm.hdu.edu.cn/showproblem.php?pid=1166 敌兵布阵
本题目涉及到了区间操作和点操作两种操作。
区间操作(区间和):我们采用了递推的方法来建树,建好了叶子结点之后,我们再回溯来建整个区间。(这个操作是关键)
点操作:点操作必然涉及到区间的改动,我们判定,只要这个点在一个区间上,那么我们就对这个区间操作。
#include "iostream" #include "string" #include "algorithm" using namespace std; #define maxn 50005 int len, peo[maxn], ans; typedef struct node { int l, r, m; int s; }node; node t[maxn*4]; void build(int ll, int le, int ri) { t[ll].l = le; t[ll].r = ri; t[ll].m = (le+ri)>>1; if(le==ri) t[ll].s = peo[le]; else { build(ll<<1, le, t[ll].m); build(ll<<1|1, t[ll].m+1, ri); t[ll].s = t[ll<<1].s + t[ll<<1|1].s; } } void query(int ll, int le, int ri) { if(le<=t[ll].l && ri>=t[ll].r) ans+=t[ll].s; else { if(le>t[ll].m) query(ll<<1|1, le, ri); else if(ri<=t[ll].m) query(ll<<1, le, ri); else { query(ll<<1, le, t[ll].m); query(ll<<1|1, t[ll].m+1, ri); } } } void add(int ll, int p, int v) { if(p>=t[ll].l && p<=t[ll].r) t[ll].s += v; if(t[ll].l==p && t[ll].r==p) return; if(p>t[ll].m) add(ll<<1|1, p, v); else if(p<=t[ll].m) add(ll<<1, p, v); } void sub(int ll, int p, int v) { if(p>=t[ll].l && p<=t[ll].r) t[ll].s -= v; if(t[ll].l==p && t[ll].r==p) return; if(p>t[ll].m) sub(ll<<1|1, p, v); else if(p<=t[ll].m) sub(ll<<1, p, v); } int main() { int i, j, C, le, ri; char op[10]; scanf("%d", &C); for(i=1; i<=C; i++) { printf("Case %d:\n", i); scanf("%d", &len); for(j=1; j<=len; j++) scanf("%d", peo+j); build(1, 1, len); while(scanf("%s", op)!=EOF) { if(strcmp(op, "End")==0) break; scanf("%d%d", &le, &ri); if(strcmp(op, "Query")==0) { ans = 0; query(1, le, ri); printf("%d\n", ans); } else if(strcmp(op, "Add")==0) { add(1, le, ri); } else if(strcmp(op, "Sub")==0) { sub(1, le, ri); } } } return 0; }
2.hdu 1754 http://acm.hdu.edu.cn/showproblem.php?pid=1754 I hate it
本题目涉及到了区间操作和点操作两种操作。
区间操作(区间最值):平常的建树。
点操作:我们每向区间插入一个点就和当前区间的最大值比较一下,如果大于最大值,则更新。(这个操作是关键)
#include "iostream" #include "string" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define maxn 200005 typedef struct node { int l, r, m, ma; }node; int ans; node t[maxn<<2]; void Build(int ll, int l, int r) { t[ll].ma=-1; t[ll].l=l; t[ll].r=r; t[ll].m=(l+r)>>1; if(l!=r) { Build(ll<<1, l, t[ll].m); Build(ll<<1|1, t[ll].m+1, r); } } void Insert(int ll, int p, int v) { if(p>=t[ll].l && p<=t[ll].r && t[ll].ma<v) t[ll].ma=v; if(p==t[ll].l && p==t[ll].r) return; if(p<=t[ll].m) Insert(ll<<1, p, v); else if(p>t[ll].m) Insert(ll<<1|1, p, v); } int Find(int ll, int l, int r) { if(l==t[ll].l && t[ll].r==r) return t[ll].ma; if(r<=t[ll].m) Find(ll<<1, l, r); else if(l>t[ll].m) Find(ll<<1|1, l, r); else { int a=Find(ll<<1, l, t[ll].m); int b=Find(ll<<1|1, t[ll].m+1, r); return a>b?a:b; } } int main() { int n, op, i, j, v, l, r; char opp[10]; while(scanf("%d%d", &n, &op)!=EOF) { Build(1, 1, n); for(i=1; i<=n; i++) { scanf("%d", &v); Insert(1, i, v); } for(i=0; i<op; i++) { scanf("%s%d%d", opp, &l, &r); if(strcmp(opp, "Q")==0) { printf("%d\n", Find(1, l, r)); } else if(strcmp(opp, "U")==0) { Insert(1, l, r); } } } }
3.hdu 1698http://acm.hdu.edu.cn/showproblem.php?pid=1698 Just a hook
本题只涉及到了区间操作。
区间操作(全区间键值的改变):在维护更新的时候我们将无用的区间屏蔽掉,这样就可以大大提高时间效率。如何屏蔽?这个是关键。
#include "iostream" #include "string" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define maxn 100005 #define L(x) (x<<1) #define R(x) (x<<1|1) typedef struct node { int l, r, z; }node; node t[maxn<<2]; void Build(int ll, int l, int r) { t[ll].l=l; t[ll].r=r; t[ll].z=1; if(l<r) { int m=(t[ll].l+t[ll].r)>>1; Build(L(ll), l, m); Build(R(ll), m+1, r); } } void Update(int ll, int l, int r, int z) { if(l==t[ll].l && t[ll].r==r) { t[ll].z=z;//更新 return; } if(t[ll].z>0)//只要不是刚好的区间,我们就往下更新,同时将这一层的区间屏蔽掉,说具体点就是t[ll].z=-1 { t[R(ll)].z = t[ll].z; t[L(ll)].z = t[ll].z; t[ll].z=-1; } if(t[ll].l==t[ll].r) return; int m=(t[ll].l+t[ll].r)>>1; if(r<=m) Update(L(ll), l, r, z); else if(l>m) Update(R(ll), l, r, z); else { Update(L(ll), l, m, z); Update(R(ll), m+1, r, z); } } int Query(int ll, int l, int r) { if(t[ll].z>0) return (t[ll].r-t[ll].l+1)*t[ll].z; int m=(t[ll].l+t[ll].r)>>1; if(r<=m) return Query(L(ll), l, r); else if(l>m) return Query(R(ll), l, r); else return Query(L(ll), l, m)+Query(R(ll), m+1, r); } int main() { int c, n, op, i, l, r, z, j, ans; scanf("%d", &c); for(i=1; i<=c; i++) { scanf("%d%d", &n, &op); Build(1, 1, n); for(j=0; j<op; j++) { scanf("%d%d%d", &l, &r, &z); Update(1, l, r, z); } ans=Query(1, 1, n); printf("Case %d: The total value of the hook is %d.\n", i, ans); } }
4.hdu 1394http://acm.hdu.edu.cn/showproblem.php?pid=1394 Minimum Inversion Number
本题是一种类型的题目——逆序数的求法。
本题涉及到了区间操作。
但是本题的区间操作有一定的讲究,也就是一边插入一边查找。不然等插入完了再来查找就不知道是在它前面还是后面出现的了。
其它的就和平常的线段树一样了。
#include "iostream" #include "string" #include "cstring" #include "algorithm" #include "cstdio" using namespace std; #define maxn 5005 #define L(x) (x<<1) #define R(x) (x<<1|1) typedef struct segtree { int l, r, s; }segtree; segtree t[maxn<<2]; int s[maxn]; void Build(int ll, int l, int r) { t[ll].l=l; t[ll].r=r; t[ll].s=0; if(l<r) { int m=(l+r)>>1; Build(L(ll), l, m); Build(R(ll), m+1, r); } } int Query(int ll, int l, int r) { if(t[ll].l==l && t[ll].r==r) return t[ll].s; int m=(t[ll].l+t[ll].r)>>1; if(r<=m) return Query(L(ll), l, r); else if(l>m) return Query(R(ll), l, r); else return Query(L(ll), l, m)+Query(R(ll), m+1, r); } void Update(int ll, int k) { t[ll].s++; if(t[ll].l==t[ll].r) return; int m=(t[ll].l+t[ll].r)>>1; if(k<=m) Update(L(ll), k); else Update(R(ll), k); } int main() { int n, i, t, ans; while(scanf("%d", &n)!=EOF) { ans=0; Build(1, 0, n-1); for(i=0; i<n; i++)//我们来求一下“输入顺序时”的逆序对数 { scanf("%d", &s[i]); ans+=Query(1, s[i], n-1);//在s[i]~n-1这个区间中有多少个数是在s[i]之前出现的,那么对于这个数而言就有多少个逆序对数,不是吗?好好想想…… Update(1, s[i]); } t=ans; for(i=0; i<n-1; i++) { t=t+(n-1-s[i])-s[i]; if(ans>t) ans=t; } printf("%d\n", ans); } return 0; }
5. hdu 2492http://acm.hdu.edu.cn/showproblem.php?pid=2492 Ping pong
本题也是一道逆序数的题目。只不为要求两种逆序都要算出来。也就是说A>B>C和A<B<C两种情况。
其实仔细想想本题我们可以得到一个这样的结论。
如果完全按照题目的描述来想算法,我们是很容易进入误区的,
但是,如果我们将题目的意思也取反来看一下,当然这个取反和原题意是等价的,就很容易知道是什么样的题目了。
#include "iostream" #include "string" #include "cstring" #include "algorithm" #include "cstdio" using namespace std; #define maxn 20010 #define maxs 100010 #define L(x) (x<<1) #define R(x) (x<<1|1) typedef struct segtree { int l, r, s; }segtree; segtree t[maxs<<2]; int s[maxn]; void Build(int f, int l, int r) { t[f].l=l; t[f].r=r; t[f].s=0; if(l<r) { int m=(l+r)>>1; Build(L(f), l, m); Build(R(f), m+1, r); } } int Query(int f, int l, int r) { if(t[f].l==l && t[f].r==r) return t[f].s; int m=(t[f].l+t[f].r)>>1; if(r<=m) return Query(L(f), l, r); else if(l>m) return Query(R(f), l, r); else return Query(L(f), l, m)+Query(R(f), m+1, r); } void Update(int f, int k) { t[f].s++; if(t[f].l==t[f].r) return; int m=(t[f].l+t[f].r)>>1; if(k<=m) Update(L(f), k); else Update(R(f), k); } int main() { int n, i, T, k; long long fs[maxn], fb[maxn], ss[maxn], sb[maxn]; scanf("%d", &T); for(k=0; k<T; k++) { scanf("%d", &n); memset(fs, 0, sizeof fs); memset(fb, 0, sizeof fb); memset(ss, 0, sizeof ss); memset(sb, 0, sizeof sb); Build(1, 1, maxs); for(i=0; i<n; i++)//我们来求一下“输入顺序时”的逆序对数 { scanf("%d", &s[i]); fb[i]=Query(1, s[i]+1, maxs);//big if(s[i]==1) { Update(1, s[i]); continue; } fs[i]=Query(1, 1, s[i]-1);//在s[i]~n-1这个区间中有多少个数是在s[i]之前出现的,那么对于这个数而言就有多少个逆序对数,不是吗?好好想想…… Update(1, s[i]); } Build(1, 1, maxs); for(i=n-1; i>=0; i--) { sb[i]=Query(1, s[i]+1, maxs); if(s[i]==1) { Update(1, s[i]); continue; } ss[i]=Query(1, 1, s[i]-1);//small Update(1, s[i]); } long long ans=0; for(i=1; i<n-1; i++) { ans+=fs[i]*sb[i]; ans+=fb[i]*ss[i]; } printf("%I64d\n", ans); } return 0; }
6. hdu 3308http://acm.hdu.edu.cn/showproblem.php?pid=3308 LCIS
本题是一种类型的题目,解法有一定的套路。也就是lm,rm,m,len,lv,rv等等,具体是什么含义,看看代码就知道了。
本题涉及到了区间操作和点的操作。
区间操作,这个比较麻烦,因为没有记录左右边界,所以要小心,这只是一个不起眼的小难题。
区间的操作,难就难在up函数,和query函数上,这两个一定要看懂。其它的就好说了。
点操作一般,就是平时的操作。
#include "iostream" #include "string" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define L(x) (x<<1) #define R(x) (x<<1|1) #define maxn 100005 typedef struct node { int lm, rm, m; int lv, rv, len; }node;//我们并没有记录l, r node t[maxn<<2]; void Up(node &f, node &l, node &r) { f.lv = l.lv; f.rv = r.rv; if(l.rv<r.lv)//是递增的 { f.lm=(l.lm==l.len?l.lm+r.lm:l.lm);//左边的最值就是它的区间长度那么……否则…… f.rm=(r.rm==r.len?r.rm+l.rm:r.rm);//右边的最值就是它的区间长度那么……否则…… f.m=max(max(f.lm, f.rm), l.rm+r.lm);//l.rm+r.lm表示不包含左右端点的值 f.m=max(max(l.m, r.m), f.m); } else//如果不是递增的 { f.lm=l.lm; f.rm=r.rm; f.m=max(l.m, r.m); } } void Build(int f, int l, int r) { t[f].len=r-l+1; if(l==r) { scanf("%d", &t[f].lv); t[f].rv = t[f].lv; t[f].lm = t[f].rm = t[f].m = 1; return; } int m=(l+r)>>1; Build(L(f), l, m); Build(R(f), m+1, r); //当建树完成的时候我们要进行统计运算,也就是向上计算 Up(t[f], t[L(f)], t[R(f)]); } void Update(int f, int l, int r, int p, int v)//我们只需要更新它的lv, rv就可以了 { if(l==r) { t[f].lv = t[f].rv = v; return; } int m=(l+r)>>1; if(p<=m) Update(L(f), l, m, p, v);// else Update(R(f), m+1, r, p, v);//我们的目的是更新叶子结点,所以不需要更新区间 //当修改完成的时候我们要进行统计运算,也就是向上计算 Up(t[f], t[L(f)], t[R(f)]); } node Query(int f, int l, int r, int &L, int &R) { if(L<=l && r<=R) return t[f]; int m=(l+r)>>1; node t1, t2; t1.len=t2.len=0; if(L<=m) t1=Query(L(f), l, m, L, R);//往左可走,就走 if(R>m) t2=Query(R(f), m+1, r, L, R);//往右可走,就走 if(t1.len && t2.len)//都找到了,从两边找到了 { node tmp; Up(tmp, t1, t2); tmp.len=t1.len+t2.len;//哦哦,有可能多次更新,所以不能大意哦!!! return tmp; } if(t1.len) return t1; return t2; } int main() { int T; scanf("%d",&T); while(T--) { int n,m,x,y; node te; char c; scanf("%d%d",&n,&m); n--; Build(1, 0,n); getchar(); while(m--) { getchar(); scanf("%c%d%d",&c,&x,&y); if(c=='Q') { te=Query(1,0,n,x,y); printf("%d\n",te.m); } else Update(1, 0, n, x, y); } } return 0; }
7.hdu 4046http://acm.hdu.edu.cn/showproblem.php?pid=4046 Panda
本题就是一道简单的线段树题目。
但是难就难在只有当我们经过一定的转化之后,才可以用线段树来做,转化的这个过程是比较困难的。
这就是本题的难点所在。转化 转化 转化 转化 再转化
#include "iostream" #include "string" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define L(x) (x<<1) #define R(x) (x<<1|1) #define maxn 50010 typedef struct node { int l, r, s; }node; node t[maxn<<2]; char str[maxn]; int a[maxn]; void build(int f, int l, int r) { t[f].l = l; t[f].r = r; if(l==r) { t[f].s=a[l]; return; } int m=(l+r)>>1; build(L(f), l, m); build(R(f), m+1, r); t[f].s=t[L(f)].s+t[R(f)].s; } void update(int f, int l, int r, int p, int v) { if(l==r) { t[f].s=v; return; } int m=(l+r)>>1; if(p<=m) update(L(f), l, m, p, v); else update(R(f), m+1, r, p, v); t[f].s=t[L(f)].s+t[R(f)].s; } int Query(int f, int l, int r, int L, int R) { if(L<=l && r<=R) return t[f].s; int m=(l+r)>>1; int ret=0; if(L<=m) ret += Query(L(f), l, m, L, R); if(R>m) ret += Query(R(f), m+1, r, L, R); return ret; } int main() { int cas,T=0,n,m,r,l,c; char st; scanf("%d",&cas); while(cas--) { scanf("%d %d",&n,&m); scanf("%s",str); memset(a,0,sizeof(a)); printf("Case %d:\n",++T); for(int i=2;i<n;i++)//标记以该下标结尾的长度为3的子串是否符合要求 { if(str[i]!='w') a[i]=0; else if(str[i-1]=='b'&&str[i-2]=='w') a[i]=1; } build(1,0,n); while(m--) { scanf("%d",&c); if(c==0) { scanf("%d %d",&l,&r); l+=2; if(l>r) printf("0\n"); else printf("%d\n",Query(1,0,n,l,r)); } else { scanf("%d %c",&l,&st); if(str[l]==st)//若要就该的字符跟原先相同,则不用修改了 continue; str[l]=st; if(l>=2&&str[l-2]=='w'&&str[l-1]=='b'&&str[l]=='w') { if(a[l]==0)//若改之前是不符合要求的串,则更新 update(1, 0,n,l,1); a[l]=1; } else if(l>=2&&a[l]==1)//若改之前是符合要求的串,则更新 { update(1, 0,n,l,0); a[l]=0; } if(l>=1&&str[l]=='b'&&str[l-1]=='w'&&str[l+1]=='w') { if(a[l+1]==0) update(1, 0,n,l+1,1); a[l+1]=1; } else if(l<n-1&&l>=1&&a[l+1]==1) a[l+1]=0,update(1, 0,n,l+1,0); if(str[l]=='w'&&str[l+1]=='b'&&str[l+2]=='w') { if(a[l+2]==0) update(1, 0,n,l+2,1); a[l+2]=1; } else if(l<n-2&&a[l+2]==1) a[l+2]=0,update(1, 0,n,l+2,0); } } } return 0; }
posted on 2012-08-21 15:54 More study needed. 阅读(233) 评论(0) 编辑 收藏 举报