2017年第八届蓝桥杯C组国赛

一.数位和

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

数学家高斯很小的时候就天分过人。一次老师指定的算数题目是:

1+2+...+100。

高斯立即做出答案:5050!

这次你的任务是类似的。但并非是把一个个的数字加起来,而是对该数字的每一个数位作累加。

这样从 1 加到 100 的“和”是:901;

从 10 加到 15 是:21,也就是:1+0+1+1+1+2+1+3+1+4+1+5,这个口算都可以出结果的。

按这样的“加法”,从 1 加到 1000 是多少呢?

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:枚举+数位计算,水题。

#include <iostream>
using namespace std;
int main()
{
  int sum = 0, temp, i;
  for(i = 1; i <= 1000; ++ i)
  {
    temp = i;
    while(temp)
    {
      sum += temp % 10;
      temp /= 10;
    }
  }
  cout << sum;
  return 0;
}

二.数字划分

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

W星球的长老交给小明一个任务: 1,2,3⋯16 这 16 个数字分为两组。

要求:这两组数字的和相同,并且,两组数字的平方和也相同,并且,两组数字的立方和也相同。

请你利用计算机的强大搜索能力解决这个问题。 并输出 1 所在的那个分组的所有数字。

这些数字要从小到大排列,两个数字间用一个空格分开。 即类似:1 4 5 8... 这样的答案。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:DFS+减枝。

#include <iostream>
#include <vector>
using namespace std;

bool DFS(int start, vector<int>& ans, int s, int ss, int sss, int& half_s, int& half_ss, int& half_sss)
{
  if(s > half_s || ss > half_ss || sss > half_sss)
  {
    return false;
  }
  if(start == 17)
  {
    if(s == half_s && ss == half_ss && sss == half_sss)
    {
      for(int i = 0; i < ans.size(); ++ i)
      {
        cout << ans[i] << " ";
      }
      cout << endl;
      return true;
    }
    return false;
  }
  for(int i = start; i <= 16; ++ i)
  {
    if(DFS(i + 1, ans, s, ss, sss, half_s, half_ss, half_sss))
    {
      return true;
    }
    ans.push_back(i);
    if(DFS(i + 1, ans, s + i, ss + i * i, sss + i * i * i, half_s, half_ss, half_sss))
    {
      return true;
    }
    ans.pop_back();
  }
  return false;
}

int main()
{
  int half_s = 0, half_ss = 0, half_sss = 0;
  for(int i = 1; i <= 16; ++ i)
  {
    half_s += i;
    half_ss += i * i;
    half_sss += i * i * i;
  }
  half_s >>= 1;
  half_ss >>= 1;
  half_sss >>= 1;
  vector<int> ans;
  ans.push_back(1);
  DFS(2, ans, 1, 1, 1, half_s, half_ss, half_sss);
  return 0;
}

三.树形显示

题目描述

本题为代码补全填空题,请将题目中给出的源代码补全,并复制到右侧代码框中,选择对应的编译语言(C/Java)后进行提交。若题目中给出的源代码语言不唯一,则只需选择其一进行补全提交即可。复制后需将源代码中填空部分的下划线删掉,填上你的答案。提交后若未能通过,除考虑填空部分出错外,还需注意是否因在复制后有改动非填空部分产生错误。

对于分类结构可以用树形来形象地表示。比如:文件系统就是典型的例子。

树中的结节具有父子关系。我们在显示的时候,把子项向右缩进(用空格,不是tab),并添加必要的连接线,以使其层次关系更醒目。

图片描述

下面的代码就是为了这个目的的,请仔细阅读源码,并填写划线部分缺少的代码。

源代码

Java

import java.util.*;
class MyTree
{
    private Map<String, List<String>>  map_ch = new HashMap<String, List<String>>();
    private Map<String,String> map_pa = new HashMap<String,String>();
    
    //增加父子级关系
    public void add(String parent, String child)
    {
        map_pa.put(child, parent);
        
        List<String> lst = map_ch.get(parent);
        if(lst==null){
            lst = new ArrayList<String>();
            map_ch.put(parent, lst);
        }
        lst.add(child);
    }
    
