蓝桥杯B组C++决赛历届真题代码整理


ps:进决赛了,之前的省赛题暂时停止更新,到决赛前就刷决赛的题目了。O(∩_∩)O

第六届决赛真题

积分之迷

小明开了个网上商店,卖风铃。共有3个品牌:A,B,C。
为了促销,每件商品都会返固定的积分。

小明开业第一天收到了三笔订单:
第一笔:3个A + 7个B + 1个C,共返积分:315
第二笔:4个A + 10个B + 1个C,共返积分:420
第三笔:A + B + C,共返积分…

你能算出第三笔订单需要返积分多少吗?

请提交该整数,不要填写任何多余的内容。

解:暴力枚举每一个值。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 9;

int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);

    for(int A = 0; A <= 105; A++)
      for(int B = 0; B <= 45; B++)
        for(int C = 0; C <= 420; C++)
    {
      if(3*A + 7*B + C == 315 && 4*A + 10*B + C == 420)
      {
        cout << "A + B + C = " << A + B + C << endl;
        return 0;
      }
    }

    //return 0;
}

完美正方形

如果一些边长互不相同的正方形,可以恰好拼出一个更大的正方形,则称其为完美正方形。

历史上,人们花了很久才找到了若干完美正方形。比如:如下边长的22个正方形
2 3 4 6 7 8 12 13 14 15 16 17 18 21 22 23 24 26 27 28 50 60
如下图那样组合,就是一种解法。此时,
紧贴上边沿的是:60 50
紧贴下边沿的是:26 28 17 21 18

22阶完美正方形一共有8种。下面的组合是另一种:
2 5 9 11 16 17 19 21 22 24 26 30 31 33 35 36 41 46 47 50 52 61
如果告诉你该方案紧贴着上边沿的是从左到右依次为:47 46 61,
你能计算出紧贴着下边沿的是哪几个正方形吗?

请提交紧贴着下边沿的正方形的边长,从左到右,用空格分开。

不要填写任何多余的内容或说明文字。
在这里插入图片描述
解:确定边长为154 * 154 的正方形后,按照行优先的顺序依次遍历每个点。如果该点已经被染色,那么直接跳过该点;否则就在已知的边长中寻找一种可行的方案;如果找不到可行的方案,则回溯更改之前的方案。直到遍历完正方形后,就是最终的答案。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 155;
int color[maxn][maxn];
int flag = 0;
int number[23] = {2, 5, 9, 11, 16, 17, 19, 21, 22, 24, 26, 30, 31, 33, 35, 36, 41, 50, 52, 47, 46, 61, -1};
bool vis[22];
int index = 0;
bool judge(int x, int y, int index)
{

  for(int i = x; i < x+number[index]; i++)
    for(int j = y; j < y+number[index]; j++)
      if(color[i][j] != -1)
        return false;

  return true;
}

void paint(int x, int y, int index, int num)
{
    for(int i = x; i < x+num; i++)
      for(int j = y; j < y+num; j++)
          color[i][j] = number[index];
}

void dfs(int x, int y)
{

  if(y == 155)
  {
    dfs(x+1, 1);
    return ;
  }
  if(x == 155)
  {

    int length = 1;
    for(int i = 1; i < maxn-1; i++)
    {
      if(color[154][i+1] != color[154][i])
      {
        cout << length << " " << endl;
        length = 1;
      }
      else
        length++;
    }
    cout << length << " " << endl;
    return ;
  }

  if(color[x][y] == -1)
  {
    for(int i = 0; i < 22; i++)
    {
      if(!vis[i] && judge(x, y, i))
      {
        vis[i] = true;
        paint(x, y, i, number[i]);
        dfs(x, y+1);
        paint(x, y, 22, number[i]);
        vis[i] = false;
      }
    }
  }
  else
      dfs(x, y+1);
}
int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    memset(color, -1, sizeof(color));
    memset(vis, false, sizeof(vis));

    paint(1, 1, 19, number[19]);
    paint(1, 48, 20, number[20]);
    paint(1, 94, 21, number[21]);
    vis[19] = vis[20] = vis[21] = true;

    dfs(1, 1);
    return 0;
}

密文搜索

题目连接:密文搜索

