牛客小白月赛11题解简录

题目传送

官方题解

A.官方说是随机数据所以暴力线段树……

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;
int N, L;

class SegmentTree {
public:
    #define ls(p) p << 1
    #define rs(p) p << 1 | 1
    struct Node {
        int l, r, minn, tag;
    }t[maxn << 2];
    
    void Push_up(int p) {
        t[p].minn = min(t[ls(p)].minn, t[rs(p)].minn);
    }
    
    void Push_down(int p) {
        if (t[p].tag) {
            t[ls(p)].minn += t[p].tag;
            t[rs(p)].minn += t[p].tag;
            t[ls(p)].tag += t[p].tag;
            t[rs(p)].tag += t[p].tag;
            t[p].tag = 0;
        }
    }
    
    void Build(int l, int r, int p) {
        t[p].l = l, t[p].r = r;
        if (l == r) {
            t[p].minn = t[p].tag = 0;
            return;
        }
        int mid = (l + r) >> 1;
        Build(l, mid, ls(p));
        Build(mid + 1, r, rs(p));
    }
    
    void Modify(int l, int r, int p, int k) {
        if (l <= t[p].l && t[p].r <= r) {
            t[p].minn += k;
            t[p].tag += k;
            return;
        }
        Push_down(p);
        int mid = (t[p].l + t[p].r) >> 1;
        if (l <= mid)    Modify(l, r, ls(p), k);
        if (mid < r)    Modify(l, r, rs(p), k);
        Push_up(p);
    }
    
    int Query(int l, int r, int p) {
        if (l <= t[p].l && t[p].r <= r && t[p].minn > 0)    return t[p].r - t[p].l + 1;
        if (t[p].l == t[p].r)    return t[p].minn > 0;
        Push_down(p);
        int mid = (t[p].l + t[p].r) >> 1;
        if (l > mid)    return Query(l, r, rs(p));
        if (r <= mid)    return Query(l, r, ls(p));
        return Query(l, r, ls(p)) + Query(l, r, rs(p));
    }
}T;

map<pair<int, int>, int> mp;

int main() {
    scanf("%d %d", &N, &L);
    T.Build(1, L + 1, 1);
    for (int i = 0; i < N; i++) {
        int op, x, y;
        scanf("%d %d %d", &op, &x, &y);
        x++, y++;
        if (x > y)    swap(x, y);
        if (op == 1 && !mp[make_pair(x, y)]) {
            T.Modify(x, y, 1, 1);
            mp[make_pair(x, y)]++;
        } else if (op == 2 && mp[make_pair(x, y)]) {
            T.Modify(x, y, 1, -1);
            mp[make_pair(x, y)]--;
        } else if (op == 3) {
            cout << T.Query(1, L + 1, 1) << endl;
        }
    }

    return 0;
}
A

B.方法是拆点,把每个点都拆成k+1个,表示已经使用过k(k属于0~K)个戒严到达此点。连边很关键。

1.连有向边,如果u本身是戒严的,则枚举u + k * n,连向v + (k + 1) * n,意义是到u用了k个,则想到达v就得用k + 1个了。否则u + k * n连向v + k * n即可,意义为不增加使用次数。而且如果u是戒严的,则k == K的个点根本不用连了,不可能再前进一步。从v向u连的也是一样的。

2.设立虚的起始点s和终点t,s只要和点1连接即可,因为s到点1(等价类)路过的戒严点不可能比0大。这也意味着其实我们连了这么多条边,大部分边其实都是与主图剥离的,也就是真正跑的时候根本没机会用到。而终点t就不同了,由于无法人为判定来“剪枝”,所以0~K都要与t相连。

3.虽然边多了点,dijkstra会被卡常可还行?不是说命题人会卡spfa吗……

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long ll;
 5 const ll INF = 2e18;
 6 const int N = 805, M = 4005;
 7 
 8 int n, m, k, hold[N], tot, s, t;
 9 ll dis[N * 11];