    //拿到父亲节点
    public String get_parent(String me){
        return map_pa.get(me);
    }

    //拿到孩子节点    
    public List<String> get_child(String me){
        return map_ch.get(me);
    }

    //生成空格    
    private String space(int n)
    {
        String s = "";
        for(int i=0; i<n; i++) s += '.';
        return s;
    }
    
    //判断x是否是所属子树的最后一个节点
    private boolean last_child(String x){
        String pa = map_pa.get(x);
        if(pa==null) return true;
        
        List<String> lst = map_ch.get(pa);
        return lst.get(lst.size()-1).equals(x);
    }
    
    public void show(String x){
        
        String s = "+--" + x;
        
        String pa = x;
        while(true){
            pa = map_pa.get(pa);
            if(pa==null) break;
            s = ___________________________________;
        }
        
        System.out.println(s);
    }
    
    public void dfs(String x){
        show(x);
        
        List<String> lst = map_ch.get(x);
        if(lst==null) return;
                
        for(String it: lst){
            dfs(it);
        }
    }
}

public class Main
{
    public static void main(String[] args)
    {
        MyTree tree = new MyTree();
        tree.add("root", "dog");
        tree.add("root", "cat");
        tree.add("root", "duck");
        tree.add("dog", "AAdog");
        tree.add("dog", "BBdog");
        tree.add("dog", "CCdog");
        tree.add("AAdog", "AAdog01");
        tree.add("AAdog", "AAdog02");
        tree.add("cat", "XXcat");
        tree.add("cat", "YYcat");
        tree.add("XXcat","XXcat-oo");
        tree.add("XXcat","XXcat-qq");
        tree.add("XXcat-qq", "XXcat-qq-hahah");
        tree.add("duck", "TTduck");
        tree.add("TTduck", "TTduck-001");
        tree.add("TTduck", "TTduck-002");
        tree.add("TTduck", "TTduck-003");
        tree.add("YYcat","YYcat.hello");
        tree.add("YYcat","YYcat.yes");
        tree.add("YYcat","YYcat.me");        
        
        tree.dfs("root");
    }
}

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:

注意:本题样例和实际有冲突,space函数本来应是生成空格,但是代码中是用'.'表示空格

1.看命令行输出可以发现,本题中的add函数就是用来增加父子级关系的。

2.注释掉填空部分会发现,所有节点全部都是顶格输出,因此我们缺少对空格的输出。观察命令行输出可以发现,每四个空格表示一级关系。代码中的space函数就是用来生成n个空格的。

s = space(4) + s; // 填空

3.别问为啥,俺也没搞懂,这样例真的迷。。。答案如下:

import java.util.*;
class MyTree
{
    // 父节点 -- 子节点列表
    private Map<String, List<String>>  map_ch = new HashMap<String, List<String>>();
    // 子节点 -- 父节点
    private Map<String,String> map_pa = new HashMap<String,String>();
    
    // 增加 父 子 关系
    public void add(String parent, String child)
    {
        map_pa.put(child, parent);
        
        List<String> lst = map_ch.get(parent);
        if(lst==null){
            lst = new ArrayList<String>();
            map_ch.put(parent, lst);
        }
        lst.add(child);
    }
    
    // 拿到父节点
    public String get_parent(String me){
        return map_pa.get(me);
    }

    // 拿到子节点
    public List<String> get_child(String me){
        return map_ch.get(me);
    }
    
    //生成......
    private String space(int n)
    {
        String s = "";
        for(int i=0; i<n; i++) s += '.';
        return s;
    }
    
    //判断x是否是其父亲的最后一个节点
    private boolean last_child(String x){
        String pa = map_pa.get(x);
        if(pa==null) return true;
        
        List<String> lst = map_ch.get(pa);
        return lst.get(lst.size()-1).equals(x);
    }
    