福尔摩斯从X星收到一份资料,全部是小写字母组成。
他的助手提供了另一份资料:许多长度为8的密码列表。
福尔摩斯发现,这些密码是被打乱后隐藏在先前那份资料中的。

请你编写一个程序,从第一份资料中搜索可能隐藏密码的位置。要考虑密码的所有排列可能性。

数据格式:

输入第一行:一个字符串s,全部由小写字母组成,长度小于1024*1024
紧接着一行是一个整数n,表示以下有n行密码,1<=n<=1000
紧接着是n行字符串,都是小写字母组成,长度都为8

要求输出:
一个整数, 表示每行密码的所有排列在s中匹配次数的总和。

例如:
用户输入:
aaaabbbbaabbcccc
2
aaaabbbb
abcabccc

则程序应该输出:
4

这是因为:第一个密码匹配了3次,第二个密码匹配了1次,一共4次。

解:首先处理一下所有的长度为8的字符串:每个字符串从小到大排序后,加入到map中。然后遍历第一行输入的字符串s,每次取其长度为8的连续字串,排序后在map中寻找。 因为每个长度为8的串排序顺序任意,因此可以排序后比较更加方便。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <map>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 155;

int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    map<string, int> mp;
    string str, s;
    int n;
    int ans = 0;
    cin >> str;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
      cin >> s;
      sort(s.begin(), s.end());
      mp[s]++;
    }
    for(int i = 0; i+8 < str.length(); i++)
    {
      string temp = str.substr(i, 8);
      sort(temp.begin(), temp.end());
      ans += mp[temp];
    }
    cout << ans << endl;
    return 0;
}

第七届决赛真题

一步之遥

从昏迷中醒来,小明发现自己被关在X星球的废矿车里。
矿车停在平直的废弃的轨道上。
他的面前是两个按钮,分别写着“F”和“B”。

小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。
按F,会前进97米。按B会后退127米。
透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。
他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。
或许,通过多次操作F和B可以办到。

矿车上的动力已经不太足,黄色的警示灯在默默闪烁…
每次进行 F 或 B 操作都会消耗一定的能量。
小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。

请填写为了达成目标,最少需要操作的次数。

注意,需要提交的是一个整数,不要填写任何无关内容(比如:解释说明等)

解: 直接暴力求解二元一次方程的解。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 1000;

int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    for(int i = 1; i <= maxn; i++)
      for(int j = 1; j <= maxn; j++)
      {
        if(i*97 - 127*j == 1) cout << i+j << endl;
      }

    return 0;
}

凑平方数

把0~9这10个数字,分成多个组,每个组恰好是一个平方数,这是能够办到的。
比如:0, 36, 5948721

再比如:
1098524736
1, 25, 6390784
0, 4, 289, 15376
等等…

注意,0可以作为独立的数字,但不能作为多位数字的开始。
分组时,必须用完所有的数字,不能重复,不能遗漏。

如果不计较小组内数据的先后顺序,请问有多少种不同的分组方案?

注意:需要提交的是一个整数,不要填写多余内容。

解: 实现0-9的全排列后,对于每种排序顺序,dfs其平方数,最后形成一个字符串加入到set中,实现取去重。

#include <iostream>
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <string>
#include <set>
#include <sstream>
using namespace std;
const int maxn = 10;
int arr[maxn] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
long long num_s[maxn];
set<string> s;

string to_string(long long num)
{
  string str;
  stringstream ss;
  ss << num;
  ss >> str;
  return str;
}

void dfs(int index, int num) // index -> The index of array arr[maxn], num -> The number of num_s[maxn]
{
  if(index == 10)
  {
    long long num_t[maxn];
    string str;
    copy(num_s, num_s+num, num_t);
    sort(num_t, num_t+num);
    for(int i = 0; i < num; i++)
    {
      str += to_string(num_t[i]) + ',';
    }

    s.insert(str);
    return ;
  }

  if(arr[index] == 0)
  {
    num_s[num] = arr[index];
    dfs(index+1, num+1);
    return;
  }

  long long number = 0;
  for(int i = index; i < maxn; i++)
  {
    number = number*10 + arr[i];
    if(number == (long long)sqrt(number) * (long long)sqrt(number))
    {
      num_s[num] = number;
      dfs(i+1, num+1);
    }
  }
}
int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);

    do
    {
      dfs(0, 0);
    }
    while(next_permutation(arr, arr+10));

    cout << s.size() << endl;

    return 0;
}

