线段树

来源:互联网 发布:广告点击赚钱软件 编辑:程序博客网 时间:2024/04/29 14:57

1247逆序对

给定一个数组A,它包含N个整数,分别是A[1],A[2],…A[N]。如果存在下标i和j,使得 i < j 且 A[i]>A[j]同时成立,则(i,j)就为一个“逆序对”。
那么A数组总共有多少对不同的“逆序对”?

第一行为n(1≤n≤100000)。
接下来是n行,每行一个长整型范围内的整数。

分析

此题需要用到离散化、数组计数,需要用线段树进行维护每个区间对应数值出现的数目。不断加入数,然后在线段树中快速查询每一段的和,加起来即可。
下面贴上代码:

#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>#include <iostream>using namespace std;const int ML=100021;struct node{    int l,r;    long long sum;}tree[4*ML];struct lsh {    int num, p;}a[ML];bool _sort(lsh x, lsh y) {    return x.num<y.num;}int n, m;long long pm1[ML],pm2[ML];void buildTree(int x,int l,int r){    tree[x].l=l, tree[x].r=r;    if( l==r) {  tree[x].sum=0; return ; }    buildTree(  x*2 ,     l      , (l+r)/2);    buildTree( x*2+1, 1+((l+r)/2),     r  );    tree[x].sum=tree[x*2].sum+ tree[x*2+1].sum;}void update(int x,int s,int e){    if( tree[x].l==s&&tree[x].r==s) {        tree[x].sum+=e;        return ;    }    if( tree[x].l>s || tree[x].r<s) return ;    update( x*2  , s, e);    update( x*2+1, s, e);    tree[x].sum= tree[x*2].sum +tree[x*2+1].sum;}long long query( int x, int l, int r){    if( tree[x].l >=l && tree[x].r <=r) return tree[x].sum;      if( tree[x].l > r || tree[x].r < l) return 0;               return query( x*2, l, r)+query( x*2+1, l, r);}long long ans=0;int main(){    freopen("1247.in","r",stdin);    freopen("1247.out","w",stdout);    scanf("%d", &n);    for( int i=1; i<=n; i++) {        scanf("%d", &a[i].num);        a[i].p=i;    }    buildTree(1,1, n);    sort( a+1, a+n+1, _sort); 离散化    int p=1;    pm1[1] = 1;    for( int i=2; i<=n; i++) {        if(a[i].num!=a[i-1].num) p++;        pm1[i]=p;    }    for ( int i=1; i<=n; i++) {        pm2[a[i].p]=pm1[i];    }    for( int i=1; i<=n; i++) {        ans+=query(1, pm2[i]+1, 100000);        update(1, pm2[i], 1);    }     printf("%lld", ans);    return 0;}

1669碉堡

中国长城有N个碉堡,把长城看成一条数轴,每个碉堡看成数轴上的一个点,第i个碉堡的坐标是X[i]。每个碉堡都有一个坚固度,第i个碉堡的坚固度是S[i]。长城是用来抵抗外敌的,所以碉堡之间要联系。任意两个碉堡之间都要联系一次,因此N个碉堡之间总共会有N×(N-1)/2次联系。两个碉堡联系一次是需要代价的,比如第i个碉堡与第j个碉堡联系一次的代价cost[i][j] = abs(X[i]-X[j]) × max(S[i],S[j]),其中abs是求绝对值。你的任务是输出N×(N-1)/2次联系的总代价。

分析

此题跟逆序对十分相似,同样需要用线段树进行数组计数,不断修改线段树,一个一个加入数组,动态维护线段树,然后用logN查询比a小的有多少个,比a大的有多少个。同时用到了乘法分配律来简化时间复杂度。
下面贴上代码:

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>const int INF=21000000;using namespace std;struct zmk {    int l, r;    long long sum, s;}tree[80010];struct node {    int s,w;}a[200010];bool _sort(node x, node y) {    return x.s<y.s;}void build (int x, int l, int r) {    tree[x].l=l, tree[x].r=r;    if(l==r) return ;    build(x*2, l, (l+r)/2);    build(x*2+1, (l+r)/2+1, r);}void update(int x,int u,int e) {    if(tree[x].l==u&&tree[x].r==u) {        tree[x].sum+=e;        tree[x].s+=u;        return ;    }    if(tree[x].l>u||tree[x].r<u) return ;    update( x*2, u, e);    update( x*2+1, u, e);    tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;    tree[x].s=tree[x*2].s+tree[x*2+1].s;}long long query1(int x, int l ,int r) {    if(tree[x].l>=l&&tree[x].r<=r ) {        return tree[x].sum;    }    if(tree[x].l>r||tree[x].r<l) return 0;    return query1(x*2,l,r)+query1(x*2+1,l,r);}long long query2(int x, int l ,int r) {    if(tree[x].l>=l&&tree[x].r<=r ) {        return tree[x].s;    }    if(tree[x].l>r||tree[x].r<l) return 0;    return query2(x*2,l,r)+query2(x*2+1,l,r);}int main() {    freopen("1669.in", "r", stdin);    freopen("1669.out", "w", stdout);    int n, _max=-INF;    scanf("%d", &n);    for( int i=1; i<=n; i++) {        scanf("%d %d", &a[i].s, &a[i].w);        _max=max(_max,a[i].w);    }    build(1, 1, _max);    sort(a+1, a+n+1, _sort);    long long ans=0;    for( int i=1; i<=n; i++) {    long long bb=a[i].w*query1(1, 1, a[i].w)-query2(1, 1, a[i].w);    long long cc=query2(1,a[i].w+1,20000)-a[i].w*query1(1, a[i].w+1, 20000 );        ans+=(bb+cc)*a[i].s;        update(1, a[i].w, 1);    }    printf("%lld", ans);}

