蓝桥杯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的小格子是同一种颜色。
(瓷砖不能切割,不能重叠,也不能只铺一部分。另外,只考虑组合图案,请忽略瓷砖的拼缝)
显然,对于 23 个小格子来说,口算都可以知道:一共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;
}