第八届决赛真题

36进制

对于16进制,我们使用字母A-F来表示10及以上的数字。
如法炮制,一直用到字母Z,就可以表示36进制。

36进制中,A表示10,Z表示35,AA表示370

你能算出 MANY 表示的数字用10进制表示是多少吗?

请提交一个整数,不要填写任何多余的内容(比如,说明文字)

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 26;
map<char, int> mp;
void init()
{
  char ch = 'A';
  int num = 10;
  for(int i = 1; i <= maxn; i++)
  {
    mp[ch++] = num++;
  }
}
int trans36_To_10(string s)
{
  int ans = 0;
  int order = (int)pow(36, s.length()-1);

  for(int i = 0; i < s.length(); i++)
  {
    if(s[i] >= '0' && s[i] <= '9')
      ans += (s[i] - '0')*order;
    else
      ans += mp[s[i]]*order;

    order /= 36;
  }

  return ans;
}
int main()
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    init();
    cout << trans36_To_10("MANY");
    return 0;
}

瓷砖样式

小明家的一面装饰墙原来是 3*10 的小方格。
现在手头有一批刚好能盖住2个小方格的长方形瓷砖。
瓷砖只有两种颜色:黄色和橙色。

小明想知道,对于这么简陋的原料,可以贴出多少种不同的花样来。
小明有个小小的强迫症:忍受不了任何22的小格子是同一种颜色。
(瓷砖不能切割,不能重叠,也不能只铺一部分。另外,只考虑组合图案,请忽略瓷砖的拼缝)
显然,对于 2
3 个小格子来说,口算都可以知道:一共10种贴法,如【p1.png所示】

但对于 3*10 的格子呢?肯定是个不小的数目,请你利用计算机的威力算出该数字。

在这里插入图片描述 【p1.png】

解:以每个格子为单位遍历整个矩形,当到达(3, 10)处时染色完成,判断是否满足题目满足的条件,然后还得将答案加入一个set中,防止方案数的重复。

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <set>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 15;
int color[maxn][maxn];
long long total;
set<string> s;
bool check()
{
  for(int i = 1; i <= 2; i++)
    for(int j = 1; j <= 9; j++)
      if(color[i][j] == color[i][j+1] && color[i][j+1] == color[i+1][j] && color[i+1][j] == color[i+1][j+1])
        return false;

  return true;
}
void dfs(int x, int y) // 0 -> yellow 1 -> orange
{
  if(y == 11)
  {
    dfs(x+1, 1);
    return ;
  }

  if(x == 4)
  {

    if(check())
    {
      int index = 0;
      string str(30, 0);
      for(int i = 1; i <= 3; i++)
        for(int j = 1; j <= 10; j++)
          str[index++] = color[i][j];
      s.insert(str);
    }
    return ;
  }

  if(color[x][y] == -1)
  {
    if(color[x][y+1] == -1)
    {
      color[x][y] = color[x][y+1] = 0;
      dfs(x, y+1);
      color[x][y] = color[x][y+1] = 1;
      dfs(x, y+1);
      color[x][y] = color[x][y+1] = -1;
    }
    if(color[x+1][y] == -1)
    {
      color[x][y] = color[x+1][y] = 0;
      dfs(x, y+1);
      color[x][y] = color[x+1][y] = 1;
      dfs(x, y+1);
      color[x][y] = color[x+1][y] = -1;
    }
  }
  else
    dfs(x, y+1);
}
int main() // crtl + shirt + c
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    memset(color, -1, sizeof(color));
    for(int i = 1; i <= 10; i++) color[4][i] = -2;
    for(int j = 1; j <= 4; j++) color[j][11] = -2;
    total = 0;
    dfs(1, 1);
    cout << s.size() << endl;
    return 0;
}

发现环

小明的实验室有N台电脑,编号1~N。原本这N台电脑之间有N-1条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。

不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了BUG。

为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?

第一行包含一个整数N。
以下N行每行两个整数a和b,表示a和b之间有一条数据链接相连。

对于30%的数据,1 <= N <= 1000
对于100%的数据, 1 <= N <= 100000, 1 <= a, b <= N