10 bool vis[N * 11];
11 int head[N * 11];
12 struct Edge {
13     int to, nxt;
14     ll cost;
15 }e[M * 22];
16 
17 void Add(int u, int v, ll cost) {
18     e[++tot].to = v, e[tot].nxt = head[u], e[tot].cost = cost, head[u] = tot;
19 }
20 
21 void Get_Graph() {
22     for (int i = 1; i <= m; i++) {
23         int u, v;
24         ll cost;
25         scanf("%d %d %lld", &u, &v, &cost);
26         if (!hold[u]) {
27             for (int j = 0; j <= k; ++j) {
28                 Add(u + j * n, v + j * n, cost);
29             }
30         }
31         if (!hold[v]) {
32             for (int j = 0; j <= k; ++j) {
33                 Add(v + j * n, u + j * n, cost);
34             }
35         }
36         if (hold[u]) {
37             for (int j = 0; j < k; ++j) {
38                 Add(u + j * n, v + (j + 1) * n, cost);
39             }
40         }
41         if (hold[v]) {
42             for (int j = 0; j < k; ++j) {
43                 Add(v + j * n, u + (j + 1) * n, cost);
44             }
45         }
46     }
47 
48     s = 0, t = n * (k + 1) + 1;
49     Add(s, 1, 0);
50     for (int i = 0; i <= k; i++) {
51         Add(n + i * n, t, 0);
52     }
53 }
54 
55 ll SPFA() {
56     for (int i = s; i <= t; i++)
57         dis[i] = INF, vis[i] = false;
58     dis[s] = 0;
59     vis[s] = true;
60     
61     queue<int> Q;
62     Q.push(s);
63     
64     while (Q.size()) {
65         int x = Q.front(); Q.pop();
66         vis[x] = false;
67         
68         for (int i = head[x]; i; i = e[i].nxt) {
69             int to = e[i].to;
70             if (dis[to] > dis[x] + e[i].cost) {
71                 dis[to] = dis[x] + e[i].cost;
72                 if (!vis[to])    Q.push(to);
73             }
74         }
75     }
76     
77     return dis[t];
78 }
79 
80 int main() {
81     scanf("%d %d %d", &n, &m, &k);
82     for (int i = 1; i <= n; i++)
83         scanf("%d", &hold[i]);
84     Get_Graph();
85     
86     ll ans = SPFA();
87     printf("%lld
", ans == INF ? -1 : ans);
88     
89     return 0;
90 }
B

 C.签到。模拟一下即可。话说我为啥要开个map强行提高复杂度……

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 int n, m, T;
 5 map<int, int> row, column;
 6 
 7 int main() {
 8     scanf("%d %d %d", &n, &m, &T);
 9     vector<vector<int>> v(n, vector<int>(m, 0));
10     
11     for (int i = 1; i <= T; ++i) {
12         int op, x;
13         scanf("%d %d", &op, &x);
14         x--;
15         if (op == 1)    row[x] = i;
16         else    column[x] = i;
17     }
18     
19     for (int i = 0; i < n; i++)
20         for (int j = 0; j < m; j++) {
21             if (row[i])    v[i][j] = max(v[i][j], row[i]);
22             if (column[j])    v[i][j] = max(v[i][j], column[j]);
23             printf("%d%c", v[i][j], " 
"[j == m - 1]);
24         }
25     
26     return 0;
27 }
C

D.正常地二叉搜索树去做会被有序卡成n方。考虑记录id后的数字排序,可以像中序遍历一样,先找到[l, r]中最早出现的那个,它的ans就是当前深度,然后两边进行分治解决。但如果暴力扫一遍去寻找最早出现的,还是n方的,所以用ST表预处理一下区间最小值。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn = 3e5 + 5;
 5 int N;
 6 long long Ans[maxn];
 7 struct V {
 8     int val, id;
 9     bool operator < (const V &rhs) const {
10         return val < rhs.val;
11     }
12 }a[maxn];
13 int ST[maxn][20], pos[maxn];
14 
15 void Pre() {
16     for (int i = 1; i <= N; i++)
17         ST[i][0] = a[i].id;
18     for (int j = 1; (1 << j) <= N; j++) {
19         for (int i = 1; i + (1 << j) - 1 <= N; i++) {
20             ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j-1))][j - 1]);
21         }
22     }
23     for (int i = 1; i <= N; i++)
24         pos[a[i].id] = i;
25 }
26 
27 int Query(int l, int r) {
28     int j = log2(r - l + 1);
29     return min(ST[l][j], ST[r - (1 << j) + 1][j]);
30 }
31 
32 void Divide(int l, int r, int depth) {
33     if (l > r)    return;
34     
35     int id = Query(l, r);
36     Ans[id] = depth;
37     Divide(l, pos[id] - 1, depth + 1);
38     Divide(pos[id] + 1, r, depth + 1);
39 }
40 
41 int main() {
42     scanf("%d", &N);
43     for (int i = 1; i <= N; ++i) {
44         scanf("%d", &a[i].val);
45         a[i].id = i;
46     }
47     sort(a + 1, a + 1 + N);
48     Pre();
49     Divide(1, N, 0);
50     for (int i = 1; i <= N; ++i) {
51         Ans[i] += Ans[i - 1];
52         printf("%lld
", Ans[i]);
53     }
54     return 0;
55 }
D

E.命题人本意是个裸的01分数规划,我读错题了,以为默认1是起点,其实是谁做起点都行只要有环路即可。因此就是二分里面套个判负环存在即可。

 1 #include <bits/stdc++.h>
 2 #define fi first
 3 #define se second
 4 using namespace std;
 5 
 6 typedef double db;
 7 const int maxn = 2005;
 8 const db eps = 1e-4;
 9 int n, m, vis[maxn];
10 vector<pair<int, db>> adj[maxn];
11 db dis[maxn];
12 
13 bool dfs(int cur, db ans) {
14     vis[cur] = 1;
15     for (auto i : adj[cur]) {
16         if (dis[i.fi] > dis[cur] + i.se - ans) {
17             dis[i.fi] = dis[cur] + i.se - ans;
18             if (vis[i.fi] || dfs(i.fi, ans))
19                 return vis[cur] = 0, 1;
20         }
21     }
22     return vis[cur] = 0;
23 }
24 
25 bool ok(db mid) {
26     memset(dis, 0, sizeof(dis));
27     for (int i = 1; i <= n; i++)
28         if (dfs(i, mid))
29             return 1;
30     return 0;
31 }
32 
33 int main() {
34     scanf("%d %d", &n, &m);
35     for (int i = 0; i < m; i++) {
36         int u, v; db c;
37         scanf("%d %d %lf", &u, &v, &c);
38         adj[u].push_back({v, c});
39     }
40     db l = 0, r = 1e9 + 5;
41     while (r - l > eps) {
42         db mid = (l + r) / 2.0;
43         if (ok(mid))    r = mid;
44         else    l = mid;
45     }
46     if (l <= 1e9)    printf("%.2lf
", l);
47     else    puts("Rinne is cute");
48 }
E

F.树根和树叶不连通的最小代价,dfs并且dp处理一下,选min(cur-fa的cost, son的和)。

 1 #include <bits/stdc++.h>
 2 #define pb push_back
 3 #define fi first
 4 #define se second
 5 using namespace std;
 6 
 7 typedef long long ll;
 8 typedef pair<int, ll> pil;
 9 const int maxn = 1e5 + 5;
10 int N, M, S;
11 vector<pil> adj[maxn];
12 ll f[maxn];
13 
14 void dfs(int cur, int fa) {
15     ll res = 0LL;
16     for (auto i : adj[cur]) {
17         if (i.fi == fa) {
18             if (cur != S)    f[cur] = i.se;
19             continue;
20         }
21         dfs(i.fi, cur);
22         res += f[i.fi];
23     }
24     if (cur == S || (adj[cur].size() > 1 && f[cur] > res))    f[cur] = res;
25 }
26 
27 int main() {
28     scanf("%d %d %d", &N, &M, &S);
29     for (int i = 0; i < M; i++) {
30         int u, v;
31         ll cost;
32         scanf("%d %d %lld", &u, &v, &cost);
33         adj[u].pb({v, cost});
34         adj[v].pb({u, cost});
35     }
36     dfs(S, 0);
37     printf("%lld
", f[S]);
38     return 0;
39 }
F

G.每读入一个数,二进制枚举其素因子(不超过7个)的组合,容斥修改答案即可。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn = 2e5 + 5, maxa = 5e5 + 5;
 5 int N, opt, x, ans;
 6 int primes[maxa], v[maxa], tot;
 7 int num[maxa];
 8 bool in[maxa];
 9 
10 void pre() {
11     for (int i = 2; i < maxa; i++) {
12         if (!v[i]) {
13             primes[tot++] = i;
14             v[i] = i;
15         }
16         for (int j = 0; j < tot && (long long)primes[j] * i < maxa; j++) {
17             v[primes[j] * i] = primes[j];
18             if (i % primes[j] == 0)    break;
19         }
20     }
21 }
22 
23 int main() {
24     pre();
25     scanf("%d", &N);
26     while (N--) {
27         scanf("%d %d", &opt, &x);
28         if (opt == 1 && in[x])    continue;
29         if (opt == 2 && !in[x])    continue;
30         if (opt == 3) {
31             printf("%d
", ans);
32             continue;
33         }
34         int que[10], tail = 0, t = x;
35         while (v[t]) {
36             int k = v[t];
37             que[tail++] = k;
38             while (t % k == 0)    t /= k;
39         }
40         for (int i = 0; i < (1 << tail); i++) {
41             int tmp = 1, cnt = 0;
42             for (int j = 0; j < tail; j++) {
43                 if ((1 << j) & i) {
44                     cnt++;
45                     tmp *= que[j];
46                 }
47             }
48             if (opt == 1) {
49                 ans += cnt & 1 ? -num[tmp] : num[tmp];
50                 num[tmp]++;
51             } else {
52                 num[tmp]--;
53                 ans += cnt & 1 ? num[tmp] : -num[tmp];
54             }
55         }
56         in[x] ^= 1;
57     }
58     return 0;
59 }
G

 G的方法二是式子稍微改一改弄成莫比乌斯函数的形式,然后枚举当前读入x的因数d,视为x是d的倍数,更新答案即可。factor不能预处理会被卡掉。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn = 2e5 + 5, maxa = 5e5 + 5;
 5 int N, opt, x, ans;
 6 int primes[maxa], mu[maxa], tot;
 7 int num[maxa];
 8 bool in[maxa], vis[maxa];
 9 
10 void pre() {
11     mu[1] = 1;
12     for (int i = 2; i < maxa; i++) {
13         if (!vis[i]) {
14             primes[tot++] = i;
15             mu[i] = -1;
16         }
17         for (int j = 0; j < tot && (long long)primes[j] * i < maxa; j++) {
18             vis[primes[j] * i] = true;
19             if (i % primes[j] == 0)    break;
20             mu[primes[j] * i] = -mu[i];
21         }
22     }
23 }
24 
25 void Update(int i, int opt) {
26     ans -= (long long)num[i] * (num[i] - 1) / 2 * mu[i];
27     num[i] += opt == 1 ? 1 : -1;
28     ans += (long long)num[i] * (num[i] - 1) / 2 * mu[i];
29 }
30 
31 int main() {
32     pre();
33     scanf("%d", &N);
34     while (N--) {
35         scanf("%d %d", &opt, &x);
36         if (opt == 1 && in[x])    continue;
37         if (opt == 2 && !in[x])    continue;
38         if (opt == 3) {
39             printf("%d
", ans);
40             continue;
41         }
42         for (int i = 1; i * i <= x; i++) {
43             if (x % i == 0) {
44                 Update(i, opt);
45                 if (x / i != i)    Update(x / i, opt);
46             }
47         }
48         in[x] ^= 1;
49     }
50     return 0;
51 }
Gmu函数版

H.边权只有三种情况所以拆点跑最短路即可。

 1 #include <bits/stdc++.h>
 2 #define pb push_back
 3 #define fi first
 4 #define se second
 5 using namespace std;
 6 
 7 typedef double db;
 8 typedef pair<db, int> pdi;
 9 const db INF = 1e18;
10 int N, M, st, ed;
11 vector<pdi> adj[300005];
12 db dis[300005];
13 
14 db dij(int st, int ed) {
15     for (int i = st; i <= ed; i++)    dis[i] = INF;
16     dis[st] = 0.0;
17     priority_queue<pdi, vector<pdi>, greater<pdi>> Q;
18     Q.push({0.0, st});
19     while (Q.size()) {
20         auto x = Q.top(); Q.pop();
21         if (dis[x.se] < x.fi)    continue;
22         for (auto i : adj[x.se]) {
23             if (dis[i.se] > dis[x.se] + i.fi) {
24                 dis[i.se] = dis[x.se] + i.fi;
25                 Q.push({dis[i.se], i.se});
26             }
27         }
28     }
29     return dis[ed];
30 }
31 
32 void add(int u, int v, db cost) {
33     adj[u].pb({fabs(cost), v + N});
34     adj[u + N].pb({fabs(1.0 / (1.0 - cost)), v + N * 2});
35     adj[u + N * 2].pb({fabs(1 - 1.0 / cost), v});
36 }
37 
38 int main() {
39     scanf("%d %d", &N, &M);
40     for (int i = 0; i < M; i++) {
41         int u, v; db cost;
42         scanf("%d %d %lf", &u, &v, &cost);
43         add(u, v, cost); add(v, u, cost);
44     }
45     st = 0, ed = N * 3 + 1;
46     adj[st].pb({0.0, 1});
47     for (int i = 0; i < 3; i++) {
48         adj[N + N * i].pb({0.0, ed});
49     }
50     
51     db ans = dij(st, ed);
52     if (ans < INF)    printf("%.3lf
", ans);
53     else    puts("-1");
54     return 0;
55 }
H

I.第一步是把C的求法递推式改一改,是全部的A1~i、B1~i的异或和;第二步按位求贡献。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long ll;
 5 const int mod = 1e9 + 7;
 6 int n;
 7 int cntA[2][32], cntB[2][32], Pow[32];
 8 
 9 int main() {
10     scanf("%d", &n);
11     vector<int> A(n), B(n);
12     for (int i = 0; i < 32; i++) {
13         Pow[i] = i ? 2LL * Pow[i - 1] % mod : 1;
14     }
15     for (int i = 0; i < n; i++) {
16         scanf("%d", &A[i]);
17     }
18     for (int i = 0; i < n; i++) {
19         scanf("%d", &B[i]);
20     }
21     for (int i = 0; i < n; i++) {
22         int a = A[i], b = B[i], tmp = 0;
23         for (int j = 0; j < 32; j++) {
24             cntA[a % 2][j]++;
25             cntB[b % 2][j]++;
26             a /= 2, b /= 2;
27             tmp = ((ll)tmp + (ll)Pow[j] * ((ll)cntA[0][j] * cntB[1][j] % mod + (ll)cntA[1][j] * cntB[0][j] % mod) % mod) % mod;
28         }
29         printf("%d ", tmp);
30     }
31     return 0;
32 }
I

 J.签到。唯一分解一下或者暴力sqrt都可以

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 int T, n, a, b;
 5 
 6 void solve(int n) {
 7     vector<int> factor, cnt;
 8     for (int i = 2; i * i <= n; ++i) {
 9         if (n % i == 0) {
10             factor.push_back(i);
11             cnt.push_back(0);
12             while (n % i == 0) {
13                 n /= i;
14                 cnt.back()++;
15             }
16         }
17     }
18     if (n > 1)    factor.push_back(n), cnt.push_back(1);
19     
20     a = 1, b = 1;
21     for (int i = 0; i < factor.size(); i++) {
22         int d = factor[i], p = cnt[i] / 2;
23         a *= pow(d, p);
24         if (cnt[i] & 1)    b *= d;
25     }
26 }
27 
28 int main() {
29     for (scanf("%d", &T); T; T--) {
30         scanf("%d", &n);
31         if (n < 0)    puts("-1");
32         else {
33             solve(n);
34             printf("%d %d
", a, b);
35         }
36     }
37     return 0;
38 }
J
原文地址:https://www.cnblogs.com/AlphaWA/p/10651045.html