在这里强调一下:开数组数据范围要注意,避免溢出等情况。

1670围棋

风靡一时的Alpha Go人工智能战胜了人类最顶级的围棋冠军,在业界引起了轰动。然而这与本题关系不大(哈哈),把围棋的棋盘看成是一个平面二维坐标系,把棋子抽象成平面二维坐标系里的一个点,总共有N个棋子,第i个棋子的坐标是(X[i],Y[i])。棋子是分“级别的”, 第i个棋子和第j个棋子的关系有如下3种:
1、 棋子i的级别比棋子j的级别高,当且仅当: X[i] >= X[j]且Y[i] >= Y[j],则棋子i在棋子j的右上方区域。
2、 棋子i的级别比棋子j的级别低,当且仅当: X[i] <= X[j]且Y[i] <= Y[j],则棋子i在棋子j的左下方区域。
3、 如果都不满足如上两个条件,则棋子i和棋子j不具备可比性。
对于第i个棋子来说,它的级别F[i]就等于级别比它低的棋子的数量。

分析

此题跟逆序对十分相似,同样需要用线段树进行数组计数,不断修改线段树,一个一个加入数组,动态维护线段树,然后用logN查询比a小的有多少个,比a大的有多少个。同时用到了乘法分配律来简化时间复杂度。
其实这题简直就是水了点,对比逆序对,此题不用离散化,只需要我们将逆序对中的所有下标替换为此题的X坐标。

下面堵上代码:

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>const int INF=21000000;using namespace std;struct zmk {    int l, r;    long long sum, s;}tree[400100];struct node {    int s, w, id;}a[100010];bool _sort(node x, node y) {    return (x.s<y.s) ||(x.s==y.s &&x.w<y.w) ;}int b[100010];void build (int x, int l, int r) {    tree[x].l=l, tree[x].r=r;    if(l==r) return ;    build(x*2, l, (l+r)/2);    build(x*2+1, (l+r)/2+1, r);}void update(int x,int u,int e) {    if(tree[x].l==u&&tree[x].r==u) {        tree[x].sum+=e;        return ;    }    if(tree[x].l>u||tree[x].r<u) return ;    update( x*2, u, e);    update( x*2+1, u, e);    tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;    tree[x].s=tree[x*2].s+tree[x*2+1].s;}long long query(int x, int l ,int r) {    if(tree[x].l>=l&&tree[x].r<=r ) return tree[x].sum;    if(tree[x].l>r||tree[x].r<l) return 0;    return query(x*2,l,r)+query(x*2+1,l,r);}int main() {    freopen("1670.in", "r", stdin);    freopen("1670.out", "w", stdout);    int n, _max=-INF;    scanf("%d", &n);    for( int i=1; i<=n; i++) {        scanf("%d %d", &a[i].s, &a[i].w);        a[i].s++, a[i].w++;        a[i].id=i;        _max=max(_max,a[i].w);    }    build(1, 1, _max);    sort( a+1, a+n+1, _sort);    for( int i=1; i<=n; i++) {        b[a[i].id]=query( 1, 1, a[i].w);        update(1, a[i].w, 1);    }    for(int i=1; i<=n; i++) printf("%d\n", b[i]);}

1665圆盘

桌面上有N个转盘,从左到右排放,编号从1到N。每个转盘只能显示0至9中的一个数字。每个转盘有一个小开关,按转盘上的开关,转盘的数字就会自动加1,(如果转盘的数字是9,那么按开关时,转盘的数字就会变成0 )。
Luka现在玩这个游戏,他有两张白纸。
Luka 重复以下的步骤m次(注意:下面的3个步骤要次序执行):
(1)选两个整数A、 B (1 ≤ A ≤ B ≤ N) 并且把它们写到第一张白纸上。
(2)把编号在区间[A,B]内(包括A、B)的所有转盘显示的数字逐一的加起来,把结果写到第二张白纸上。
(3)对编号在区间[A,B]内(包括A、B)的转盘,都按一次开关。