输入保证合法。

按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。

样例输入:
5
1 2
3 1
2 4
2 5
5 3

样例输出:
1 2 3 5

解:题目给出的是无根树,先转换为有根树来计算(这里总是将1作为树根)。利用两个vector数组,一个名为v的数组记录电脑之间的无向边,一个名为dirv的数组记录电脑之间的边从小到大的排序。如下图,以样例为例。
在这里插入图片描述
在这里插入图片描述
得到两个数组后,用dirv数组跑bfs,对于每次出现过的点进行标记,当一个点第二次出现时就说明是环路上的一个点,bfs结束。
以bfs得到的点为起点,dfs整棵树。每次遍历其他点时标记的是走过的边,这里利用map进行标记。这样走回原点的时候dfs就可以结束,输出结果。

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <set>
#include <vector>
#include <queue>
#include <sstream>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 1000005;
vector<int> v[maxn];
vector<int> dirv[maxn];
map<string, int> mp;
int in[maxn];
bool vis[maxn];
int path[maxn];
int flag = 0;
bool finish;
string to_String(int a)
{
  string s;
  stringstream ss;
  ss << a;
  ss >> s;
  return s;
}
int topo_sort()
{
  queue<int> Q;
  int result = 0;
  bool flag = false;
  memset(vis, false, sizeof(vis));
  Q.push(1);

  while(!Q.empty())
  {
    int s = Q.front();

    Q.pop();
    if(vis[s])
    {
      result = s;
      break;
    }
    vis[s] = true;

    for(int i = 0; i < dirv[s].size(); i++)
    {
      Q.push(dirv[s][i]);
    }

  }

  return result;
}

void init()
{
  int n;
  scanf("%d", &n);
  memset(in, 0, sizeof(in));
  for(int i = 1; i <= n; i++)
  {
    int a,b;
    scanf("%d%d", &a, &b);
    if(a > b) swap(a, b);
    in[b]++;
    dirv[a].push_back(b);
    v[a].push_back(b);
    v[b].push_back(a);
  }

}

void dfs_path(int nowpos, int endpos, int index)
{
  if(finish) return;
  if(nowpos == endpos && flag++ > 0)
  {
    sort(path, path+index);
    for(int i = 0; i < index; i++)
      printf("%d%c", path[i], i == index-1?'\n':' ');
    finish = true;
    return ;
  }

  for(int i = 0; i < v[nowpos].size(); i++)
  {
    string s1 = to_String(nowpos) + "," + to_String(v[nowpos][i]);
    string s2 = to_String(v[nowpos][i]) + "," + to_String(nowpos);
    if(!mp[s1] && !mp[s2])
    {
      mp[s1] = mp[s2] = 1;
      path[index] = v[nowpos][i];
      dfs_path(v[nowpos][i], endpos, index+1);
      mp[s1] = mp[s2] = 0;
      path[index] = 0;
    }
  }
}
int main() // crtl + shirt + c
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    init();
    int start = topo_sort();
    //cout << start  << endl;
    memset(path, 0, sizeof(path));
    finish = false;
    dfs_path(start, start, 0);

    return 0;
}

对局匹配

小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分,代表他的围棋水平。

小明发现网站的自动对局系统在匹配对手时,只会将积分差恰好是K的两名用户匹配在一起。如果两人分差小于或大于K,系统都不会将他们匹配。

现在小明知道这个网站总共有N名用户,以及他们的积分分别是A1, A2, … AN。

小明想了解最多可能有多少名用户同时在线寻找对手,但是系统却一场对局都匹配不起来(任意两名用户积分差不等于K)?

第一行包含两个个整数N和K。
第二行包含N个整数A1, A2, … AN。

对于30%的数据,1 <= N <= 10
对于100%的数据,1 <= N <= 100000, 0 <= Ai <= 100000, 0 <= K <= 100000

一个整数,代表答案。

样例输入:
10 0
1 4 2 8 5 7 1 4 2 8

样例输出:
6

再比如,
样例输入:
10 1
2 1 1 1 1 4 4 3 4 4

样例输出:
8

解: 用动态规划来做。假设K值为2,则分数 0,2,4,6…,2n的数为一组,1,3,5,7,…,2n+1的数为另一组,两组之间没有影响,状态转移方程为
dp[i] = max{dp[]i-k], dp[i-2*k] + a[i]}

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <set>
#include <vector>
#include <queue>
#include <sstream>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 100005;

