红黑树习题分析

  关于红黑树的介绍可参考下文

http://url.cn/52jRxDi

练习题

1.硬木种类(POJ2418)问题 ««
问题描述:硬木是植物树群,有宽阔的叶子,产生水果或坚果,并且通常在冬天休眠。美国的温带气候产生了数百种硬木树种,例如橡树、枫树和樱桃都是硬木树种,它们是不同的物种。所有硬木树种共同占美国树木的40%。
利用卫星成像技术,自然资源部编制了一份特定日期的每棵树的清单。你需要计算每个树种的总分数。
输入格式:输入包括卫星观测到的每棵树的树种清单。每行表示一棵树的树种,树种名称不超过30个字符。所有树种不超过10,000种,不超过1,000,000棵树。
输出格式:按字母顺序输出每个树种的名称,以及对应的百分比,百分比精确到第4个小数位。
输入样例:
Red Alder
Ash
Aspen
Basswood
Ash
Beech
Yellow Birch
Ash
Cherry
Cottonwood
Ash
Cypress
Red Elm
Gum
Hackberry
White Oak
Hickory
Pecan
Hard Maple
White Oak
Soft Maple
Red Oak
Red Oak
White Oak
Poplan
Sassafras
Sycamore
Black Walnut
Willow
输出样例:
Ash 13.7931
Aspen 3.4483
Basswood 3.4483
Beech 3.4483
Black Walnut 3.4483
Cherry 3.4483
Cottonwood 3.4483
Cypress 3.4483
Gum 3.4483
Hackberry 3.4483
Hard Maple 3.4483
Hickory 3.4483
Pecan 3.4483
Poplan 3.4483
Red Alder 3.4483
Red Elm 3.4483
Red Oak 6.8966
Sassafras 3.4483
Soft Maple 3.4483
Sycamore 3.4483
White Oak 10.3448
Willow 3.4483
Yellow Birch 3.4483

解题思路:

用map<string,int>容器mymap累计每个树种出现的次数,插入所有的树种字符串,正向遍历mymap求解。对应的AC程序如下:

#include<iostream>
#include<map>
#include<string>
#define MAXN 35
using namespace std;
int n=0;
map<string,int> mymap;
void solve()                        //求解函数
{    map<string,int>::iterator it;
     for (it=mymap.begin();it!=mymap.end();it++)
     {    cout << it->first << " ";
         printf("%.4f\n",it->second*100.0/n);
     }
}
int main()
{    char s[MAXN];
     while(gets(s))
    {    n++;
         mymap[s]++;
    }
    solve();
     return 0;
}

 2.营业额统计问题 «««

问题描述:公司的账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
(1)该天的最小波动值= min{|该天以前某天的营业额-当天的营业额|}
(2)当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序来计算这个值。第一天的最小波动值为第一天的营业额。
输入格式:输入文件第一行为正整数 n(n≤1000000),表示该公司从成立一直到现在的天数,接下来的n行每行有一个正整数,表示第i天公司的营业额。
输出格式:输出文件仅有一个正整数,为所有天的最小波动值和。
输入样例:
6
5
1
2
5
4
6
输出样例:
12

解题思路:

本题是输入每天的营业额,当输入第i天的营业额x时,求出之前所有营业额中与x最接近的营业额,两者差的绝对值就是该天的最小波动值,需要累计所有这样的最小波动值。
显然当x与之前某个营业额相同时,该天的最小波动值为0,所以可以不必存放重复的营业额,为此采用set容器mys存放每天的营业额。这样该问题就转换为在mys中求x的最接近的元素问题,采用例4.4的思路求解。对应的程序如下:

