2021牛客寒假算法基础集训营4 ABEFGHJ题解

链接:https://ac.nowcoder.com/acm/contest/9984#question


听完雨巨的分析后豁然开朗许多%%%, 感觉自己又行了

A 九峰与签到题(签到)

注意一下任意时间的意思,即每个时间都要满足才行

#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 5e5 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9;
int vis[N];
void solve() {
    int n, m; cin >> n >> m;
    map<int, int> mp1; map<int, int> mp2;
    vector<int> ans;
    for (int i = 1; i <= n; i++) {
        int opt;
        string s;
        cin >> opt >> s;
        mp1[opt]++;
        if (s == "AC") mp2[opt]++;
        if (mp1[opt] > 2 * mp2[opt]) {
            vis[opt] = 1;
        }
    }
    int f = 0;
    for (int i = 1; i <= 20; i++) {
        if (!vis[i] && mp1[i]) {
            printf("%d ", i);
            f = 1;
        }
    }
    if (!f) printf("-1\n");
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

B 武辰延的字符串(二分+字符串哈希)

有两个串s和t,我们现在要两个s的前缀拼出t的前缀,最普通的方法就是O2 的暴力枚举,考虑怎么去减少枚举,那自然就是二分了。

先用字符串哈希O(N)处理好两个字符串,然后枚举第一个前缀,我们用二分找第二个前缀最多可以匹配到多少长度为止,因为确定第一个前缀后,方案数就是第二个前缀最长匹配长度。

然后不断枚举第一个前缀,直到失配退出,把结果相加就可以啦。

#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 2e5 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9 + 7;
ull f1[N], f2[N], p[N];
char s1[N], s2[N];
int n, m;
ull Get(ull *f, int l, int r) {
    return f[r] - f[l-1] * p[r - l + 1];
}

unordered_map<ull, int> mp;
void solve() {
    scanf("%s", s1+1);
    scanf("%s", s2+1);
    n = strlen(s1+1); m = strlen(s2+1);
    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        f1[i] = f1[i-1] * 1331 + (s1[i] - 'a' + 1);
        mp[f1[i]] = 1;
        p[i] = p[i-1] * 1331;
    }
    for (int i = 1; i <= m; i++) {
        f2[i] = f2[i-1] * 1331 + (s2[i] - 'a' + 1);
    }
    ll ans = 0;
    for (int i = 1; i <= m; i++) {
        if (s1[i] != s2[i]) break;
        int l = i+1, r = m;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (mp[Get(f2, i+1, mid)]) l = mid + 1;
            else r = mid - 1;
        }
        ans += (r - i);
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

E 九峰与子序列(DP+字符串哈希 | 暴力)

有点像背包的题目,但多了一个判断能不能取的条件
写出状态:f[i][j]代表前i个字符串匹配到前j个位置有多少方案
列出转移方程:f[i][j] = f[i-1][j] + f[i-1][j-len] * (hash[j-len+1, j] == 串i)
但这样会mle,所以用滚动数组优化一下就好了

#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 5e6 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9 + 7;
char s1[N], s2[N];
ull f[N], p[N];
ll dp[N];
ull Get(int l, int r) {
    return f[r] - f[l-1] * p[r - l + 1];
}
void solve() {
    int n, len; cin >> n >> s1+1;
    len = strlen(s1+1);
    p[0] = 1;
    for (int i = 1; i <= len; i++) {
        f[i] = f[i-1] * 1331 + (s1[i] - 'a' + 1);
        p[i] = p[i-1] * 1331;
    }
    dp[0] = 1;
    for (int i = 1; i <= n; i++) {
        cin >> s2 + 1;
        int l = strlen(s2+1);
        ull tmp = 0;
        for (int j = 1; j <= l; j++) tmp = tmp * 1331 + (s2[j] - 'a' + 1);
        for (int j = len; j >= l; j--) {
            dp[j] += dp[j-l] * (Get(j-l+1, j) == tmp);
        }
    }
    cout << dp[len] << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

F 魏迟燕的自走棋(贪心+并查集)

和之前牛客多校的一道签到题很像,当时是用HK冲过去的,std应该是并查集。

这题也是用并查集维护,思维非常巧妙。

我们将每个人看成一个点,武器的能力值抽象成边,这样就转化成图论的模型了。

我们从联通块角度去看,如果一个联通块还没有形成环,那么他们之间的武器是可以互相换的,即他们当中的任意点都可以拥有新武器,我们称这个联通块没有饱和。反之,如果联通块内有环,那么这个块中的点都不能再拥有别的武器了我们称这个联通块饱和。

接下来我们分类讨论一下所有询问的情况:

  1. 两个点属于同一个联通块,如果这个联通块是饱和的,就略过;如果这个联通块不饱和,就加上当前武器的能力值并把联通块设置成饱和。
  2. 两个点属于不同的联通块,如果其中一个点是饱和的,那就将合并后的联通块设成饱和;如果都不是饱和,合并后也是不饱和;如果两个都是饱和的,就略过。
#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 2e5 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9 + 7;
struct Query {
    int x, y, w;
    int id;
    bool operator < (const Query& rhs) const {
        return w > rhs.w;
    }
}q[N];
int fa[N], flag[N];
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
void solve() {
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; i++) fa[i] = i, flag[i] = 0;
    for (int i = 1; i <= m; i++) {
        int k; cin >> k;
        if (k == 1) {
            cin >> q[i].x >> q[i].w;
            q[i].y = q[i].x;
        } else {
            cin >> q[i].x >> q[i].y >> q[i].w;
        }
    }
    sort(q+1, q+m+1);
    ll ans = 0;
    for (int i = 1; i <= m; i++) {
        int fx = find(q[i].x), fy = find(q[i].y);
        if (fx == fy) {
            if (!flag[fx]) {
                ans += q[i].w;
                flag[fx] = 1;
            }
        } else {
            if (flag[fx] && flag[fy]) continue;
            else if (!flag[fx] && !flag[fy]) {
                ans += q[i].w;
                fa[fx] = fy;
            } else {
                ans += q[i].w;
                fa[fx] = fy;
                flag[fy] = 1;
            }
        }
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

G 九峰与蛇形填数(线段树)

听说很多人都是暴力+剪枝过的,而且std还卡常

因为我们只要知道最后覆盖的是什么就可以,所以用二维的线段树去区间覆盖,N*Nlog(N)的复杂度。

最后我们可以通过起始位置和k, O(1)推出当前位置的值(分奇偶考虑一下,简单推一下式子),也不多说,具体看代码。

#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 2000 + 10;
const int M = 3000 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9 + 7;
struct node {
    struct tree {
        int l, r;
        int lazt, sum;
    }t[N<<2];
    void push_down(int u) {
        if (t[u].lazt) {
            t[u<<1].lazt = t[u<<1|1].lazt = t[u].lazt;
            t[u<<1].sum = t[u<<1|1].sum = t[u].lazt;
            t[u].lazt = 0;
        }
    }
    void build(int u, int l, int r) {
        t[u].l = l, t[u].r = r;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(u<<1, l, mid);
        build(u<<1|1, mid+1, r);
    }
    void modify(int u, int ql, int qr, int val) {
        if (ql <= t[u].l && qr >= t[u].r) {
            t[u].lazt = val;
            t[u].sum = val;
            return;
        }
        push_down(u);
        int mid = (t[u].l + t[u].r) >> 1;
        if (ql <= mid) modify(u<<1, ql, qr, val);
        if (qr > mid) modify(u<<1|1, ql, qr, val);
    }
    int query(int u, int pos) {
        if (t[u].l == t[u].r) return t[u].sum;
        int mid = (t[u].l + t[u].r) >> 1;
        push_down(u);
        if (pos <= mid) return query(u<<1, pos);
        else return query(u<<1|1, pos);
    }
}tree[2005];
int a[M], b[M], k[M];
inline int bf(int x, int y, int ai, int bi, int ki) {
    int tmp = x - ai + 1;
    if (tmp % 2 == 1) return tmp * ki - (bi+ki-1-y);
    else return (tmp-1)*ki+1 + (bi+ki-1-y);
}
void solve() {
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; ++i) tree[i].build(1, 1, n);
    for (int i = 1; i <= m; ++i) {
        cin >> a[i] >> b[i] >> k[i];
        for (int j = a[i]; j <= a[i]+k[i]-1; ++j) {
            tree[j].modify(1, b[i], b[i]+k[i]-1, i);
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            int cnt = tree[i].query(1, j);
            if (cnt == 0) printf("0 ");
            else printf("%d ", bf(i, j, a[cnt], b[cnt], k[cnt]));
        }
        printf("\n");
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

H 吴楚月的表达式(模拟)

因为只有+ - * / 四种运算,所以这里可以用到一个小的trick。

我们把当前节点的值看成是 a + b

假设当前运算符为x,下一个节点值为a[v]

  1. x为 ‘+’ : a的值变成a+b,b的值变为a[v]
  2. x为 ‘-’ : a的值变成a+b,b的值变为-a[v]
  3. x为 '’ : a的值仍为a,b的值变为ba[v]
  4. x为 ‘/’ : a的值仍为a,b的值变为b/a[v]
#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 2e5 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9 + 7;
int n, cnt, h[N], a[N], fa[N];
ll ans[N];
struct Edge {
    int to, next;
    char w;
}e[M];
void add(int u, int v, char w) {
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = h[u];
    h[u] = cnt++;
}
ll ksm(ll a, ll b) {
    ll res = 1, base = a;
    while (b) {
        if (b & 1) res = res * base % MOD;
        base = base * base % MOD;
        b >>= 1;
    }
    return res;
}
void dfs(int u, int far, ll pre, ll now) {
    for (int i = h[u]; ~i; i = e[i].next) {
        int v = e[i].to;
        if (v == far) continue;
        if (e[i].w == '+') dfs(v, u, (now+pre+MOD)%MOD, a[v]);
        else if (e[i].w == '-') dfs(v, u, (now+pre+MOD)%MOD, -a[v]);
        else if (e[i].w == '*') dfs(v, u, pre, now*a[v]%MOD);
        else dfs(v, u, pre, now*ksm(a[v], MOD-2)%MOD);
    }
    ans[u] = (pre+now+MOD)%MOD;
}
void solve() {
    cin >> n; memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 2; i <= n; i++) cin >> fa[i];
    for (int i = 2; i <= n; i++) {
        char c; cin >> c;
        add(i, fa[i], c);
        add(fa[i], i, c);
    }
    dfs(1, 0, 0, a[1]);
    for (int i = 1; i <= n; i++) printf("%lld%c", ans[i], i == n ? '\n' : ' ');
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

J 邬澄瑶的公约数(质因数分解)

我们知道求最大公因数可以转化为公共质因子之积

把所有数质因数分解一下,然后求出最小的公共质因子之积

#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
#define fi first
#define se second
#define il inline
#define re register
typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;
const int N = 1e4 + 10;
const int M = 5e5 + 10;
const ll INF = 1e18;
const double eps = 1e-5;
const int MOD = 1e9+7;
ll ksm(ll a, ll b) {
    ll res = 1, base = a % MOD;
    while (b) {
        if (b & 1) res = res * base % MOD;
        base = base * base % MOD;
        b >>= 1;
    }
    return res % MOD;
}
int prime[N], k = 0;
bool is_prime[N];
void get_prime(){
    memset(is_prime, true, sizeof is_prime);
    is_prime[0] = is_prime[1] = false;
    for(int i = 2 ; i < N;i++){
        if (is_prime[i]) prime[++k] = i;
        for(int j = 1; j <= k && i * prime[j] < N;j++){
            is_prime[i * prime[j]] = false;      //得到合数的最小质因子
            if(i % prime[j] == 0) break;
        }
    }
}
ll a[N], b[N], Min[N];
void get_fac(int x, int j) {
    for (int i = 1; i <= k; i++) {
        //if (prime[i] * prime[i] > x) break;
        int cnt = 0;
        while (x % prime[i] == 0) x /= prime[i], cnt++;
        Min[i] = min(Min[i], cnt * b[j]);
    }
}
void solve() {
    get_prime();
    int n; cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= k; i ++) Min[i] = INF;
    for (int i = 1; i <= n; i++) {
        get_fac(a[i], i);
    }
    ll ans = 1;
    for (int i = 1; i <= k; i++) {
        if (Min[i] != INF)
            ans = ans * ksm(prime[i], Min[i]) % MOD;
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值