2021牛客寒假算法基础集训营6部分题解(A,C,D,E,F,G,H,I,J)

oj: 牛客

A 回文括号序列计数

oj: 牛客

题意

询问长度为 n n n 的括号序列的个数。

括号序列定义:

  1. 空串是括号序列。
  2. 两个括号序列 P P P Q Q Q 的拼接是括号序列。
  3. 如果 P P P 是括号序列,‘(’+ P P P +')'是括号序列。

题解

脑筋急转弯题。

一个长度不为0的括号序列的两端字符必定不同,所以左右反转后一定不是括号序列,更不可能与之前相同。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int main() {
    int T = read();
    _for(i, T) {
        int n = read();
        if(n == 0) printf("1\n");
        else printf("0\n");
    }
    return 0;
}

C 末三位

oj: 牛客

题意

求出 5 n 5^n 5n 的末三位数字。

题解

快速幂,中间对1000取模即可。

注意输出时要有前导0。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int mod = 1000;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

LL quickPow(LL x, LL n, LL mod) {
    LL ans = 1;
    while(n) {
        if(n & 1) ans *= x, ans %= mod;
        x *= x, x %= mod;
        n >>= 1;
    }
    return ans;
}

int main() {
    LL n;
    while(~scanf("%lld", &n)) {
        LL ans = quickPow(5, n, mod);
        int len = 0;
        for(LL x = ans; x; ++len) x /= 10;
        _for(i, 3 - len) printf("0");
        printf("%lld\n", ans);
    }
    return 0;
}

D 划数

oj: 牛客

题意

给定一个数组。每回合取出其中两个数,然后放回这两个数的和对 11 11 11 取余的值,直到数组还剩两个数。

现在已知数组还剩 c n t ( c n t ≥ 11 ) cnt(cnt\ge 11) cnt(cnt11)和一个未知数,求出那个未知数。

题解

n n n 2 2 2 时,显然答案就是另一个数。

否则,因为 c n t ≥ 11 cnt\ge 11 cnt11,所以它不可能是取出又放回的数,那么那个未知数就一定是取出又放回的数。

而且拿出又放回是不影响数组总和对 11 11 11 取余的值的,所以直接拿数组总和减去 c n t cnt cnt,然后对 11 11 11 取模即可。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

LL n, cnt;

void sol() {
    LL sum = 0;
    _for(i, n) sum += read();
    if(n == 2) printf("%lld\n", sum - cnt);
    else printf("%lld\n", (sum - cnt) % 11);
}

int main() {
    while (~scanf("%lld%lld", &n, &cnt)) {
        sol();
    }
    return 0;
}

E 网格

oj: 牛客

题意

有一个 n n n m m m 列的网格,第 i i i j j j 列上有数字 a i , j a_{i,j} ai,j。每个位置需要从上下左右四个方向中选择互相垂直的两个。

定义 w ( x ) = x + p o p c n t ( x ) w(x)=x+popcnt(x) w(x)=x+popcnt(x) ,其中 p o p c n t ( x ) popcnt(x) popcnt(x) 表示 x x x 的二进制位中 1 1 1 的位的数量。

如果两个相邻的位置 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) 互相位于对方选择的某个方向上,则对答案由 w ( a x 1 , y 1   x o r   a x 2 , y 2 ) w(a_{x_1,y_1}\ xor\ a_{x_2,y_2}) w(ax1,y1 xor ax2,y2) 的贡献,其中 x o r xor xor 表示二进制中的按位异或。

求出最大值。

题解

参考自牛客959548301号的题解。

题目就相当于,有一个网格图,要选出若干条边,要求每个点不能同时有向(上&下)或(左&右)的边,求最大边权和。

首先假设 n = 1 n=1 n=1,限制就变成了不能选相邻两条边,求最大代价,发现只能dp。

发现行列是独立的,互不影响,所以对行和列分别计算。

又发现行与行之间互相又是独立的,所以对每一行分别计算。(列同理)

限制条件就是同一行中不能选相邻两条边(不然有一个点同时选了左右)

d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1] 表示前 i i i 条边,上一个选没选的最大价值。

转移就枚举这个点选不选,上个点选不选分别转移。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 1005;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int n, m;
LL a[maxn][maxn];
LL dp[maxn][2];

LL getval(LL a, LL b) {
    LL tem = a ^ b, num = 0;
    for(LL x = tem; x; x >>= 1) if(x & 1) ++num;
    return tem + num;
}

