• 树状数组与线段树(二)


    树状数组

    1.小朋友排队

    n 个小朋友站成一排。

    现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

    每个小朋友都有一个不高兴的程度。

    开始的时候,所有小朋友的不高兴程度都是 0

    如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k

    请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

    如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

    输入格式

    输入的第一行包含一个整数 n,表示小朋友的个数。

    第二行包含 nn 个整数 H1,H2,,Hn,分别表示每个小朋友的身高。

    输出格式

    输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

    数据范围

    1n100000
    0Hi1000000

    输入样例:

    3
    3 2 1
    

    输出样例:

    9
    

    样例解释

    首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

    解题思路:解题关键就是找到最少的交换次数,我们可能会想到冒泡排序法,他就是通过不断的交换排序来实现的,因此我们可以大胆的假设一些规律,最少的交换次数==逆序对的个数。

    对于冒泡排序来说,每次交换最多减少一个逆序数

    假设数组有k个逆序对,

    ①至少需要交换k次,因此次数>=k

    ②在冒泡排序中,每次交换(Ai,Ai+1)且Ai>Ai+1,因此必然会使逆序数减1

    综上我们得出最少的交换次数==逆序对的个数

    接下来,我们要求每一个小朋友的逆序数,然后根据前n项和公式求出他的不高兴数,在求解单个逆序数时,我们就用到树状数组来统计在该数组中,前方大于他的数和后方小于他的数,总和即为该数的逆序数。

    代码:

    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int N=1000010;
    int tr[N],h[N];
    ll con[N];
    int n;
    
    int lowbit(int x)
    {
        return x&-x;
    }
    
    void add(int x,int v)
    {
        for(int i=x;i<N;i+=lowbit(i))
            tr[i]+=v;
    }
    int query(int x)
    {
        int ans=0;
        for(int i=x;i;i-=lowbit(i))
            ans+=tr[i];
        return ans;
    }
    int main()
    {
        int i,j;
        cin>>n;
        for(i=0;i<n;i++)
        {
            cin>>h[i];
            h[i]++;
        }
        //计算前面比他大的数
        long long ans=0;
        for(i=0;i<n;i++)
        {
            con[i]=query(N-1)-query(h[i]);
            add(h[i],1);
        }
        //计算后面比它小的数
        memset(tr,0,sizeof(tr));
        for(i=n-1;i>=0;i--)
        {
            con[i]+=query(h[i]-1);
            add(h[i],1);
        }
        //总和
        for(i=0;i<n;i++)
            ans+=con[i]*(con[i]+1)/2;
        cout<<ans;
        return 0;
    }

    差分

    1.差分

    输入一个长度为n的整数序列。

    接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。

    请你输出进行完所有操作后的序列。

    输入格式

    第一行包含两个整数n和m。

    第二行包含n个整数,表示整数序列。

    接下来m行,每行包含三个整数l,r,c,表示一个操作。

    输出格式

    共一行,包含n个整数,表示最终序列。

    数据范围

    1n,m100000,
    1lrn,
    1000c1000,
    10001000

    解题思路:

    给定a[1],a[2],...a[n]构造查分数组b[N],使得

    a[i]=b[1]+b[2]+...b[i]

    核心操作:将a[L~R]全部加上C,等价于b[L]+=C,b[R+1]-=C

    1.a[1~L-1]无影响

    2.a[L~R]加上了C

    3.a[R+1~N]无影响

    #include<iostream>
    using namespace std;
    const int N=100010;
    int b[N],q[N];
    void insert(int l,int r,int c)
    {
        b[l]+=c;
        b[r+1]-=c;
    }
    int main()
    {
        int n,i,j,m,l,r,c;
        cin>>n>>m;
        for(i=1;i<=n;i++)
            cin>>q[i];
        for(i=1;i<=n;i++)
            b[i]=q[i]-q[i-1];
        while(m--)
        {
            cin>>l>>r>>c;
            insert(l,r,c);
        }
        for(i=1;i<=n;i++)
        {
            q[i]=q[i-1]+b[i];
            cout<<q[i]<<" ";
        }
        return 0;
    
    }
  • 相关阅读:
    div中子div在firefox ie 水平居中对齐
    Access数据库自定义连接字符串(详细有图百度文档)
    ASP页面乱码
    国内开源asp.netCMS汇总
    sql 2005
    卡巴斯基授权key导入方式方法及其导入key基本原理
    关于SqlServer服务无法启动的症状分析和解决方法
    js下流媒体的在线播放
    用JS+vml作三维报表
    了解嵌入式数据库(sqlite,firebird)
  • 原文地址:https://www.cnblogs.com/xiaofengzai/p/12238235.html
Copyright © 2020-2023  润新知