int main() // crtl + shirt + c
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    int num[maxn];
    int dp[maxn];
    int n, k;
    int maxscore = -1;
    cin >> n >> k;
    memset(num, 0, sizeof(num));
    memset(dp, 0, sizeof(dp));
    for(int i = 1; i <= n ;i++)
    {
      int x;
      cin >> x;
      maxscore = max(maxscore, x);
      num[x]++;
    }

    int sum = 0;
    if(k == 0)
    {
      for(int i = 1; i <= maxscore; i++)
      {
        if(num[i]) sum++;
      }
    }
    else
    {
      for(int i = 0; i < k; i++)
      {

        dp[i] = num[i];
        dp[i+k] = max(num[i], num[i+k]);
        int  index = i+k;
        for(int  j = i + 2*k; j <= maxscore; j += k)
        {
          dp[j] = max(dp[j-k], dp[j - 2*k] + num[j]);
          index = j;
        }
        sum += dp[index];
      }

    }
    cout << sum << endl;
    return 0;
}



第九届决赛真题

激光样式

x星球的盛大节日为增加气氛,用30台机光器一字排开,向太空中打出光柱。
安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!
国王很想知道,在目前这种bug存在的情况下,一共能打出多少种激光效果?

显然,如果只有3台机器,一共可以成5种样式,即:
全都关上(sorry, 此时无声胜有声,这也算一种)
开一台,共3种
开两台,只1种

30台就不好算了,国王只好请你帮忙了。

要求提交一个整数,表示30台激光器能形成的样式种数。

解:将30台机器看作2进制的30位,为0代表机器关闭,为1代表机器开启。然后从0到pow(2, 30)遍历每个数,对于每个数,利用移位的方法,每次右移一位,如果移位后的数是奇数则说明尾数为1,由此判断是否有两个相邻机器同时开启的情况。

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <set>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 15;

int main() // crtl + shirt + c
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    long long e = (long long)pow(2, 30);
    long long ans = 0;
    for(long long i = 0; i < e; i++)
    {
      long long temp = i;
      int flag = 1;
      while(temp)
      {
        long long num1 = temp;
        long long num2 = temp >> 1;
        if(num1%2 == 1 && num2%2 == 1)
        {
          flag = 0;
          break;
        }
        temp = temp >> 1;
      }
      if(flag) ans++;
    }
    cout << ans << endl;
    return 0;
}

调手表

小明买了块高端大气上档次的电子手表,他正准备调时间呢。
在 M78 星云,时间的计量单位和地球上不同,M78 星云的一个小时有 n 分钟。
大家都知道,手表只有一个按钮可以把当前的数加一。在调分钟的时候,如果当前显示的数是 0 ,那么按一下按钮就会变成 1,再按一次变成 2 。如果当前的数是 n - 1,按一次后会变成 0 。
作为强迫症患者,小明一定要把手表的时间调对。如果手表上的时间比当前时间多1,则要按 n - 1 次加一按钮才能调回正确时间。
小明想,如果手表可以再添加一个按钮,表示把当前的数加 k 该多好啊……
他想知道,如果有了这个 +k 按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。
注意,按 +k 按钮时,如果加k后数字超过n-1,则会对n取模。
比如,n=10, k=6 的时候,假设当前时间是0,连按2次 +k 按钮,则调为2。

「输入格式」
一行两个整数 n, k ,意义如题。

「输出格式」
一行一个整数
表示:按照最优策略按键,从一个时间调到另一个时间最多要按多少次。

「样例输入」
5 3

「样例输出」
2

「样例解释」
如果时间正确则按0次。否则要按的次数和操作系列之间的关系如下:
1:+1
2:+1, +1
3:+3
4:+3, +1

解: 题目并没有说默认从0时间开始调,不过当作从0开始写出的bfs也是对的,算是基础的bfs。