void sol() {
    _rep(i, 1, n) _rep(j, 1, m) a[i][j] = read();
    LL ans = 0;
    _rep(i, 1, n) {
        _rep(j, 1, m) dp[j][0] = dp[j][1] = 0;
        dp[2][1] = getval(a[i][1], a[i][2]);
        _rep(j, 3, m) {
            dp[j][0] = max(dp[j - 1][0], dp[j - 1][1]);
            dp[j][1] = dp[j - 1][0] + getval(a[i][j], a[i][j - 1]);
        }
        ans += max(dp[m][0], dp[m][1]);
    }
    _rep(i, 1, m) {
        _rep(j, 1, n) dp[j][0] = dp[j][1] = 0;
        dp[2][1] = getval(a[1][i], a[2][i]);
        _rep(j, 3, n) {
            dp[j][0] = max(dp[j - 1][0], dp[j - 1][1]);
            dp[j][1] = dp[j - 1][0] + getval(a[j][i], a[j - 1][i]);
        }
        ans += max(dp[n][0], dp[n][1]);
    }
    printf("%lld\n", ans);
}

int main() {
    n = read(), m = read();
    sol();
    return 0;
}

F 组合数问题

oj: 牛客

题意

求出 ( n 0 ) + ( n 4 ) + ( n 8 ) + . . . + ( n n ) \binom{n}{0}+\binom{n}{4}+\binom{n}{8}+...+\binom{n}{n} (0n)+(4n)+(8n)+...+(nn) 的值,结果对 998244353 998244353 998244353 取余。

题解

打表之后OEIS出一个公式:

f ( n ) = 4 f ( n − 1 ) − 6 f ( n − 2 ) + 4 f ( n − 3 ) f(n)=4f(n-1)-6f(n-2)+4f(n-3) f(n)=4f(n1)6f(n2)+4f(n3)