做完M次这样的操作后,请你输出第二张白纸的数据。

分析

这题还是十分水的,动态维护十颗线段树,第一次做没经验……
下面堵上代码:

#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>#include <iostream>using namespace std;struct node {    int l, r, add;    long long sum;    int n[10];}tree[2000010];int N, M;char a[2500010];void build( int x, int l, int r) {    tree[x].l=l, tree[x].r=r;    if( l==r) {  tree[x].sum=a[l]-'0'; tree[x].n[a[l]-'0']++; return ; }    build( x*2  ,     l     ,(l+r)/2);    build( x*2+1,1+((l+r)/2),    r  );    tree[x].sum= tree[x*2].sum+ tree[x*2+1].sum;    for( int i=0; i<=9; i++) tree[x].n[i]= tree[x*2].n[i] + tree[x*2+1].n[i];}long long _dat( int x) {    long long Sum=0;    int t[12];    for( int i=0; i<=9; i++) t[i]=tree[x].n[i];    for( int i=0; i<=9; i++) tree[x].n[(i+tree[x].add)%10]=t[i], t[i]=0;    for( int i=0; i<=9; i++) Sum+=tree[x].n[i]*i;    return Sum;}void pushdown(int x) {    tree[x].sum=_dat(x);    tree[ x*2 ].add+=tree[x].add;    tree[x*2+1].add+=tree[x].add;    //tree[ x*2 ].sum=_dat(x*2);    //tree[x*2+1].sum=_dat(x*2+1);    tree[x].add=0;}void update(int x,int l,int r,int k){    if( tree[x].l>=l && tree[x].r<=r) {        tree[x].add+=k;        if( tree[x].l==tree[x].r) tree[x].sum=_dat(x),tree[x].add=0;        else pushdown(x);        return ;    }    if( tree[x].l>r || tree[x].r<l) return ;    if( tree[x].add) pushdown(x);    update( x*2  , l, r, k);    update( x*2+1, l, r, k);    tree[x].sum= tree[x*2].sum+tree[x*2+1].sum;    for( int i=0; i<=9; i++) tree[x].n[i]= tree[x*2].n[i] + tree[x*2+1].n[i];}long long query( int x, int l, int r) {    if( tree[x].add) pushdown(x);    if( tree[x].l > r || tree[x].r < l) return 0;     if( tree[x].l >=l && tree[x].r <=r) return tree[x].sum;    return query(x*2, l, r)+query( x*2+1, l, r);}int main() {    freopen("1665.in","r",stdin);    freopen("1665.out","w",stdout);    scanf("%d %d", &N, &M);    scanf("\n");    for( int i=1; i<=N; i++) scanf("%c", &a[i]);    build(1, 1, N);     for( int i=1; i<=M ;i++) {        int aa, bb;        scanf("%d %d", &aa, &bb);        printf("%lld\n", query(1, aa, bb));        update(1, aa, bb, 1);    }    return 0;   }

1667 发型

有n头奶牛,从左往右排,每头奶牛都有一个高度,最左边的奶牛往右看,只能看到比它矮的奶牛。求最左边的奶牛所能看到的奶牛数量。

分析

这题从题目可以看出数据具有单调性:往右看右边一旦出现第一头奶牛比它高,无论后面的奶牛高度是多少,肯定也看不到那些奶牛。根据它的单调性及数据范围,我们可以想到二分的方法。二分一头奶牛的位置,如果从最左边的奶牛到这头奶牛的区间内,如果最高奶牛的高度小于最左边奶牛的高度,就证明还可以看到更多的奶牛,就把区间缩小到mid-R,反之则缩小到L-mid。求区间内的最大值,我们很自然地可以想到线段树,做一遍线段树,判断一下,修改区间就解决这题了。

时间复杂度O(n log n log n):

#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>#include <iostream>using namespace std;const int INF=2100083647;struct node{    int l, r;    long long max;}tree[400010];int n;long long ans;long long a[100010];void buildTree(int x,int l,int r){    tree[x].l=l, tree[x].r=r;    if( l==r) {  tree[x].max=a[l]; return ; }    buildTree(  x*2 ,     l      , (l+r)/2);    buildTree( x*2+1, 1+((l+r)/2),     r  );    tree[x].max=max(tree[x*2].max,tree[x*2+1].max) ;}long long query( int x, int l, int r) {    if( tree[x].l >=l && tree[x].r <=r) return tree[x].max;      if( tree[x].l > r || tree[x].r < l) return -INF;               return max(query(x*2, l, r),query( x*2+1, l, r));}int erfen(int x) {    int l=0, r=n+1;    while(l+1<r) {        int mid=(l+r)/2;        if(query(1,x+1,mid)>=a[x]) r=mid;        else l=mid;    }    return l;}int main() {    freopen("1667.in","r",stdin);    freopen("1667.out","w",stdout);    scanf("%d", &n);    for(int i=1; i<=n; i++) scanf("%lld", &a[i]);    buildTree( 1, 1, n);    for( int i=1; i<=n; i++) {        ans+=erfen(i)-i;    }    printf("%lld", ans);    return 0;}