    //显示x
    public void show(String x){
        
        String s = "+--" + x;
        
        String pa = x;
        while(true){// 判断节点x的深度
            pa = map_pa.get(pa);
            if(pa==null) break;
            s = (last_child(pa)? "." : "|") + space(4) + s; // 填空
        }
        
        System.out.println(s);
    }
    
    //遍历x
    public void dfs(String x){
        show(x);
        
        List<String> lst = map_ch.get(x);
        if(lst==null) return;
                
        for(String it: lst){
            dfs(it);
        }
    }
}

public class Main
{
    public static void main(String[] args)
    {
        MyTree tree = new MyTree();
        tree.add("root", "dog");
        tree.add("root", "cat");
        tree.add("root", "duck");
        tree.add("dog", "AAdog");
        tree.add("dog", "BBdog");
        tree.add("dog", "CCdog");
        tree.add("AAdog", "AAdog01");
        tree.add("AAdog", "AAdog02");
        tree.add("cat", "XXcat");
        tree.add("cat", "YYcat");
        tree.add("XXcat","XXcat-oo");
        tree.add("XXcat","XXcat-qq");
        tree.add("XXcat-qq", "XXcat-qq-hahah");
        tree.add("duck", "TTduck");
        tree.add("TTduck", "TTduck-001");
        tree.add("TTduck", "TTduck-002");
        tree.add("TTduck", "TTduck-003");
        tree.add("YYcat","YYcat.hello");
        tree.add("YYcat","YYcat.yes");
        tree.add("YYcat","YYcat.me");        
        
        tree.dfs("root");
    }
}

四.小数第n位

题目描述

我们知道,整数做除法时,有时得到有限小数,有时得到无限循环小数。

如果我们把有限小数的末尾加上无限多个 0,它们就有了统一的形式。

本题的任务是:在上面的约定下,求整数除法小数点后的第 n 位开始的 3 位数。

输入描述

输入一行三个整数:a b n,用空格分开。a 是被除数,b 是除数,n 是所求的小数后位置(0<a,b,n<10^9)

输出描述

输出一行 3 位数字,表示:a 除以 b,小数后第 n 位开始的 3 位数字。

输入输出样例

示例

输入

1 8 1

输出

125

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:朴素的想法无非就是模拟竖式计算。

#include <iostream>
using namespace std;
int main()
{
  int a, b, n;
  cin >> a >> b >> n;
  a %= b;
  while(n >= 2)
  {
    -- n;
    a *= 10;
    a %= b;
  }
  for(int i = 1; i <= 3; ++ i)
  {
    a *= 10;
    cout << a / b;
    a %= b;
  }
  return 0;
}

本题的数据规模高达10^9,直接模拟很容易超时。回到模拟本身,我们之所以要模拟是因为本题数据的位数会很高,我们是没有办法直接存储下来并做运算的。对于循环小数,只要我们获得了循环节就可以很快解决,但这种方法并不能处理不循环小数。我们并不是只能1位1位手动计算,我们可以一次计算多位,再用1位进行微调。

例如计算1/7的小数点后第13位,我们可以先将被除数*10变为1000000000/7=1428571428(相当于把前十位小数移动到小数点前面),于是被除数变为1000000000%7=4,接下来就是再除3次了。

这样我们就可以将复杂度从O(10^9)降低为O(10^8)。

#include <iostream>
using namespace std;
int main()
{
  long long a, b, n;
  const long long mul = 1e10;
  cin >> a >> b >> n;
  a %= b;
  while(n > 10)
  {
    n -= 10;
    a *= mul;
    a %= b;
  }
  while(n > 1)
  {
    n --;
    a *= 10;
    a %= b;
  }
  for(int i = 1; i <= 3; ++ i)
  {
    a *= 10;
    cout << a / b;
    a %= b;
  }
  return 0;
}

五.分考场

题目描述

n 个人参加某项特殊考试。

为了公平,要求任何两个认识的人不能分在同一个考场。

求是少需要分几个考场才能满足条件。

输入描述

输入格式:

第一行,一个整数 n (1≤n≤100),表示参加考试的人数。

第二行,一个整数 m,表示接下来有 m 行数据。