#include <iostream>
#include <math.h>
#include <map>
#include <stdio.h>
#include <set>
#include <vector>
#include <queue>
#include <sstream>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 100005;
struct node
{
  int step;
  int time;
  node(int s, int t)
  {
    step = s;
    time = t;
  }
};
void bfs(int n, int k)
{
  bool vis[maxn];
  memset(vis, false, sizeof(vis));
  queue<node> Q;
  Q.push(node(0, 0));

  int maxtime = -1;
  while(!Q.empty())
  {
    node nn = Q.front();
    Q.pop();
    if(!vis[nn.time])
    {
      vis[nn.time] = true;
      maxtime = max(maxtime, nn.step);
      if(!vis[(nn.time+1)%n]) Q.push(node(nn.step+1,(nn.time+1)%n));
      if(!vis[(nn.time+k)%n]) Q.push(node(nn.step+1,(nn.time+k)%n));
    }
  }
  cout << maxtime << endl;
}
int main() // crtl + shirt + c
{
    //freopen("d:in.txt","r",stdin);
    //freopen("d:testout.txt","w",stdout);
    int n, k;
    cin >> n >> k;
    bfs(n ,k);
    return 0;
}

搭积木

小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。
在搭积木时,小明选取 m 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。
随后,小明可以在上面摆放第1层,第2层,……,最多摆放至第n层。摆放积木必须遵循三条规则:

规则1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
规则2:同一层中的积木必须连续摆放,中间不能留有空隙;
规则3:小明不喜欢的位置不能放置积木。

其中,小明不喜欢的位置都被标在了图纸上。图纸共有n行,从下至上的每一行分别对应积木的第1层至第n层。每一行都有m个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。
现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。
由于这个答案可能很大,你只需要回答这个答案对1000000007(十亿零七)取模后的结果。
注意:地基上什么都不放,也算作是方案之一种。

【输入格式】
输入数据的第一行有两个正整数n和m,表示图纸的大小。
随后n行,每行有m个字符,用来描述图纸 。每个字符只可能是‘.’或‘X’。

【输出格式】
输出一个整数,表示答案对1000000007取模后的结果。

【样例输入1】
2 3
…X
.X.

【样例输出1】
4

【样例说明1】
成功的摆放有(其中O表示放置积木):
(1)
…X
.X.
(2)
…X
OX.
(3)
O.X
OX.
(4)
…X
.XO

【样例输入2】
3 3
…X
.X.

【样例输出2】
16

解:DP+二维数组前缀和。
设dp[i][l][r]为 第i层区间[l,r]内的方案数总和,这里的[l,r]是指包含所有元素,例如[3,5]就是第3,4,5个积木都摆放时的方案数,因此区间内一定没有X。
转移方程为
dp[i][l][r] = Σ dp[i-1][x][y],其中 1 <= x <= l, r <= y <= m;表示第i-1层必须包含第i层的[l,r]区间。
然后在每一层,枚举[l,r]区间的所有情况,对于每一种情况,使用转移方程算出答案。但求和的过程是O(n²)的,因此对于第 i-1 层先算出其前缀和,然后在第 i 层就能在O(1)内算出和。
另外,由于前缀和涉及减法,输出答案时一定要约束到正数。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define maxn 105
#define mod 1000000007
using namespace std;

int c[maxn][maxn];
long long dp[maxn][maxn][maxn];
long long s[maxn][maxn]; // l-r
int n,m;
void solve()
{
  cin >> n >> m;
  for(int i = n; i >= 1; i--)
    for(int j = 1; j <= m; j++)
  {
      char ch;
      cin >> ch;
      c[i][j] = c[i][j-1];
      if(ch == 'X')
        c[i][j]++;
  }

  dp[0][1][m] = 1;
  for(int l = 1; l <= m; l++)
    for(int r = l; r <= m; r++)
    {
      s[l][r] = (s[l-1][r] + s[l][r-1] - s[l-1][r-1] + dp[0][l][r]) % mod;
      //cout << " l = " << l << " r = " << r << " " << s[l][r] << endl;
    }

  long long ans = 1;
  for(int i = 1; i <= n; i++)
  {
    for(int l = 1; l <= m; l++)
      for(int r = l; r <= m; r++)
      {
          if(c[i][r] - c[i][l-1] == 0)
          {
              dp[i][l][r] = (s[l][m] - s[l][r-1] - s[0][m] + s[0][r-1] + dp[i][l][r]) % mod;
              //cout << "i = " << i << " l = " << l << " r = " << r << " " << dp[l][r] << endl;
              ans = (ans + dp[i][l][r]) % mod;
          }
      }

    for(int l = 1; l <= m; l++)
      for(int r = l; r <= m; r++)
        s[l][r] = (s[l-1][r] + s[l][r-1] - s[l-1][r-1] + dp[i][l][r]) % mod;
  }
  cout << (ans + mod) % mod << endl;
}
int main()
{

    //freopen("d:in.txt","r",stdin);
    //freopen("testout","w",stdout);
    solve();

    return 0;
}

