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

oj: 牛客

A 九峰与签到题

oj: 牛客

题解

签到题

代码

#pragma GCC optimize(2)
#include 
#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)
#define nl(i, n) (i == n - 1 ? "\n":" ")
using namespace std;
const int maxn = 100005;

inline int read() {
     
    int 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 num[maxn], f[maxn], ac[maxn];
vector<int> a;
int n, m;

int main() {
     
    m = read(), n = read();
    char s[10];
    _rep(i, 1, m) {
     
        int x = read();
        scanf("%s", s);
        ++num[x];
        if(s[0] == 'A') ++ac[x];
        if(ac[x] * 2 < num[x]) f[x] = 1;
    }
    vector<int> ans;
    _rep(i, 1, n) if(!f[i]) ans.push_back(i);
    if(ans.size() == 0) {
     
        printf("-1\n");
        return 0;
    }
    _for(i, ans.size()) printf("%d%s", ans[i], nl(i, ans.size()));
    return 0;
}

B 武辰延的字符串

oj: 牛客

题意

给出两个字符串,如果可以从 s s s 串中取出两个前缀组合而成的串正好也是 t t t 串的前缀,求出有多少种组合。

题解

可以认为组合的过程就是先从 s s s 中取出一段前缀同时也是 t t t 的前缀,我们称这一段为 A A A ;再从 s s s 中取出一段前缀,我们称这一段为 B B B A A A B B B 合成 A B AB AB 依然是 t t t 的前缀。

想到扩展 K M P KMP KMP 算法的 e x t e n d extend extend 数组的意义为 s [ i , n ] s[i,n] s[i,n] t t t 的最长公共前缀的长度。

首先求出 s s s t t t 的最长公共前缀,它的长度为 e x t e n d [ 0 ] extend[0] extend[0] 。这也是 A A A 串的最长长度。

A A A 确定后,只需取出 t t t 中除了 A A A 之外的部分和 s s s 求最长公共前缀,求出的前缀长度就是这个 A A A 对答案的贡献。

枚举 A A A 的长度即可, A A A 的长度的取值范围是 [ 1 , e x t e n d [ 0 ] ] [1,extend[0]] [1,extend[0]]

代码

#pragma GCC optimize(2)
#include 
#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)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
typedef long long LL;
const LL 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;
}

char s[maxn], t[maxn];
int n, m;
int nxt[maxn], ext[maxn];
void getnext(char t[]) {
     
    int tlen = strlen(t);
    nxt[0] = tlen;
    int now = 0;
    while (t[now] == t[now + 1] && now + 1 < tlen) now++;
    nxt[1] = now;
    int p0 = 1;
    for (int i = 2; i < tlen; i++) {
     
        if (i + nxt[i - p0] < nxt[p0] + p0)
            nxt[i] = nxt[i - p0];
        else {
     
            int now = nxt[p0] + p0 - i;
            now = max(0ll, now);
            while (t[now] == t[i + now] && i + now < tlen) now++;
            nxt[i] = now;
            p0 = i;
        }
    }
}
void exkmp(char s[], char t[]) {
     
    getnext(t);
    int slen = strlen(s), tlen = strlen(t);
    int now = 0;
    while (s[now] == t[now] && now < min(slen, tlen)) now++;
    ext[0] = now;
    int p0 = 0;
    for (int i = 1; i < slen; i++) {
     
        if (i + nxt[i - p0] < ext[p0] + p0)
            ext[i] = nxt[i - p0];
        else {
     
            int now = ext[p0] + p0 - i;
            now = max(now, 0ll);
            while (t[now] == s[i + now] && now < tlen && now + i < slen) now++;
            ext[i] = now;
            p0 = i;
        }
    }
}

int main() {
     
    scanf("%s%s", s, t);
    n = strlen(s), m = strlen(t);
    LL ans = 0;
    getnext(s);
    exkmp(t, s);
    _rep(i, 1, ext[0]) ans += ext[i];
    printf("%lld\n", ans);
    return 0;
}