#include<iostream>
#include<set>
using namespace std;
int ans;                                   //存放求解结果
set<int> mys;
set<int>::iterator it;
int main()
{    int n,x;
     while(cin >> n)
     {    mys.clear();
        scanf("%d",&x);                  //输入第一天的营业额 
        int ans=x; 
        mys.insert(x);
        for(int i=1;i<n;i++)             //输入并处理其他天的营业额
         {    scanf("%d",&x);
              it=mys.find(x);
              if (it!=mys.end())          //找到x,即是重复元素,最小波动值为0
                   continue;
              it=mys.lower_bound(x);      //查找x的上界 
              if (it==mys.begin())        //上界为首元素,即x小于以前所有营业额 
              {    ans += *it-x;          //累计该天的最小波动值
                   mys.insert(x);         //将x插入到mys 
                   continue; 
              }
              if (it==mys.end())          //没有找到上界,即x大于以前所有营业额
              {    it--;                  //it指向尾元素
                   ans += x- *it;         //累计该天的最小波动值
                   mys.insert(x);         //将x插入到mys 
                   continue;
              } 
              int y=*it;                  //其他情况,求y为x的上界
              it--;
              int z=*it;                  //z为x的下界
              if(y-x>x-z)                 //求最接近x的营业额差
                   ans += x-z;            //累计该天的最小波动值
              else
                   ans += y-x;
              mys.insert(x);              //将x插入到mys
         }
        cout << ans << endl;             //输出结果
    }
}

3.Holedox吃蛋糕(HDU4302)问题 ««««
问题描述:Holedox是一种小动物,可以看成一个点。它生活在一条长度为L的直管中,Holedox只能沿管道移动。蛋糕可能会随时出现在管道的任何地方。当Holedox想要吃蛋糕时,它总是去吃距离最近的一个蛋糕。如果有多片在不同方向上Holedox都可以选择的蛋糕,Holedox会选择最近移动的方向。如果没有蛋糕,Holedox就会停留在原处。
输入格式:输入包含几个测试用例。输入的第一行包含一个整数T(1≤T≤10),表示测试用例的数量,然后是每个测试用例的输入数据。每个测试用例的第一行包含两个整数L,n( 1≤L,n≤100000),分别表示管道的长度和事件的数量。下来的n行,每行描述一个事件:
(1)0 x:表示一块蛋糕出现在x位置(0≤x≤L,x是整数)。
(2)1:代表Holedox想吃蛋糕。
在每个测试用例中,Holedox始终从0位置开始。
输出格式:输出Holedox将移动的总距离。Holedox不需要返回0位置。
输入样例:
3
10 8
0 1
0 5
1
0 2
0 0
1
1
1

10 7
0 1
0 5
1
0 2
0 0
1
1

10 8
0 1
0 1
0 5
1
0 2
0 0
1
1
输出样例:
Case 1: 9
Case 2: 4
Case 3: 2

解题思路:

从样例看出蛋糕坐标可能重复,为此用multiset容器mys存放当前输入的蛋糕坐标,now表示Holedox动物当前坐标(初始为0),ans累计Holedox吃蛋糕移动的总距离(初始为0)。way表示最近移动的方向(向左为0,向右为1)。实际上该问题与上一个示例类似,也是在mys中求最接近元素问题,只是需要考虑方向,所以要复杂一些。
对于op=1的查询,将x直接插入的mys中,注意mys中会按坐标自动递增排列。
对于op=0的查询,从now位置开始,求出mys中第一个大于等于now的位置it(now坐标的上界):
(1)若it为mys.begin()或者mys.end()时需要特别考虑。若it位置在蛋糕起点,肯定是直接吃it位置的蛋糕,考虑方向如下:
  若now就在it位置,则吃it位置的蛋糕,但方向不变。
  否则向右移动吃it位置的蛋糕,way改为1。
若it为mys.end(),说明now的右边没有蛋糕,it减1指向最右的蛋糕,此时只能向左吃最右的蛋糕,方向改为向左,即置way=0。
(2)若it为蛋糕的中间位置,说明至少有两片蛋糕(因为it不等于mys.begin()同时又存在now右边的蛋糕),那么只需要在now坐标前后选一个最近蛋糕。显然[now..*it]之间不可能有除it外的其他蛋糕,求出it位置前面的一片蛋糕位置it1,它在now坐标的左边,再在it1和it中选择一片最近的蛋糕,在吃蛋糕移动时需要考虑方向,同一位置吃蛋糕方向不变,其他位置吃蛋糕需要考虑左右移动来修改方向。

 对应的AC程序如下:
