题目集传送门
打铁弱鸡,若各位大佬发现错误麻烦评论指正。
A. 结果填空:有趣的数字
蓝桥杯标准的送温暖题,枚举每个数判断是否含有数字5并进行判素数后计数即可。
const int MAXN = 100000;
bool check(int n) {
while(n) {
if(n % 10 == 5) return true;
n /= 10;
}
return false;
}
bool prime(int n) {
if(n < 2) return false;
bool ret = true;
for(int i = 2; i * i <= n; i++) {
if(n % i == 0) ret = false;
}
return ret;
}
int main() {
int cnt = 0;
for(int i = 1; i <= MAXN; i++)
if(check(i) && prime(i)) cnt++;
cout << cnt << endl;
return 0;
}
B. 结果填空:爬楼梯
超级经典的dp,蓝桥杯日常经典题变形。
萌新可以先做这道题 传送门
通过暴力枚举我们可以发现当步数为1和2时,方案数为1 1 2 3 5 8 11,是斐波那契数列。
但显然,暴力是没有前途的,下面介绍另一种方法。
定义F(i)为走到第i级台阶的方案数,则显然可得当 i>2 时有 F(i) = F(i-1) + F(i-2)。因为要走到第i级台阶,可以是由i-1级台阶上来也可以是由i-2级台阶上来,所以第i级的方案数应等于两者之和。
此题是一次最多可以上四级台阶,但第5级和第7级不能踩,则可以得到表达式F(i) = F(i-1) + F(i-2)+ F(i-3) + F(i-4),并特判F(5) = F(7) = 0。
到这里就可以写出简单的递归程序了,但是只会写递归是没有前途的(手动滑稽),所以给出非递归的dp版,当然下面的版本完全可以预处理前四项精简代码,但我懒得改了。
int a[117];
int main() {
a[0] = 1;
for(int i = 1; i <= 10; i++) {
if(i == 5 || i == 7) continue;
if(i - 1 >= 0) a[i] += a[i - 1];
if(i - 2 >= 0) a[i] += a[i - 2];
if(i - 3 >= 0) a[i] += a[i - 3];
if(i - 4 >= 0) a[i] += a[i - 4];
}
for(int i = 1; i <= 10; i++) cout << a[i] << endl;
return 0;
}
C. 结果填空:七巧板
wa了,盲猜原理是和用直线划分平面一样,额。。。也可能是看错题了,待补 不会补了。
D. 结果填空:苹果
在网上看到了贪心的写法,大致上都是能在当前选3就选3,不能选再和旁边的并用,但都存在反例。做的时候也考虑过贪心但未能成功证明,于是写了个暴力的递归来枚举所有的情况,稍微剪枝优化后时间在1s左右。此题个人认为应该是dp的背包问题,但学艺不精没写出dp来。。。
int ans = 0;
int a[117] = {7, 2, 12, 5, 9, 9, 8, 10, 7, 10, 5, 4, 5, 8, 4, 4, 10, 11, 3, 8, 7, 8, 3, 2, 1, 6, 3, 9, 7, 1};
int sum[117];
void dfs(int idex, int num) {
if(idex == 30) {
ans = max(ans, num);
return;
}
if(sum[idex] / 3 + num < ans) return;//剪枝优化
//不共用
dfs(idex + 1, num + a[idex] / 3);
//往后共用
if(idex + 2 < 30) {
int min_num = min(a[idex], a[idex + 1]);
min_num = min(min_num, a[idex + 2]);//共用最多能分几个
for(int k = 1; k <= min_num; k++) {
for(int i = 0; i < 3; i++) a[idex + i] -= k;
dfs(idex + 1, num + a[idex] / 3 + k);
for(int i = 0; i < 3; i++) a[idex + i] += k;
}
}
}
int main() {
for(int i = 29; i >= 0; i--) sum[i] = sum[i + 1] + a[i];
dfs(0, 0);
cout << ans << endl;
return 0;
}
E. 结果填空:方阵
没做,待补 不会补了
F. 程序设计:寻找重复项
题意简单明了,作为c++选手,直接使用了STL库。
解法一:Hash
解法二:维护一个有序序列,二分进行查找和插入,复杂度logn
对于萌新:各种库函数和板子当然可用,但不能依赖,不然岂不本末倒置。
const int MAXN = 2e6;
LL a, b, c;
LL num[MAXN + 117];
unordered_set<int> s;
int main() {
num[0] = 1;
s.insert(1);
scanf("%lld%lld%lld", &a, &b, &c);
for(int i = 1; i <= MAXN; i++) {
num[i] = (a * num[i - 1] + num[i - 1] % b) % c;
if(s.count(num[i])) {
printf("%d\n", i);
break;
} else s.insert(num[i]);
if(i == MAXN) puts("-1");
}
return 0;
}
G. 程序设计:被袭击的村庄
题意是个坑点:现在,给定上述的所有信息,我们想知道A村被袭击之后的道路、房屋、田地的总伤害,以及全村的总伤害。 正确的应该是输出被袭击之后道路、房屋、田地耐久度为0的数量和全村的总伤害。
模拟题,维护一个decrement矩阵来记录耐久度的减少量,对于每一发炮弹,更新对应伤害范围内的耐久度,对于高级炮弹还需特判溅射伤害。
const int MAXN = 300 + 117;
int n, m, k;
LL a, b, c, w;
LL harm[MAXN][MAXN];//炮弹的范围伤害
LL decrement[MAXN][MAXN];//耐久度的减少量
int village[MAXN][MAXN];//村庄布局
int q, id, x, y;
LL road, house, field, sum;
int dx[8] = { -1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = { -1, 0, 1, -1, 1, -1, 0, 1};
bool check(int x, int y) {//判断坐标是否合法
if(x < 0 || x >= n) return false;
if(y < 0 || y >= m) return false;
return true;
}
void sputtering(int x, int y) {//往周围8个格子溅射伤害
for(int i = 0; i < 8; i++) {
if(check(x + dx[i], y + dy[i]))
decrement[x + dx[i]][y + dy[i]] += w;
}
}
void solve() {
int bex = x - k / 2, bey = y - k / 2;
for(int i = 0; i < k; i++) {
for(int j = 0; j < k; j++) {
if(check(bex + i, bey + j)) {
decrement[bex + i][bey + j] += harm[i][j];
if(id == 0) sputtering(bex + i, bey + j);
}
}
}
}
void pr() {
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(village[i][j] == 1) {
//road += min(decrement[i][j], a);
road += decrement[i][j] >= a;
sum += min(decrement[i][j], a);
} else if(village[i][j] == 2) {
//house += min(decrement[i][j], b);
house += decrement[i][j] >= b;
sum += min(decrement[i][j], b);
} else {
//field += min(decrement[i][j], c);
field += decrement[i][j] >= c;
sum += min(decrement[i][j], c);
}
}
}
cout << road << " " << house << " " << field << endl;
cout << sum << endl;
}
int main() {
cin >> n >> m;
cin >> a >> b >> c;
cin >> k >> w;
for(int i = 0; i < k; i++)
for(int j = 0; j < k; j++)
cin >> harm[i][j];
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cin >> village[i][j];
cin >> q;
while(q--) {
cin >> id >> x >> y;
x--, y--;
solve();
}
pr();
return 0;
}
H. 程序设计:字符串
此题需要前置技能点:模运算,即89%M = (8*10%M + 9*1%M)%M。
先假设进制为10进制,题意为给定一个数,至多交换两位数字使得到的数字是M的倍数。
题目中说到多解输出字典序最小,则容易想到枚举。考虑一下复杂度,枚举交换的位置需要o(
),计算mod需要o(
),总的复杂度o(
)。
对多次取模操作进行优化,假设a=123%M,b=321%M,考虑123和321之间的关系:321 = 123 - 1*100 - 3*1 + 3*100 + 1*1,根据模运算则有b = (a - 1*100 - 3*1 + 3*100 + 1*1)%M。即假设交换任意i位置和j位置的数,A为交换前的值,B为交换后的值,有B = (A - i*
- j*
+ i*
+ j*
)%M,复杂度O(1)。
最后,进制为26,需要预处理一下幂次的取模,以及注意减法运算时答案的取正。
const int MAXN = 2000 + 117;
char s[MAXN];
int len;
int a[MAXN];
int fact[MAXN];
int M;
int num, now;
void sub(int mul, int order) {//now=now-mul*26^order
int sum = fact[order];
sum = sum * mul % M;
now = ((now - sum) % M + M) % M;
}
void add(int mul, int order) {//now=now+mul*26^order
int sum = fact[order];
sum = sum * mul % M;
now = (now + sum) % M;
}
void init() {//预处理幂次
scanf("%s", s);
scanf("%d", &M);
fact[0] = 1;
for(int i = 1; i < MAXN; i++) fact[i] = fact[i - 1] * 26 % M;
len = strlen(s);
for(int i = 0; i < len; i++) {
a[i] = s[i] - 'A';
num = (num * 26 + a[i]) % M;
}
}
int main() {
init();
if(num == 0) puts("0 0");
else {
bool pr = false;
for(int i = 0; i < len && !pr; i++) {
for(int j = 0; j < len && !pr; j++) {
now = num;
sub(a[i], len - 1 - i);
sub(a[j], len - 1 - j);
add(a[i], len - 1 - j);
add(a[j], len - 1 - i);
if(now == 0) {
printf("%d %d\n", i + 1, j + 1);
pr = true;
}
}
}
if(!pr) puts("-1 -1");
}
return 0;
}
I. 程序设计:最短路
此题需要前置技能点:单源最短路Dij的堆优化
如果你会了Dij的堆优化,那么就可以做此题了。
题目要求从起点到每个点再回来的最短路径的总和。对于一个点,有:来回最短路 = 去最短路 + 回最短路。利用Dij可求得起点去每个点的最短路,回的最短路容易想到对每个点都用一遍Dij,但这种做法的复杂度o(
)。可以注意到,无论怎么样起点总是固定的,如若将所有的边都反向,则跑一遍Dij即可求得所有回来的最短路,如下图。
const int MAXN = 6e4 + 117;
int t;
struct qnode {
int v;
LL c;
qnode(int _v = 0, LL _c = 0): v(_v), c(_c) {}
bool operator <(const qnode &r)const {
return c > r.c;
}
};
struct Edge {
int v;
LL cost;
Edge(int _v = 0, LL _cost = 0): v(_v), cost(_cost) {}
};
vector<Edge> E[2][MAXM];
bool vis[MAXN];
LL dist[2][MAXN];
void Dijstra(int flag, int n, int start) {
memset(vis, false, sizeof(vis));
for(int i = 1; i <= n; i++) dist[flag][i] = 1e18;
priority_queue<qnode> que;
while(!que.empty()) que.pop();
dist[flag][start] = 0;
que.push(qnode(start, 0));
qnode tmp;
while(!que.empty()) {
tmp = que.top();
que.pop();
int u = tmp.v;
if(vis[u]) continue;
vis[u] = true;
for(int i = 0; i < E[flag][u].size(); i++) {
int v = E[flag][tmp.v][i].v;
int cost = E[flag][u][i].cost;
if(!vis[v] && dist[flag][v] > dist[flag][u] + cost) {
dist[flag][v] = dist[flag][u] + cost;
que.push(qnode(v, dist[flag][v]));
}
}
}
}
void addedge(int flag, int u, int v, LL w) {
E[flag][u].push_back(Edge(v, w));
}
int n, m;
LL sum;
void init() {
sum = 0;
for(int i = 0; i < MAXM; i++) {
E[0][i].clear();
E[1][i].clear();
}
}
int main() {
scanf("%d", &t);
while(t--) {
init();
scanf("%d%d", &n, &m);
int u, v;
LL w;
while(m--) {
scanf("%d %d %lld", &u, &v, &w);
addedge(0, u, v, w);
addedge(1, v, u, w);
}
Dijstra(0, n, 1);
Dijstra(1, n, 1);
for(int i = 1; i <= n; i++) sum += dist[0][i] + dist[1][i];
printf("%lld\n", sum);
}
return 0;
}
J. 程序设计:迷宫
没做,待补 补是不可能补的