D 温澈滢的狗狗

oj: 牛客

题意

n n n 个狗狗,按照 1 − n 1-n 1n 编号,每个狗狗有一个颜色值,不同颜色值的狗狗会有亲密度,其亲密度为编号差的绝对值。

对所有的狗狗组合按照“亲密度、较小的编号、较大的编号”为 3 3 3 个关键字排序,求出第 k k k 大的组合的狗狗的编号。如果不存在就输出 − 1 -1 1.

题解

我们发现组合的个数 v a l val val 随着最大的狗狗编号差值 d i f dif dif 线性增加。可以考虑二分枚举 d i f dif dif 求出 v a l val val,使之与 k k k 作比较,以此求出第一个大于等于 k k k v a l val val 对应的 d i f dif dif

枚举方法采用尺取法保证 O ( n ) O(n) O(n) 的复杂度。

如果不存在这样的 d i f dif dif 就输出 − 1 -1 1.

如果存在,那么我们可以断定第 k k k 大的组合的编号差的绝对值为 d i f dif dif,并且最后一个差值为 d i f dif dif 的狗狗组合的排名是 v a l val val

之后只需要将最后一个组合向前移动 v a l − k val-k valk 次,即可求出排名为 k k k 的组合。

代码

#include 
#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;
}

LL n, k;
int a[maxn];
int num[maxn];

LL che(int mid) {
     
    _for(i, mid + 1) ++num[a[i]];
    LL ans = 0;
    int l = 0, r = mid;
    do {
     
        ans += mid + 1 - num[a[l]];
        ++num[a[++r]];
        --num[a[l++]];
    }while(r < n);
    do {
     
        ans += r - l - num[a[l]];
        --num[a[l++]];
    }while(l < n);
    return ans;
}