矩阵求和

经过重重笔试面试的考验,小明成功进入 Macrohard 公司工作。
今天小明的任务是填满这么一张表:
表有 n 行 n 列,行和列的编号都从1算起。
其中第 i 行第 j 个元素的值是 gcd(i, j)的平方,
gcd 表示最大公约数,以下是这个表的前四行的前四列:
1 1 1 1
1 4 1 4
1 1 9 1
1 4 1 16

小明突然冒出一个奇怪的想法,他想知道这张表中所有元素的和。
由于表过于庞大,他希望借助计算机的力量。

「输入格式」
一行一个正整数 n 意义见题。

「输出格式」
一行一个数,表示所有元素的和。由于答案比较大,请输出模 (10^9 + 7)(即:十亿零七) 后的结果。

「样例输入」
4

「样例输出」
48

解: 欧拉函数
count(d)为[1,n]中gcd(i,j) = d的个数。
因此ans = Σ count(d) * d² (1 <= d <= n)
关键在于如何求count(d)。
这里我们可以转换一下:
gcd(i,j) = d(1 <= i,j <= n) <=> gcd(i, j) = 1(1 <= i, j <= n/d),如下图所示。
在这里插入图片描述
转换为求前N个数中互质的数的对数之后,不难想到用欧拉函数来写。

这里简单了解一下欧拉函数以及用到的性质。

欧拉函数:前N个数中与N互质的数的个数,这里用phi[n]来表示

所有的数都可以写成其质因数相乘的形式。

欧拉函数通式:phi[n] = n * (1-p1) * (1-p2) * …* (1-pn) 。
此处pi为n的质因数的倒数。
例如12的质因数为2,3 因此 phi[12] = 12 * (1-1/2) * (1-1/3) = 4,即与12互质的个数有4个(1,5,7,11)。通式利用的是容斥原理,2为12的质因数,12有1/2的数是2的倍数;3为12的质因数,12有1/3的数是3的倍数。所以通式的意思是12中既不是2的倍数也不是3的倍数的个数

还需要欧拉函数的几个性质。

(1)如果n为质数,则phi[n] = n-1。这条显然成立,前n个数都与n互质,因此个数为n-1。
证明:
phi[n] = n * (1-1/n) = n-1。

(2) 如果 n mod m=0,其中p为质数,则 phi[n * m]= phi[n] * m,否则 phi[n * m]= phi[n] ∗ phi[m]。
证明:
phi[n*m] = n * m *

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define maxn 10000005
#define mod 1000000007
using namespace std;

bool vis[maxn];
long long s[maxn];
long long prime[maxn];
long long phi[maxn];
void Euler(int n)
{
  s[1] = prime[0] = phi[1] = 1;
  for(int i = 2; i < n; i++)
  {
    if(!vis[i])
    {
      prime[prime[0]++] = i;
      phi[i] = i-1;
    }
    for(int j = 1; j < prime[0] && i*prime[j] <= n; j++)
    {
      vis[i*prime[j]] = true;
      if(i % prime[j])
      {
        phi[i*prime[j]] = phi[i] * phi[prime[j]];
      }
      else
      {
        phi[i*prime[j]] = phi[i] * prime[j];
        break;
      }
    }
  }
  for(int i = 2; i < n; i++)
  {
    s[i] = (s[i-1]%mod + (2*phi[i])%mod) % mod;
  }
}
void solve()
{
  int n;
  cin >> n;
  Euler(maxn);
  long long sum = 0;
  for(int d = 1; d <= n; d++)
  {
    sum = (sum%mod + s[n/d]%mod * d%mod * d%mod) % mod;
  }
  cout << sum << endl;
}
int main()
{
  int t = 1;
  //scanf("%d", &t);
  while(t--)
    solve();
  return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值