#include <iostream>
#include <set>
#include<algorithm>
using namespace std;
void solve(int cas)                     //求解第cas个测试用例 
{
    multiset<int> mys;                        //重复当前为止的蛋糕位置 
    multiset<int>::iterator it, ti;
    int L, n;
    int op, x;
    int now = 0;                                //Holedox动物当前的位置 
    int l, r, way = 0, ans = 0;
    cin >> L >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> op;
        if (op == 0)                            //处理0 x事件 
        {
            cin >> x;
            if (x <= L)
                mys.insert(x);              //插入到mys 
            continue;
        }
        if (mys.empty())                      //开始处理查询:mys为空的情况 
        {
            way = 0;                           //初始起点为0 
            continue;
        }
        it = mys.lower_bound(now);             //求第一个大于等于now的位置it 
        if (it == mys.end() || it == mys.begin()) //it位置在蛋糕两端的情况 
        {
            if (it == mys.begin())              //it位置在蛋糕起点
            {
                if (*it == now) way = way;       //now在it位置,吃起点的蛋糕,方向不变
                else way = 1;                 //向右吃起点的蛋糕 
            }
            else                             //now的右边没有蛋糕
            { it--;                       //it指向最右蛋糕 
                 way = 0;                      //向左吃最右蛋糕
            }
            ans += abs(*it - now);               //从now位置到it位置吃蛋糕
            now = *it;
            mys.erase(it);
        }
        else                                 //it位置在蛋糕中间的情况
        {
            r = *it;                           //r为刚好大于等于now的坐标
            ti = it;
            it--;                            //it减1指向now位置左边的蛋糕
            l = *it;
            l = abs(now - l);                    //修改l为now左边蛋糕到now的距离
            r = abs(now - r);                    //r为it位置蛋糕到now的距离
            if (l > r || l == r && way == 1)        //满足条件,吃it位置的蛋糕
            {
                ans += r;
                if (r == 0) way = way;           //距离为0,方向不变
                else way = 1;                 //否则,方向改为向右
                now = *ti;
                mys.erase(it);
            }
        }
    }
    cout << "Case " << cas << ": " << ans << endl;
}
int main()
{
    int t, cas = 1;
    cin >> t;
    while (t--)
        solve(cas++);
    return 0;
}

4.畅通工程(HDU1232)问题 ««
问题描述:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入格式:测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N(N< 1000)和道路数目M,随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。注意两个城市之间可以有多条道路相通,也就是说:
3 3
1 2
1 2
2 1
这种输入也是合法的。当N为0时,输入结束,该用例不被处理。
输出格式:对每个测试用例,在一行里输出最少还需要建设的道路数目。
输入样例:
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
输出样例:
1
0
2
998

解题思路:

该问题属于问题类型Ⅰ。要使全省任何两个城镇间都实现交通,最少的道路是所有城镇之间都有一条路径,即全部城镇构成一棵树。采用并查集来求解,等价关系就是道路相连关系,由输入构造并查集,每棵子树中的所有城镇是有路径的,求出其中子树的个数ans,那么最少还需要建设的道路数就是ans-1。对应的程序如下:

#include <iostream>
using namespace std;
#define MAXN 1005
int father[MAXN];                         //并查集存储结构
int rank[MAXN];                           //存储结点的秩
int n;                                    //n个城镇 
int m;                                    //m条道路
void Init()                         //初始化 
{    for (int i=1;i<=n;i++)
     {    father[i]=i;
         rank[i]=0;
     }
}
int Find(int x)                     //查找x结点的根结点 
{    if(x!=father[x])
         father[x]= Find(father[x]);      //路径压缩 
     return father[x];
}
void Union(int x,int y)                 //x和y的两个集合的合并
{    int rx=Find(x);
     int ry=Find(y);
     if (rx==ry)                          //x和y属于同一棵树的情况 
         return;
     if(rank[rx]<rank[ry])
         father[rx]=ry;                   //rx结点作为ry的孩子 
     else
     {    if(rank[rx]==rank[ry])           //秩相同,合并后rx的秩增1
              rank[rx]++;
         father[ry]=rx;                   //ry结点作为rx的孩子  
     }
}
int main()
{    while(scanf("%d%d",&n,&m) && n)      //输入一个测试用例 
     {    Init();
         int a,b;
         for(int i=1;i<=m;i++)
         {    scanf("%d%d",&a,&b);
              Union(a,b);
         }
         int ans=0;
         for (int i=1;i<=n;i++)           //求子树个数
              if (father[i]==i)
                   ans++;
         printf("%d\n",ans-1);
     }        
     return 0;
}

猜你喜欢

转载自www.cnblogs.com/jiangxiaoju/p/10321493.html