以下 m 行每行的格式为:两个整数 a,b,用空格分开 (1≤a,b≤n )表示第 a 个人与第 b 个人认识。

输出描述

输出一行一个整数,表示最少分几个考场。

输入输出样例

示例

输入

5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5

输出

4

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:

[误区]对认识关系建模,用关系边表示,于是可以用并查集对认识关系进行维护。最终利用并查集求出认识关系的最大集合,其大小(最多互相认识的人)就是答案。

这种错误在于,1-3认识,1-2认识不代表2-3认识,而如果用并查集则默认2-3认识,与题意不符。

思路:本题实际上是个染色问题,将数据建成图之后,要求相邻节点之间不能用重复的颜色标识,求最少的颜色数。

可以用 二分答案 + DFS染色判断。

邻接矩阵版:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int adjRect[N][N], n;
int Color[N];

bool checkColor(int p, int c)
{
    for (int i = 1; i <= n; ++i)
    {
        if (adjRect[p][i] && c == Color[i])
        {
            return false;
        }
    }
    return true;
}

bool DFS(int p, int color, int colorNum)
{
    int i, c;
    for (i = 1; i <= n; ++i)
    {
        if (p == i || adjRect[i][p] == 0 || Color[i])
        {
            continue;
        }
        for (c = 1; c <= colorNum; ++c)
        {
            if (c != color && checkColor(i, c))
            {
                Color[i] = c;
                if (DFS(i, c, colorNum))
                {
                    break;
                }
        Color[i] = 0;
            }
            if (c == colorNum)
            {
                return false;
            }
        }
    }
    return true;
}

bool check(int colorNum)
{
    memset(Color, 0, sizeof(Color));
    for (int i = 1; i <= n; ++i)
    {
        if (!Color[i])
        {
            Color[i] = 1;
            if (!DFS(i, 1, colorNum))
            {
                return false;
            }
        }
    }
    return true;
}

int main()
{
    int m, i, a, b, L, R, M, ans;
    cin >> n >> m;
    L = 1;
    R = n;
    for (i = 1; i <= m; ++i)
    {
        cin >> a >> b;
        adjRect[a][b] = adjRect[b][a] = 1;
    }
    while (L <= R)
    {
        M = (L + R) >> 1;
        if (check(M))
        {
            ans = M;
            R = M - 1;
        }
        else
        {
            L = M + 1;
        }
    }
    cout << ans;
    return 0;
}

链式前向星版(邻接表):

#include <iostream>
#include <cstring>
using namespace std;
struct Edge{
  int to, next;
};

Edge edges[10010];
int cnt, Head[110], Color[110];

void add(int from, int to)
{
  edges[++ cnt].to = to;
  edges[cnt].next = Head[from];
  Head[from] = cnt;
}

bool checkColor(int p, int color)
{
  int e, to;
  for(e = Head[p]; e != 0; e = edges[e].next)
  {
    to = edges[e].to;
    if(color == Color[to])
    {
      return false;
    }
  }
  return true;
}

bool DFS(int p, int color, int colorNum)
{
  int to, e, c;
  for(e = Head[p]; e != 0; e = edges[e].next)
  {
    to = edges[e].to;
    if(!Color[to]) // 该邻接点还未上色
    {
      for(c = 1; c <= colorNum; ++ c)
      {
        if(c != color && checkColor(to, c)) // 检查to上色的合法性
        {
          Color[to] = c;
          if(DFS(to, c, colorNum))
          {
            break;
          }
          Color[to] = 0;
        }
        if(c == colorNum) // 找不到合法的上色方案
        {
          return false;
        }
      }
    }
  }
  return true;
}

bool check(int colorNum, int n)
{
  memset(Color, 0, sizeof(Color));
  for(int i = 1; i <= n; ++ i)
  {
    if(!Color[i])
    {
      Color[i] = 1;
      if(!DFS(i, 1, colorNum))
      {
        return false;
      }
    }
  }
  return true;
}