之后利用矩阵快速幂求解。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (LL i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (LL i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const LL mod = 998244353;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

struct mat {
    LL a[3][3];
    mat(){
        _for(i, 3) _for(j, 3) a[i][j] = 0;
    }
};
mat mat_mul(mat x, mat y) {
    mat res;  //用来表示得到的新的矩阵;
    memset(res.a, 0, sizeof(res.a));
    for (LL i = 0; i < 3; i++)
        for (LL j = 0; j < 3; j++)
            for (LL k = 0; k < 3; k++) {
                res.a[i][j] += x.a[i][k] * y.a[k][j];
                res.a[i][j] %= mod;
            }
    return res;
}
LL pow(LL n) {
    mat c, res;
    c.a[0][0] = 4;
    c.a[0][1] = -6;
    c.a[0][2] = 4;
    c.a[1][0] = 1;
    c.a[2][1] = 1;
    for (LL i = 0; i < 3; i++) res.a[i][i] = 1;
    while (n) {
        if (n & 1) res = mat_mul(res, c);
        c = mat_mul(c, c);
        n = n >> 1;
    }
    LL ans = 0;
    _for(i, 3) ans += res.a[0][i], ans %= mod;
    return (ans % mod + mod) % mod;
}

LL sol(LL n) {
    if(n <= 3) return 1;
    return pow(n - 3);
}

int main() {
    LL n;
    while(~scanf("%lld", &n)) {
        printf("%lld\n", sol(n));
    }
    return 0;
}

G 机器人

oj: 牛客

题意

n n n 个机器人,每个机器人会读入一个 x x x ,并返回 a x + b ax+b ax+b

给定一个数 x x x,通过对机器人重新排序求出返回的最大值。

题解

状压 d p dp dp 求解即可。

注意结果会爆 l o n g l o n g long long longlong,改用 i n t 128 int128 int128

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef __int128 LL;
const int maxn = 25;

inline LL read() {
    LL x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}
inline void write(LL x) {
    if (x < 0) { putchar('-'); x = -x; }
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

LL dp[1 << 21];
LL n, x;
LL a[maxn], b[maxn];

void sol() {
    _for(i, n) a[i] = read(), b[i] = read();
    _for(i, n) dp[1 << i] = a[i] * x + b[i];
    for(int s = 1; s < (1 << n); ++s) {
        _for(j, n) {
            if((s | (1 << j)) > s) {
                dp[s | (1 << j)] = max(dp[s | (1 << j)], dp[s] * a[j] + b[j]);
            }
        }
    }
    write(dp[(1 << n) - 1]);
    printf("\n");
}

int main() {
    n = read(), x = read();
    sol();
    return 0;
}

H 动态最小生成树

oj: 牛客

题意

有一张 n n n 个点 m m m 条边的图,每条边连接点 u i , v i u_i,v_i ui,vi,边权为 w i w_i wi。他想进行 q q q 次操作,有如下两种类型:

  1. 修改第 x x x 条边为连接点 y , z y,z y,z ,边权为 t t t
  2. 查询只用编号在 [ l , r ] [l,r] [l,r] 范围内的边,得到的最小生成树权值是多少。

题解

这题数据较弱,暴力也可过。

代码

#include <bits/stdc++.h>
#define m_p make_pair
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 205;
const int maxm = 30005;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

struct poi {
    int u, v, w;
    poi(){}
    poi(int u, int v, int w):u(u), v(v), w(w) {}
}a[maxm];

int n, m, q;

template <typename T>
struct Kruskal {
    int f[maxn];
    vector< pair<T, pair<int, int> > > arr;

    void init() {
        arr.clear();
        _for(i, n + 1) f[i] = i;
    }
    int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
    void update(int a, int b) { f[find(a)] = find(b); }
    void addedge(int u, int v, T w) { arr.push_back(m_p(w, m_p(u, v))); }
    T doit() {
        T ans = 0;
        sort(arr.begin(), arr.end());
        _for(i, arr.size()) {
            if (find(arr[i].second.first) != find(arr[i].second.second)) {
                ans += arr[i].first;
                update(arr[i].second.first, arr[i].second.second);
            }
        }
        return ans;
    }
    int che() {
        for(int i = 2; i <= n; ++i) if(find(i) != find(1)) return 0;
        return 1;
    }
};

Kruskal<LL> k;

void sol() {
    _rep(i, 1, m) {
        int u = read(), v = read(), w = read();
        a[i] = poi(u, v, w);
    }
    _for(i, q) {
        int op = read();
        if(op == 1) {
            int x = read(), y = read(), z = read(), t = read();
            a[x] = poi(y, z, t);
        }
        else {
            int l = read(), r = read();
            k.init();
            _rep(i, l, r) k.addedge(a[i].u, a[i].v, a[i].w);
            LL ans = k.doit();
            if(!k.che()) printf("Impossible\n");
            else printf("%lld\n", ans);
        }
    }
}

int main() {
    n = read(), m = read(), q = read();
    sol();
    return 0;
}

H 动态最小生成树

oj: 牛客

题意

给定起点和终点,检索是否存在路径,若有则输出最短路径长度。

题解

b f s bfs bfs 即可。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 105;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

struct poi {
    int x, y, dep;
    poi() {}
    poi(int x, int y, int dep):x(x), y(y), dep(dep) {}
};

int ans = -1;
char s[maxn][maxn];
int n, m;
int sx, sy, tx, ty;
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};

void bfs() {
    queue< poi > q;
    q.push(poi(sx, sy, 0));
    s[sx][sy] = '#';
    while(q.size()) {
        poi t = q.front(); q.pop();
        if(t.x == tx && t.y == ty) {
            ans = t.dep * 100;
            return;
        }
        _for(k, 4) {
            int xx = t.x + dir[k][0], yy = t.y + dir[k][1];
            if(s[xx][yy] == '.') {
                s[xx][yy] = '#';
                q.push(poi(xx, yy, t.dep + 1));
            }
        }
    }
}

void init() {
    ans = -1;
}

void sol() {
    init();
    sx = read(), sy = read(), tx = read(), ty = read();
    _rep(i, 1, n) scanf("%s", s[i] + 1);
    bfs();
    printf("%d\n", ans);
}

int main() {
    n = read(), m = read();
    sol();
    return 0;
}

J 天空之城

oj: 牛客

题意

n n n 个城市和 m m m 条边,询问能否遍历所有城市。每个城市可以多次经过,且重复经过时不计算额外时间。

题解

显然直接求最小生成树,然后判断是否覆盖全部城市即可。

代码

#include <bits/stdc++.h>
#define m_p make_pair
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 100005;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int cnt = 0;
int n, m;
string s;
map<string, int> mp;

void init() {
    mp.clear();
    cnt = 0;
}

template <typename T>
struct Kruskal {
    int f[maxn];
    vector< pair<T, pair<int, int> > > arr;

    void init() {
        arr.clear();
    }
    int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
    void update(int a, int b) { f[find(a)] = find(b); }
    void addedge(int u, int v, T w) { arr.push_back(m_p(w, m_p(u, v))); }
    T doit() {
        T ans = 0;
        sort(arr.begin(), arr.end());
        _for(i, arr.size()) {
            if (find(arr[i].second.first) != find(arr[i].second.second)) {
                ans += arr[i].first;
                update(arr[i].second.first, arr[i].second.second);
            }
        }
        return ans;
    }
};

Kruskal<LL> k;

void sol() {
    init();
    k.init();
    cin >> s;
    _for(i, m) {
        cin >> s;
        if(!mp.count(s)) mp[s] = cnt++;
        int u = mp[s];
        cin >> s;
        if(!mp.count(s)) mp[s] = cnt++;
        int v = mp[s];
        LL val;
        cin >> val;
        k.addedge(u, v, val);
    }
    _for(i, n) k.f[i] = i;
    LL ans = k.doit();
    for(int i = 1; i < n; ++i) {
        if(k.find(i) != k.find(0)) {
            cout << "No!\n";
            return;
        }
    }
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while(cin >> n >> m) {
        sol();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值