计蒜客 2020 蓝桥杯大学 B 组省赛模拟赛(一)

题目集传送门

打铁弱鸡,若各位大佬发现错误麻烦评论指正。

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( n 2 n^2 ),计算mod需要o( n 1 n^1 ),总的复杂度o( n 3 n^3 )。
对多次取模操作进行优化,假设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* 1 0 i 10^i - j* 1 0 j 10^j + i* 1 0 j 10^j + j* 1 0 i 10^i )%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( n 3 n^3 )。可以注意到,无论怎么样起点总是固定的,如若将所有的边都反向,则跑一遍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. 程序设计:迷宫

没做,待补 补是不可能补的

发布了2 篇原创文章 · 获赞 1 · 访问量 236

猜你喜欢

转载自blog.csdn.net/qq_26754655/article/details/104064201