int main()
{
  int n, m, i, a, b, L, R, M, ans;
  cin >> n >> m;
  for(i = 1; i <= m; ++ i)
  {
    cin >> a >> b;
    add(a, b);
    add(b, a);
  }
  L = 1, R = n;
  while(L <= R)
  {
    M = (L + R) >> 1;
    if(check(M, n))
    {
      ans = M;
      R = M - 1;
    }
    else
    {
      L = M + 1;
    }
  }
  cout << ans;
}

由于颜色会很多,用DFS染色法直接判断会很费时间,因此上述代码会超时。还有一种在染色过程中及时动态增加颜色种数的方案。

思路:我们对每一个学生枚举要放置到哪一个教室,DFS+减枝。

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 110;
//    邻接矩阵                            教室与其安排的学生  教室里已有的学生人数
int acquaintances[N][N], ans = 0x3f3f3f3f, student[N][N], classCapability[N], n;

void DFS(int stu, int classNum)// 当前要安排的学生 当前的教室数量
{
	if (classNum >= ans)//当前安排的教室数量大于等于已有的答案,不需要再考虑了 减枝
	{
		return;
	}
	if (stu == n + 1)// 学生安排完了
	{
		ans = min(ans, classNum);//更新教室使用的个数
		return;
	}
	int classId, stuId;
	for (classId = 1; classId <= classNum; ++classId)
	{
		for (stuId = classCapability[classId]; stuId >= 1; --stuId)
		{
			if (acquaintances[stu][student[classId][stuId]])//冲突,换一间教室
			{
				break;
			}
			if (stuId == 1)//和这个教室里的学生不冲突
			{
				++classCapability[classId];
				student[classId][classCapability[classId]] = stu;
				DFS(stu + 1, classNum);
				--classCapability[classId];
			}
		}
	}
	//还可以为这个学生放置到另一间空教室
	//贪心(把当前不冲突的放进集合)不一定能得到最优解,原因在于,可能存在很多j>i,j与i不冲突,但与k<i冲突,此时如果把i与k放在一个集合,那么j只能令开集合。但是如果把i单独成集合,接下来就可以往这个集合里放j。所以必须考虑i单独开一个集合的情况
	++classCapability[classNum + 1];
  student[classNum + 1][classCapability[classNum + 1]] = stu;
	DFS(stu + 1, classNum + 1);
	--classCapability[classNum + 1];
}

int main()
{
	int m, a, b, i;
  scanf("%d%d", &n, &m);
	for (i = 1; i <= m; ++i)
	{
		scanf("%d%d", &a, &b);
		acquaintances[a][b] = acquaintances[b][a] = 1;
	}
	DFS(1, 0);
	printf("%d",ans);
	return 0;
}

六.合根植物

题目描述

w 星球的一个种植园,被分成 m×n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。

这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入描述

第一行,两个整数 m,n,用空格分开,表示格子的行数、列数(1≤m,n≤1000)。

接下来一行,一个整数 k(0≤k≤10^5 ),表示下面还有 kk 行数据。

接下来 k 行,每行两个整数 a,b,表示编号为 a 的小格子和编号为 b 的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。

比如:5×4 的小格子,编号:

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20

输出描述

输出植物数量。

输入输出样例

示例

输入

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

输出

5

样例说明

其合根情况参考下图:

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

思路:并查集裸题。。。不过需要将二维坐标转成一维索引再套并查集模板。

#include <iostream>
#include <cstring>
using namespace std;

int father[1000010];

int Find(int x)
{
  return father[x] != x ? father[x] = Find(father[x]) : x;
}

void Union(int A, int B, int& ans)
{
  A = Find(A);
  B = Find(B);
  if(A != B)
  {
    father[A] = B;
    -- ans;
  }
}

int main()
{
  int m, n, i, u, v, ans;
  cin >> m >> n;
  ans = n = n * m;
  for(i = 1; i <= n; ++ i)
  {
    father[i] = i;
  }
  cin >> m;
  for(i = 1; i <= m; ++ i)
  {
    cin >> u >> v;
    Union(u, v, ans);
  }
  cout << ans;
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值