void sol() {
     
    _for(i, n) a[i] = read();
    LL l = 0, r = n, dif = 0, val = 0;
    while(l <= r) {
     
        LL mid = (l + r) >> 1;
        LL tem = che(mid);
        if(tem >= k) {
     
            dif = mid;
            val = tem;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    if(dif == 0 || dif == n) {
     
        printf("-1\n");
        return;
    }
    l = n - dif - 1, r = n - 1;
    while(a[l] == a[r]) --l, --r;
    while(val > k) {
     
        --l, --r;
        while(a[l] == a[r]) --l, --r;
        --val;
    }
    printf("%lld %lld\n", l + 1, r + 1);
}

int main() {
     
    while(cin >> n >> k) {
     
        sol();
    }
    return 0;
}

E 九峰与子序列

oj: 牛客

题意

给定长度为 n n n 的字符串序列 a a a 和字符串 k k k ,询问 a a a 有多少子序列拼接起来等于 k k k

题解

因为我们要组合的是 a a a 的子序列,所以每个序列都有取和不取两个状态,这样组成一个 01 01 01 背包的问题。

我们记录 d p [ i ] [ j ] dp[i][j] dp[i][j] 为前 i i i 个序列组成 k k k 串的前 j j j 个字符的情况数。

那么当 k [ j − l e n ( a [ i ] ) + 1 : j ] = a [ i ] k[j-len(a[i])+1:j]=a[i] k[jlen(a[i])+1:j]=a[i] 时, d p [ i ] [ j ] + = d p [ i − 1 ] [ j − l e n ( a [ i ] ) ] dp[i][j]+=dp[i-1][j-len(a[i])] dp[i][j]+=dp[i1][jlen(a[i])]

同时使用字符串哈希快速比较两个串是否相等。

然后枚举 i i i j j j 即可。

代码

#pragma GCC optimize(2)
#include 
#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;
typedef unsigned long long ULL;
const int maxn = 5000005;

inline int read() {
     
    int 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;
}

const ULL P = 131;
ULL has[maxn], p[maxn];
char s[maxn], t[maxn];
int n;
int sl;
LL dp[maxn];

int che(ULL hash, int l, int r) {
     
    return hash == has[r] - has[l] * p[r - l];
}

int main() {
     
    p[0] = 1;
    for(int i = 1; i < maxn; ++i) {
     
        p[i] = p[i - 1] * P;
    }
    n = read();
    scanf("%s", s);
    sl = strlen(s);
    has[0] = 0;
    _for(i, sl) has[i + 1] = has[i] * P + s[i];
    dp[0] = 1;
    _for(j, n) {
     
        scanf("%s", t);
        int tl = strlen(t);
        ULL hat = 0;
        _for(i, tl) hat = hat * P + t[i];
        for(int i = sl; i >= tl; --i) {
     
            dp[i] += dp[i - tl] * che(hat, i - tl, i);
        }
    }
    printf("%lld\n", dp[sl]);
    return 0;
}

F 魏迟燕的自走棋

oj: 牛客

题意

n n n 个人和 m m m 件装备,每件装备各有一个属性值,且每件装备只可以被装备给特定的几个人。求出如何给这些人配备装备可以使得总属性值最大。

题解

每件装备不管配给谁,只要被配备,它的属性值就会对答案有贡献。

所以我们采取贪心的策略,按照属性值的大小从大到小地枚举装备,去找增广路。

若找到则把这件装备的属性值加到答案上。

若找不到则意味着一旦装上这件装备则会有另一件已经使用的装备被卸下来。而我们是按照属性值从大到小枚举的,所以被卸下来的装备的属性值一定不比装上的属性值小,所以总的来看这样做会减小总属性值。所以直接放弃即可。

增广路采用匈牙利算法寻找。

值得注意的是一般的匈牙利算法会超时,原因在于每次寻找增广路前都要清空 v i s vis vis 数组,这会占用大量的时间。所以我们寻找完增广路后回溯地清空增广路上的 v i s vis vis 数组,这样就不用额外清空了,节省了大量的时间。

至于没有找到增广路的点,意味着下一次遍历到它依然找不到增广路,所以他们的 v i s vis vis 数组不清空也不影响答案。

代码

#include
#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;
const int maxm = 200005;

inline int read() {
     
    int 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;
pair<int, int> p[maxn];
int head[maxn], to[maxm], nex[maxm], tot;

inline void addedge(int u, int v) {
     
    to[tot] = v;
    nex[tot] = head[u];
    head[u] = tot++;
}

int vis[maxn], matching[maxn];

inline int dfs(int u) {
     
    for(int i = head[u]; ~i; i = nex[i]) {
     
        int v = to[i];
        if(!vis[v]) {
     
            vis[v] = 1;
            if(matching[v] == 0 || dfs(matching[v])) {
     
                matching[v] = u;
                vis[v] = 0;
                return 1;
            }
        }
    }
    return 0;
}

int main() {
     
    n = read(), m = read();
    tot = 0;
    _for(i, m + 1) head[i] = -1;
    _rep(i, 1, m) {
     
        int tn = read();
        _for(j, tn) addedge(i, read());
        p[i].first = read();
        p[i].second = i;
    }
    sort(p + 1, p + m + 1);
    LL ans = 0;
    for(int i = m; i > 0; --i) {
     
        if(dfs(p[i].second)) {
     
            ans += p[i].first;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

G 九峰与蛇形填数

oj: 牛客

题意

把从 1 1 1 开始的数字按照 S S S 型填入一个矩形。

在一个初始全 0 0 0 的矩形区域内填入若干个小矩形,每个小矩形填入后会覆盖掉原先的数字。

求出 m m m 次操作后的大矩形的数字。

题解

利用线段树维护每一行的每个区间的小矩形的编号,所有的操作都结束后,再通过每个位置的矩形编号和矩形信息算出这个位置的数字。

时间复杂度: O ( m k   l o g n ) O(mk\ logn) O(mk logn)

代码

#pragma GCC optimize(2)
#include 
#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)
#define nl(i, n) (i == n - 1 ? "\n":" ")
using namespace std;
typedef long long LL;
const int maxn = 2005;

inline int read() {
     
    int 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, k;
    poi() {
     }
    poi(int x, int y, int k): x(x), y(y), k(k) {
     }
};

LL T[maxn][maxn << 2];

void update(int s, int node, int beg, int end, int l, int r, int f) {
     
    if(l <= beg && r >= end) {
     
        T[s][node] = f;
        return;
    }
    if(T[s][node]) {
     
        T[s][node << 1] = T[s][node << 1 | 1] = T[s][node];
        T[s][node] = 0;
    }
    int mid = (beg + end) >> 1;
    if(l <= mid) update(s, node << 1, beg, mid, l, r, f);
    if(r > mid) update(s, node << 1 | 1, mid + 1, end, l, r, f);
    return;
}
LL query(int s, int node, int beg, int end, int pos) {
     
    if (T[s][node] || beg == end) return T[s][node];
    int mid = (beg + end) >> 1;
    if(mid >= pos) return query(s, node << 1, beg, mid, pos);
    else return query(s, node << 1 | 1, mid + 1, end, pos);
}

int n, m;
vector<poi> a;

void init() {
     
    a.clear();
    a.push_back(poi(0, 0, 0));
}

int getval(int v, int i, int j) {
     
    int px = i - a[v].x, py = j - a[v].y;
    int ans = px * a[v].k;
    if(px & 1) ans += a[v].k - py;
    else ans += py + 1;
    return ans;
}

void sol() {
     
    init();
    _rep(i, 1, m) {
     
        int x = read(), y = read(), k = read();
        a.push_back(poi(x, y, k));
        _for(j, k) update(x + j, 1, 1, n, y, y + k - 1, i);
    }
    _rep(i, 1, n) {
     
        _rep(j, 1, n) {
     
            int val = query(i, 1, 1, n, j);
            if(val == 0) printf("0%s", nl(j, n + 1));
            else printf("%d%s", getval(val, i, j), nl(j, n + 1));
        }
    }
}

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

H 吴楚月的表达式

oj: 牛客

题意

给你一颗树,树上每个节点有一个正数,树的每条边有一个运算符,树的每条路径是一个表达式。求出节点 1 1 1 到其他节点的路径的表达式的值。

题解

时间所限,必须仅仅遍历 1 1 1 遍就求出所有表达式的值。

由于表达式只有 + − ∗ / +-*/ +/ 四种运算,而且没有括号,所以任意一个表达式都可以分解为 A + B A+B A+B B B B 为最后面的仅由 ∗ / */ / 构成的表达式, A A A 为剩余部分,遇到减号就变成加上负数。

每个节点的 A + B A+B A+B 仅仅依赖父节点的 A + B A+B A+B,所以我们可以通过一次遍历就求出所有节点的 A + B A+B A+B,然后单独计算出每个节点的表达式的值。

代码

#pragma GCC optimize(2)
#include 
#define m_p make_pair
#define p_i pair
#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)
#define nl(i, n) (i == n - 1 ? "\n":" ")
using namespace std;
typedef long long LL;
const LL maxn = 100005;
const LL mod = 1000000007;

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 {
     
    LL num, op;
    poi() {
     }
    poi(LL num, LL op): num(num), op(op) {
     }
};

LL n, a[maxn];
char s[maxn];
LL fa[maxn];
vector<poi> G[maxn];
LL ans[maxn];
vector<pair<LL, LL> > lastans;

void init() {
     
    lastans.clear();
    _rep(i, 1, n) ans[i] = -1;
}

void exgcd(LL a, LL b, LL &g, LL &x, LL &y) {
     
    if (!b) {
     
        g = a;
        x = 1;
        y = 0;
    } else {
     
        exgcd(b, a % b, g, y, x);
        y -= x * (a / b);
    }
}
LL inv(LL x) {
     
    LL a, k, g;
    exgcd(x, mod, g, a, k);
    if(a < 0) a += mod;
    return a;
}

void dfs(LL u) {
     
    for(auto i : G[u]) {
     
        if(i.op == 1) lastans.push_back(m_p(lastans.back().first + lastans.back().second, a[i.num]));
        else if(i.op == 2) lastans.push_back(m_p(lastans.back().first + lastans.back().second, -a[i.num]));
        else if(i.op == 3) lastans.push_back(m_p(lastans.back().first, lastans.back().second * a[i.num]));
        else lastans.push_back(m_p(lastans.back().first, lastans.back().second * inv(a[i.num])));
        lastans.back().first %= mod;
        lastans.back().second %= mod;
        ans[i.num] = lastans.back().first + lastans.back().second;
        dfs(i.num);
        lastans.pop_back();
    }
}

void sol() {
     
    init();
    _rep(i, 1, n) a[i] = read();
    for(LL i = 2; i <= n; ++i) fa[i] = read();
    scanf("%s", s + 2);
    _rep(i, 2, n) {
     
        if(s[i] == '+') G[fa[i]].push_back(poi(i, 1));
        else if(s[i] == '-') G[fa[i]].push_back(poi(i, 2));
        else if(s[i] == '*') G[fa[i]].push_back(poi(i, 3));
        else if(s[i] == '/') G[fa[i]].push_back(poi(i, 4));
    }
    ans[1] = a[1];
    lastans.push_back(m_p(0, a[1]));
    dfs(1);
    _rep(i, 1, n) {
     
        if(ans[i] < 0) ans[i] += mod;
        else if(ans[i] >= mod) ans[i] %= mod;
    }
    _rep(i, 1, n) printf("%lld%s", (ans[i] + mod) % mod, nl(i, n + 1));
}

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

J 邬澄瑶的公约数

oj: 牛客

题意

求出 gcd ⁡ ( x 1 p 1 , ⋯   , x n p n ) \gcd(x_1^{p_1},\cdots,x_n^{p_n}) gcd(x1p1,,xnpn) 的值。

题解

分别分解出每个数字的质因数以及每个质因数出现的次数,然后取出所有数字的公共质因数并取最小次数构成的数就是答案。

代码

#pragma GCC optimize(2)
#include 
#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 maxn = 100005;
const LL mod = 1000000007;

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;
}

map<LL, LL> mp[2];
LL f;
LL n;
vector<LL> x, p;

vector<LL> arr;
LL vis[100006];
void doit(LL maxnum) {
     
    for(LL i = 2; i <= maxnum; ++i) {
     
        if(!vis[i]) arr.push_back(i);
        for(LL j = 0; j < arr.size() && arr[j] * i <= maxnum; ++j) {
     
            vis[arr[j] * i] = 1;
            if(i % arr[j] == 0) break;
        }
    }
}

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;
}

void sol() {
     
    _for(i, n) x.push_back(read());
    _for(i, n) p.push_back(read());
    for(LL i = 0, tx = x[0]; i < arr.size() && arr[i] <= tx; ++i) {
     
        if(tx % arr[i] == 0) {
     
            LL num = 0;
            while(tx % arr[i] == 0) {
     
                ++num;
                tx /= arr[i];
            }
            mp[f][arr[i]] = num * p[0];
        }
    }
    f ^= 1;
    for(LL i = 1; i < n; ++i) {
     
        mp[f].clear();
        for(LL j = 0, tx = x[i]; j < arr.size() && arr[j] <= tx; ++j) {
     
            if(tx % arr[j] == 0) {
     
                LL num = 0;
                while(tx % arr[j] == 0) {
     
                    ++num;
                    tx /= arr[j];
                }
                if(mp[f ^ 1].count(arr[j])) mp[f][arr[j]] = min(mp[f ^ 1][arr[j]], num * p[i]);
            }
        }
        f ^= 1;
    }
    LL ans = 1;
    f ^= 1;
    for(auto i : mp[f]) {
     
        ans *= quickPow(i.first, i.second, mod);
        ans %= mod;
    }
    printf("%lld\n", ans);
}

int main() {
     
    doit(10000);
    n = read();
    sol();
    return 0;
}

你可能感兴趣的:(线段树,二分图匹配,KMP及扩展KMP)