1668垃圾桶

本题采用的是动态规划+线段树求最小值的算法,先将每个区间按开始点排序,再使用动态规划。使用f[i]表示清洁前i个垃圾桶所需最小费用maps[i]和mapt[i]表示以i结尾的清洁工清洁的区间。可以推出状态转移方程:f[i]=min (每个在maps[i]和mapt[i]之间结尾的区间+所需费用,f[i])

下面贴上部分代码:

int main(){    freopen ("1668.in","r",stdin);    freopen ("1668.out","w",stdout);    long long m,l,r;    cin>>m>>l>>r;    r=r-l+1;    for (int i=1; i<=m; i++)    {        cin>>stw[i].s>>stw[i].t>>stw[i].w;        stw[i].s-=l-1;        stw[i].t-=l-1;    }    sort (stw+1,stw+m+1,xx);    l=1;    build (1,0,r);    for (int i=0; i<=300000; i++)    {        maps[i]=m+1;        mapt[i]=0;    }    for (int i=m; i; i--) maps[stw[i].t]=i;        //头    for (int i=1; i<=m; i++) mapt[stw[i].t]=i;    //尾    update (1,0,f[0]=0);    for (int i=1; i<=r; i++)    {        f[i]=INF;        for (int j=maps[i]; j<=mapt[i]; j++)        f[i]=min (querymi (1,stw[j].s-1,i-1) + stw[j].w,f[i]);//线段树询问最小值        update (1,i,f[i]);    //修改    }    if (f[r]==INF) cout<<-1;    else cout<<f[r];    return 0;}

1666 售票系统

某次列车途经C个城市,城市编号依次为1到C,列车上共有S个座位,每一个售票申请包含三个参数, O为起点,D为目的地站,N为车票张数。售票系统对该售票申请受理或不受理的决定,只有在从O到D的区段内列车上都有N个或N个以上的空座位时该售票申请才被受理。请你写个程序,实现这个自动售票系统。

分析

求一个区间内的值的问题我们会想到线段树,因为它必须保证区段内都有那么多座位,所以我们要考虑最坏情况,即在线段树内记录该区间的最少座位数,每一次都要进行修改和维护

这里有一个坑,就是终点站不能算入区间内,就是相当于把相邻两个车站之间连一条边,把边看成线段树。简单来说就是在输入时,加一句D - -。

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<string>#include<algorithm>#include<cmath>#include<iomanip>using namespace std;long long s[60010],oo=2147483647;int C,S,R,O,D,N;struct data{    long long sum,add;}tree[4*60010];void build(long long now, long long L, long long R){    if (L==R) {tree[now].sum=s[L];}    else    {        build(now*2,L,(L+R)/2);        build(now*2+1,(L+R)/2+1,R);        tree[now].sum=min(tree[now*2].sum,tree[now*2+1].sum);    } }void down(long long root, long long L, long long R){    int LS=root*2,RS=root*2+1,mid=(L+R)/2;    tree[LS].add+=tree[root].add;     tree[RS].add+=tree[root].add;    tree[LS].sum+=tree[root].add;    tree[RS].sum+=tree[root].add;    tree[root].add=0;}void update(long long now, long long L, long long R, int x, int y, int v)    {    if (x>R || y<L) return;    if (x<=L && y>=R)     {        tree[now].add+=v; tree[now].sum+=v; return;    }    down(now,L,R);    update(now*2,L,(L+R)/2,x,y,v);    update(now*2+1,(L+R)/2+1,R,x,y,v);    tree[now].sum=min(tree[now*2].sum,tree[now*2+1].sum);}long long query(long long now, long long L, long long R, int x, int y)    {    if (x>R || y<L) return oo;    if (x<=L && y>=R) return tree[now].sum;    down(now,L,R);    return min(query(now*2,L,(L+R)/2,x,y),query(now*2+1,(L+R)/2+1,R,x,y));}int main(){    freopen("1666.in","r",stdin);    freopen("1666.out","w",stdout);    cin>>C>>S>>R;    for (int i=1; i<=C; i++) s[i]=S;    build(1,1,C);    for (int i=1; i<=R; i++)     {        scanf("%d%d%d",&O,&D,&N); D--;        if (query(1,1,C,O,D)<N) cout<<"NO"<<endl;        else        {            update(1,1,C,O,D,-N);            cout<<"YES"<<endl;        }    }       return 0;}
0 0
原创粉丝点击