练习选讲(2023.10)

发布时间 2023-10-20 23:26:19作者: Jasper08

10 月

10.1

P2572 [SCOI2010] 序列操作:线段树(紫)

维护每个区间的两个懒标记,\(0/1\) 的数量、左端点起 \(0/1\) 的数量、右端点起 \(0/1\) 的数量、区间内最大连续 \(0/1\) 长度即可。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e5+10;

int n, m, a[N];

struct Node {
    int l, r, tag1=-1, tag2=0;
    int cnt0=0, cnt1=0, l0=0, l1=0, r0=0, r1=0, m0=0, m1=0;
} seg[N<<2];

void pushup(Node &U, Node &L, Node &R) {
    U.cnt0 = L.cnt0+R.cnt0, U.cnt1 = L.cnt1+R.cnt1;
    U.l0 = L.cnt1?L.l0:L.l0+R.l0, U.l1 = L.cnt0?L.l1:L.l1+R.l1;
    U.r0 = R.cnt1?R.r0:R.r0+L.r0, U.r1 = R.cnt0?R.r1:R.r1+L.r1;
    U.m0 = max(max(L.m0, R.m0), L.r0+R.l0), U.m1 = max(max(L.m1, R.m1), L.r1+R.l1);
}

void pushup(int u) {
    pushup(seg[u], seg[u<<1], seg[u<<1|1]);
}

void modify(Node &U, int op) {
    if (op == 0) U.tag1=U.tag2=0, U.cnt0=U.l0=U.r0=U.m0=U.r-U.l+1, U.cnt1=U.l1=U.r1=U.m1=0;
    else if (op == 1) U.tag1=1, U.tag2=0, U.cnt0=U.l0=U.r0=U.m0=0, U.cnt1=U.l1=U.r1=U.m1=U.r-U.l+1;
    else U.tag2 ^= 1, swap(U.cnt0, U.cnt1), swap(U.l0, U.l1), swap(U.r0, U.r1), swap(U.m0, U.m1);
}

void pushdown(Node &U, Node &L, Node &R) {
    if (U.tag1 != -1) modify(L, U.tag1), modify(R, U.tag1), U.tag1 = -1;
    if (U.tag2) modify(L, 2), modify(R, 2), U.tag2 = 0;
}

void pushdown(int u) {
    pushdown(seg[u], seg[u<<1], seg[u<<1|1]);
}

void build(int u, int l, int r) {
    seg[u].l = l, seg[u].r = r;
    if (l == r) {
        if (a[l]) seg[u].cnt1 = seg[u].l1 = seg[u].r1 = seg[u].m1 = 1;
        else seg[u].cnt0 = seg[u].l0 = seg[u].r0 = seg[u].m0 = 1;
        return ;
    }
    int mid = l + r >> 1;
    build(u<<1, l, mid), build(u<<1|1, mid+1, r);
    pushup(u);
}

void modify(int u, int l, int r, int op) {
    if (seg[u].l >= l && seg[u].r <= r) {modify(seg[u], op); return ;}
    pushdown(u);
    int mid = seg[u].l + seg[u].r >> 1;
    if (l <= mid) modify(u<<1, l, r, op);
    if (r > mid) modify(u<<1|1, l, r, op);
    pushup(u);
}

Node query(int u, int l, int r) {
    if (seg[u].l >= l && seg[u].r <= r) return seg[u];
    pushdown(u);
    int mid = seg[u].l + seg[u].r >> 1; Node res, L, R;
    if (l <= mid) L = query(u<<1, l, r);
    if (r > mid) R = query(u<<1|1, l, r);
    pushup(res, L, R);
    return res;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

    build(1, 1, n);
    int op, l, r;
    while (m -- ) {
        scanf("%d%d%d", &op, &l, &r); l ++, r ++;
        if (op == 0) modify(1, l, r, 0);
        else if (op == 1) modify(1, l, r, 1);
        else if (op == 2) modify(1, l, r, 2);
        else if (op == 3) printf("%d\n", query(1, l, r).cnt1);
        else printf("%d\n", query(1, l, r).m1);
    }
    return 0;
}

10.2

ABC249F. Ignore Operations:堆,贪心(绿)

可以发现,进行一次 1 操作后,前面的所有操作都相当于作废了。我们考虑倒着做,维护当前删除 2 操作后得到的结果最大值 \(sum\),对于每个 1 操作我们考虑是否删除,若不删除则 \(ans=\max(ans,a_i+sum)\),否则 \(k\leftarrow k-1\)。对于每个 2 操作,若 \(a_i\ge 0\) 则一定不劣,否则我们可以将其跳过。当然,我们有可能删去 \(>k\) 次操作,所以我们用一个堆维护当前删去了哪些 2 操作,若堆的大小 \(>k\) 则不断取出堆头加到 \(sum\) 中。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n, k, op[N], a[N]; ll ans = -9e18, sum;
priority_queue<int> q;

int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%d%d", &op[i], &a[i]);
    for (int i = n; i >= 1 && k >= 0; --i) {
        if (op[i] == 1) ans = max(ans, sum+a[i]), k --;
        else {
            if (a[i] >= 0) sum += a[i];
            else q.push(a[i]);
        }
        while ((int)q.size() > k) sum += q.top(), q.pop();
    }
    ans = max(ans, sum);
    printf("%lld\n", ans);
    return 0;
}

SP13015. CNTPRIME - Counting Primes:珂朵莉树,质数筛(黄)

两个板子。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

const int N = 1e6+10;

int T, n, m, a[N];
int idx, prime[N]; bool st[N];

struct Node {
    int l, r; mutable int v;
    Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
    bool operator < (const Node &T) const {
        return l < T.l;
    }
};

typedef set<Node>::iterator iter;
set<Node> s;

iter split(int pos) {
    iter it = s.lower_bound(Node(pos)); 
    if (it != s.end() && it->l == pos) return it;
    it --;
    if (it->r < pos) return s.end();
    int l = it->l, r = it->r, v = it->v;
    s.erase(it), s.insert(Node(l, pos-1, v));
    return s.insert(Node(pos, r, v)).first;
}   

void assign(int l, int r, int v) {
    iter itr = split(r+1), itl = split(l);
    s.erase(itl, itr), s.insert(Node(l, r, v));
}

int query(int l, int r) {
    int cnt = 0; iter itr = split(r+1), itl = split(l);
    for (iter it = itl; it != itr; ++it) if (!st[it->v]) cnt += it->r-it->l+1;
    return cnt;
}


void get_prime() {
    st[0] = st[1] = 1;
    for (int i = 2; i <= 1000000; ++i) {
        if (!st[i]) prime[++ idx] = i;
        for (int j = 1; j <= idx && prime[j] <= 1000000/i; ++j) {
            st[i*prime[j]] = 1;
            if (i % prime[j] == 0) break; 
        }
    }
}

void solve() {
    scanf("%d%d", &n, &m); s.clear();
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), s.insert(Node(i, i, a[i]));
    int op, l, r, v;
    while (m -- ) {
        scanf("%d%d%d", &op, &l, &r);
        if (op == 0) scanf("%d", &v), assign(l, r, v);
        else printf("%d\n", query(l, r));
    }
}

int main() {
    scanf("%d", &T); get_prime();
    for (int i = 1; i <= T; ++i) printf("Case %d:\n", i), solve();
    return 0;
}

P1558 色板游戏:线段树(绿)

注意到颜色数很小,可以状压表示。时间复杂度 \(O(nk\log n)\)

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e5+10;

int n, t, m;

struct Node {
    int l, r, s = 0, tag = 0;
} seg[N<<2];

void pushup(Node &U, Node &L, Node &R) {
    U.s = L.s | R.s;
}

void pushup(int u) {
    pushup(seg[u], seg[u<<1], seg[u<<1|1]);
}

void modify(Node &U, int c) {
    U.s = U.tag = c;
}

void pushdown(Node &U, Node &L, Node &R) {
    if (!U.tag) return ;
    modify(L, U.tag), modify(R, U.tag), U.tag = 0;
} 

void pushdown(int u) {
    pushdown(seg[u], seg[u<<1], seg[u<<1|1]);
}

void build(int u, int l, int r) {
    seg[u].l = l, seg[u].r = r;
    if (l == r) {seg[u].s = 1; return ;}
    int mid = l + r >> 1;
    build(u<<1, l, mid), build(u<<1|1, mid+1, r);
    pushup(u);
}

void modify(int u, int l, int r, int c) {
    if (seg[u].l >= l && seg[u].r <= r) {modify(seg[u], c); return ;}
    pushdown(u);
    int mid = seg[u].l + seg[u].r >> 1;
    if (l <= mid) modify(u<<1, l, r, c);
    if (r > mid) modify(u<<1|1, l, r, c);
    pushup(u);
}

int query(int u, int l, int r) {
    if (seg[u].l >= l && seg[u].r <= r) return seg[u].s;
    pushdown(u);
    int mid = seg[u].l + seg[u].r >> 1; int res = 0;
    if (l <= mid) res |= query(u<<1, l, r);
    if (r > mid) res |= query(u<<1|1, l, r);
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> t >> m;
    build(1, 1, n);
    char op; int a, b, c;
    while (m -- ) {
        cin >> op >> a >> b; if (a > b) swap(a, b);
        if (op == 'C') cin >> c, modify(1, a, b, 1<<c-1);
        else {
            int s = query(1, a, b), cnt = 0;
            for (int i = 0; i < t; ++i) if ((s >> i) & 1) cnt ++; 
            cout << cnt << '\n';
        }
    }
    return 0;
}

P3740 [HAOI2014] 贴海报:线段树,珂朵莉树(绿)

珂朵莉树裸题。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

const int N = 1010;

int n, m, idx; bool nums[N];

struct Node {
    int l, r; mutable int v;
    Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
    bool operator < (const Node &T) const {
        return l < T.l;
    }
};

typedef set<Node>::iterator iter;
set<Node> s;

iter split(int pos) {
    iter it = s.lower_bound(Node(pos)); 
    if (it != s.end() && it->l == pos) return it;
    it --;
    if (it->r < pos) return s.end();
    int l = it->l, r = it->r, v = it->v;
    s.erase(it), s.insert(Node(l, pos-1, v));
    return s.insert(Node(pos, r, v)).first;
}   

void assign(int l, int r, int v) {
    iter itr = split(r+1), itl = split(l);
    s.erase(itl, itr), s.insert(Node(l, r, v));
}

int count(int l, int r) {
    int cnt = 0; iter itr = split(r+1), itl = split(l); nums[0] = 1;
    for (iter it = itl; it != itr; ++it) if (!nums[it->v]) nums[it->v] = 1, cnt ++;
    return cnt;
}

int main() {
    scanf("%d%d", &n, &m);
    s.insert(Node(1, n, 0));
    while (m -- ) {
        int l, r; scanf("%d%d", &l, &r);
        assign(l, r, ++ idx);
    }
    printf("%d\n", count(1, n));
    return 0;
}

P9117 [春季测试 2023] 涂色游戏:模拟(橙)

对于每一行每一列,记录其最后一次修改的时间和颜色。对于点 \((i,j)\),取第 \(i\) 行和第 \(j\) 列中最后一次修改时间最晚的颜色。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef pair<int, int> pii;

const int N = 1e5+10;

int T, n, m, q;
pii row[N], col[N];

void solve() {
    scanf("%d%d%d", &n, &m, &q);
    
    for (int i = 1; i <= n; ++i) row[i] = {0, 0};
    for (int i = 1; i <= m; ++i) col[i] = {0, 0};
    
    int op, x, c;
    for (int i = 1; i <= q; ++i) {
        scanf("%d%d%d", &op, &x, &c);
        if (!op) row[x] = {i, c};
        else col[x] = {i, c};
    }
    
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) 
            printf("%d ", max(row[i], col[j]).second);
        puts("");
    }
}

int main() {
    scanf("%d", &T);
    while (T --) solve();
    return 0;
}

P8512 [Ynoi Easy Round 2021] TEST_152:珂朵莉树,树状数组(紫)

区间覆盖?珂朵莉树!

由于一次操作至多增加 \(2\) 个区间,所以最终的区间数是 \(O(n)\) 的。即珂朵莉树维护的时间复杂度是 \(O(n\log n)\) 的。我们可以先将所有询问按右端点从小到大排序,记录每个区间产生的时间,删去这个区间时,我们用树状数组减去这个区间对后面时间点的贡献;插入一个区间时,我们用树状数组加上这个区间对后面时间点的贡献。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

typedef long long ll;

const int N = 5e5+10;

int n, m, q; ll c[N], ans[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, ll k) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}

ll query(int x) {
    ll res = 0;
    for (int i = x; i >= 1; i -= lowbit(i)) res += c[i];
    return res;
}

struct Operation {
    int l, r, v;
} oper[N];

struct Query {
    int l, r, id;
    bool operator < (const Query &T) const {
        return (r == T.r) ? l < T.l : r < T.r;
    }
} qry[N];

struct Node {
    int l, r, t; mutable int v;
    Node (int l, int r = 0, int v = 0, int t = 0) : l(l), r(r), v(v), t(t) {}
    bool operator < (const Node &T) const {
        return l < T.l;
    }
};

set<Node> s;

auto split(int pos) {
    auto it = s.lower_bound(Node(pos));
    if (it != s.end() && it->l == pos) return it;
    it --;
    int l = it->l, r = it->r, v = it->v, t = it->t;
    s.erase(it), s.insert(Node(l, pos-1, v, t));
    return s.insert(Node(pos, r, v, t)).first;
}

void assign(int l, int r, int v, int t) {
    auto itr = split(r+1), itl = split(l);
    for (auto it = itl; it != itr; ++it) if (it->t) add(it->t, -1ll*(it->r-it->l+1)*it->v);
    s.erase(itl, itr), s.insert(Node(l, r, v, t)), add(t, 1ll*(r-l+1)*v);
}

int main() {
    scanf("%d%d%d", &n, &m, &q); s.insert(Node(1, n, 0, 0));

    int l, r, v, id;
    for (int i = 1; i <= n; ++i) scanf("%d%d%d", &l, &r, &v), oper[i] = {l, r, v};
    for (int i = 1; i <= q; ++i) scanf("%d%d", &l, &r), qry[i] = {l, r, i};
    sort(qry+1, qry+q+1);

    int now = 0;
    for (int i = 1; i <= q; ++i) {
        l = qry[i].l, r = qry[i].r, id = qry[i].id;
        while (now < r) now ++, assign(oper[now].l, oper[now].r, oper[now].v, now);
        ans[id] = query(r) - query(l-1);
    }
    
    for (int i = 1; i <= q; ++i) printf("%lld\n", ans[i]);
    return 0;
}

P5104 红包发红包:数学,期望(绿)

容易猜到剩下 \(x\) 元时,第 \(1\) 个人期望拿到 \(\dfrac x2\) 元。则答案为 \(\dfrac{w}{2^k}\)

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long

const int P = 1e9+7;

int w, n, k;

int power(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) (res *= a) %= P;
        (a *= a) %= P, b >>= 1;
    } 
    return res;
}

signed main() {
    cin >> w >> n >> k;
    cout << w * power(power(2, k), P-2)%P << '\n';
    return 0;
}

CF472D. Design Tutorial: Inverse the Problem:最小生成树,dfs(蓝)

将这个邻接矩阵看作一张图,跑一遍最小生成树,用 dfs 判断即可。注意一些细枝末节的判断。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> pii;

const int N = 2010;

struct Edge {
    int u, v, w;
    bool operator < (const Edge &T) const {
        return w < T.w;
    }
}; vector<Edge> e;

int n, a[N][N], p[N], dist[N][N];
vector<pii> E[N];

int find(int x) {
    return p[x] == x ? p[x] : p[x] = find(p[x]);
}

void dfs(int u, int now) {
    for (auto edge : E[now]) {
        int v = edge.first, w = edge.second;
        if (!dist[u][v] && u != v) dist[u][v] = dist[u][now]+w, dfs(u, v);
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) scanf("%d", &a[i][j]);
    for (int i = 1; i <= n; ++i) { 
        p[i] = i;
        for (int j = 1; j <= i; ++j) {
            if (i == j) {if (a[i][j]) puts("NO"), exit(0);}
            else if (a[i][j] != a[j][i] || a[i][j] < 1) puts("NO"), exit(0);
            e.push_back({i, j, a[i][j]});
        }
    }

    sort(e.begin(), e.end());
    int now = n-1;
    for (auto edge : e) {
        int u = edge.u, v = edge.v, w = edge.w;
        int fu = find(u), fv = find(v);
        if (fu == fv) continue;
        p[fu] = fv, now --, E[u].push_back({v, w}), E[v].push_back({u, w});
        if (!now) break;
    }

    for (int i = 1; i <= n; ++i) dfs(i, i);
    
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j)
            if (a[i][j] != dist[i][j])
                puts("NO"), exit(0);
    }
    puts("YES");
    return 0;
}

ABC246G. Game on Tree 3:二分,dp(蓝)

显然答案具有单调性。

不妨设当前二分的答案为 \(x\),我们可以将所有满足 \(a_i\ge x\) 的点染为黑色,所有满足 \(a_i<x\) 的点染为白色。令 \(f_i\) 表示 B 走到 \(i\) 号点时,A 至少要进行多少次操作才能使 B 不能在以 \(i\) 为根的子树内走到任何黑点。考虑状态转移,当存在边 \((i,j)\) 且以 \(j\) 为根的子树内存在黑点可以让 B 走到时,B 就一定会走过去。所以,A 至少要进行 \(\max(\sum f_j-1,0)\) 次操作(减去 \(1\) 是因为 A 在 B 走到 \(i\) 后还可以进行一次操作),另外还要考虑 \(i\) 号点是否为黑点。

所以我们有:

\[f_i=\max\left(\sum f_j-1,0\right)+[a_i\ge x] \]

时间复杂度 \(O(n\log V)\)

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 2e5+10;

int n, a[N], f[N];
vector<int> nums, e[N];

void dfs(int u, int fa, int x) {
    int sum = 0; 
    for (auto v : e[u]) {
        if (v == fa) continue;
        dfs(v, u, x), sum += f[v];
    }
    f[u] = max(sum-1, 0)+(a[u] >= x);
}

int main() {
    scanf("%d", &n); nums.push_back(0);
    for (int i = 2; i <= n; ++i) scanf("%d", &a[i]), nums.push_back(a[i]);
    for (int i = 1; i < n; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }

    sort(nums.begin(), nums.end());
    int l = 0, r = n-1;
    while (l < r) {
        int mid = l + r + 1 >> 1; dfs(1, 1, nums[mid]);
        if (f[1] >= 1) l = mid;
        else r = mid-1;
    }
    printf("%d\n", nums[l]);
    return 0;
}

CF875D. High Cry:单调栈,st 表,二分(紫)

用单调栈求出 \(a_i\) 左边第一个 \(\le a_i\) 的位置 \(L_i\),右边第一个 \(<a_i\) 的位置 \(R_i\)(为什么一个小于等于一个小于?因为一个闭区间和一个开区间正好拼成一个完整的区间)。二分在 \([L_i,i]\) 的区间中找到一个最小的位置 \(l\) 使得 \(a_l\vee a_{l+1}\vee \cdots \vee a_i=a_i\),在 \([i,R_i]\) 中找到一个最大的位置 \(r\) 使得 \(a_i\vee a_{i+1}\vee\cdots\vee a_{r}=a_{i}\)。则 \(a_i\) 所产生的不合法的区间即为 \((i-l+1)(r-i+1)\)。将总的区间数减去不合法的区间数量之和即可。

点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <cmath>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n, a[N], L[N], R[N], st[N][25]; ll ans;
stack<int> s;

int query(int l, int r) {
    int k = log2(r-l+1);
    return st[l][k] | st[r-(1<<k)+1][k];
}

int findL(int l, int r, int x) {
    while (l < r) {
        int mid = l + r >> 1;
        if (query(mid, x) > a[x]) l = mid+1;
        else r = mid;
    } 
    return l;
}

int findR(int l, int r, int x) {
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (query(x, mid) > a[x]) r = mid-1;
        else l = mid;
    }
    return l;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), st[i][0] = a[i];

    for (int j = 1; (1<<j) <= n; ++j) {
        for (int i = 1; i+(1<<j)-1 <= n; ++i)
            st[i][j] = st[i][j-1] | st[i+(1<<j-1)][j-1];
    }

    for (int i = 1; i <= n; ++i) {
        while (s.size() && a[s.top()] <= a[i]) s.pop();
        if (s.size()) L[i] = s.top()+1; else L[i] = 1;
        s.push(i);
    }
    while (s.size()) s.pop();
    for (int i = n; i >= 1; --i) {
        while (s.size() && a[s.top()] < a[i]) s.pop();
        if (s.size()) R[i] = s.top()-1; else R[i] = n;
        s.push(i);
    }

    for (int i = 1; i <= n; ++i) {
        int l = findL(L[i], i, i), r = findR(i, R[i], i);
        ans += ((ll)i-l+1)*(r-i+1);
    }
    printf("%lld\n", 1ll*n*(n+1)/2-ans);
    return 0;
}

10.5

CF103687L. Candy Machine:贪心(绿)

假设最终选择的集合的平均数不超过 \(k\),那么应将 \(\le k\) 的数全部选入,然后贪心选择 \(>k\) 的部分中最小的若干个数。因此将 \(n\) 个数从小到大排序后,最优解一定是一个前缀。枚举每个前缀,二分找到位置即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e6+10;

int n, ans, a[N]; ll tot; double aver;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), tot += a[i];
	
	aver = tot / (double)n;
	sort(a+1, a+n+1);
	int pos = upper_bound(a+1, a+n+1, aver) - a; ans = n-pos+1;
	
	for (int i = n; i > 1; --i) {
		tot -= a[i], aver = tot / (double)(i-1);
		pos = upper_bound(a+1, a+i, aver) - a, ans = max(ans, i-pos);
	}
	
	printf("%d\n", ans);
	return 0;
}

P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT):FWT(紫)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = (1<<17)+10, P = 998244353;

int n, m, a[N], b[N], A[N], B[N];

void init() {
    for (int i = 0; i < n; ++i) A[i] = a[i], B[i] = b[i];
}

void FWT_OR(int f[], int op) {
    for (int x = 1; x < n; x <<= 1) {
        for (int i = 0; i < n; i += x<<1) 
            for (int j = 0; j < x; ++j)
                (f[i+j+x] += f[i+j] * op) %= P;
    }
}

void FWT_AND(int f[], int op) {
    for (int x = 1; x < n; x <<= 1) {
        for (int i = 0; i < n; i += x<<1) 
            for (int j = 0; j < x; ++j) 
                (f[i+j] += f[i+j+x] * op) %= P;
    }
}

void FWT_XOR(int f[], int op) {
    for (int x = 1; x < n; x <<= 1) {
        for (int i = 0; i < n; i += x<<1) 
            for (int j = 0, f1, f2; j < x; ++j)
                f1 = f[i+j], f2 = f[i+j+x], f[i+j] = 1ll * (f1+f2) * op % P, f[i+j+x] = 1ll * (f1-f2) * op % P;
    }
}

void FWT_CALC() {
    for (int i = 0; i < n; ++i) A[i] = 1ll * A[i] * B[i] % P;
}

void output() {
    for (int i = 0; i < n; ++i) printf("%d ", (A[i]+P)%P); puts("");
}

int main() {
    scanf("%d", &m); n = 1 << m;
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
    for (int i = 0; i < n; ++i) scanf("%d", &b[i]);

    init(), FWT_OR(A, 1), FWT_OR(B, 1), FWT_CALC(), FWT_OR(A, -1), output();
    init(), FWT_AND(A, 1), FWT_AND(B, 1), FWT_CALC(), FWT_AND(A, -1), output();
    init(), FWT_XOR(A, 1), FWT_XOR(B, 1), FWT_CALC(), FWT_XOR(A, 499122177), output();
    return 0;
}

10.9

ABC245D. Polynomial division:数学(黄)

其实就是高精度除法模拟。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int n, m, A[210], B[210], C[210];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 0; i <= n; ++i) scanf("%d", &A[i]);
	for (int i = 0; i <= n+m; ++i) scanf("%d", &C[i]);
	
	for (int i = m, j = n+m; i >= 0; --i, --j) {
		B[i] = C[j] / A[n];
		for (int k = j, t = n; k >= i; --k, --t) C[k] -= B[i] * A[t];
	}
	for (int i = 0; i <= m; ++i) printf("%d ", B[i]);
	return 0;
} 

ABC245E. Wrapping Chocolate:排序,贪心(绿)

将所有巧克力和盒子放在一起,分别以 \(x,y,type\)(若巧克力和盒子的 \(x,y\) 都相同,则盒子排在前面)为第一、二、三关键字从大到小排序。然后扫一遍所有巧克力和盒子,若当前为盒子则将它的 \(y\) 加入一个 multiset,否则在 multiset 里找到最小的一个 \(y'\) 使得 \(y'\ge y\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

const int N = 4e5+10;

int n, m;
multiset<int> pos;

struct Node {
	int x, y, id;
	bool operator < (const Node &T) const {
		return (x == T.x) ? ((y == T.y) ? id > T.id : y > T.y) : x > T.x;
	}
} s[N];

int main() {
	scanf("%d%d", &n, &m);
	int x, y;
	for (int i = 1; i <= n; ++i) scanf("%d", &x), s[i].x = x;
	for (int i = 1; i <= n; ++i) scanf("%d", &y), s[i].y = y, s[i].id = 1;
	for (int i = 1; i <= m; ++i) scanf("%d", &x), s[n+i].x = x;
	for (int i = 1; i <= m; ++i) scanf("%d", &y), s[n+i].y = y, s[n+i].id = 2;
	
	sort(s+1, s+n+m+1);
	for (int i = 1; i <= n+m; ++i) {
		if (s[i].id == 2) pos.insert(s[i].y);
		else {
			auto now = pos.lower_bound(s[i].y);
			if (now == pos.end()) puts("No"), exit(0);
			pos.erase(now);
		}
	}
	puts("Yes");
	return 0;
}

ABC245F. Endless Walk:拓扑排序(绿)

显然,若一个点出度为 \(0\),则这个点显然不可能不停止地走下去,给这个点打上标记。若一个点能到达的所有点都是有标记的点,则这个点也要打上标记。最后没有被打上标记的点的数量即为答案。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2e5+10;

int n, m, cnt; vector<int> e[N];
bool st[N]; int deg[N];

void toposort() {
	queue<int> q;
	for (int i = 1; i <= n; ++i) if (!deg[i]) st[i] = 1, q.push(i);
	while (q.size()) {
		int u = q.front(); q.pop();
		for (auto v : e[u]) {
			deg[v] --;
			if (!deg[v]) st[v] = 1, q.push(v);
		} 
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[v].push_back(u), deg[u] ++;
	}
	
	toposort();
	for (int i = 1; i <= n; ++i) if (!st[i]) cnt ++;
	printf("%d\n", cnt);
	return 0;
}

ABC245G. Foreign Friends:最短路(蓝)

我们枚举颜色的每个二进制位,每次把当前二进制位上为 \(0\) 的颜色加入起始点中,更新所有颜色为当前二进制位上为 \(1\) 的点的答案。再用二进制位上为 \(1\) 的更新为 \(0\) 的。这是因为若两个颜色不同,则它们至少有一个二进制不同。

时间复杂度 \(O(n\log n\log k)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

typedef long long ll;
typedef pair<ll, ll> pii;

const int N = 1e5+10;

int n, m, k, l, col[N]; vector<pii> e[N]; vector<int> start;
ll dist[N], ans[N]; bool st[N];

void dijkstra(int bit, int now) {
	memset(dist, 0x7f, sizeof dist), memset(st, 0, sizeof st);
	priority_queue<pii, vector<pii>, greater<pii>> q;
	for (auto u : start) if (!(col[u]>>bit&1^now)) q.push({0, u}), dist[u] = 0;
	while (q.size()) {
		auto t = q.top(); q.pop();
		int u = t.second; ll dis = t.first;
		if (st[u]) continue; st[u] = 1;
		if (col[u]>>bit&1^now) ans[u] = min(ans[u], dist[u]);
		for (auto [v, w] : e[u]) {
			if (dist[v] > dis+w)
				dist[v] = dis+w, q.push({dist[v], v});
		}
	}
}

int main() {
	int u, v, w;
	scanf("%d%d%d%d", &n, &m, &k, &l);
	for (int i = 1; i <= n; ++i) scanf("%d", &col[i]);
	for (int i = 1; i <= l; ++i) scanf("%d", &u), start.push_back(u);
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d%d", &u, &v, &w);
		e[u].push_back({v, w}), e[v].push_back({u, w});
	}
	
	memset(ans, 0x7f, sizeof ans);
	for (int i = 0; 1<<(i-1) <= k; ++i) dijkstra(i, 0), dijkstra(i, 1);
	for (int i = 1; i <= n; ++i) printf("%lld ", ans[i]>=1e18?-1:ans[i]); puts("");
	return 0;
}

10.10

ABC244D. Swap Hats:枚举(红)

猜结论:若 \(a_1,b_1\)\(a_2,b_2\)\(a_3,b_3\) 只有一对相同,则输出 No;否则输出 Yes

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int cnt; char s[5], t[5];

int main() {
	cin >> s[1] >> s[2] >> s[3] >> t[1] >> t[2] >> t[3];
	for (int i = 1; i <= 3; ++i) if (s[i] == t[i]) cnt ++;
	if (cnt == 1) puts("No"); else puts("Yes");
	return 0;
} 

ABC244E. King Bombee:dp(黄)

\(f_{i,j,0/1}\) 表示考虑到 \(A\) 中前 \(i\) 个数,\(A_i=j\)\(X\) 出现次数模 \(2=0\)\(1\) 的方案数。状态转移是显然的。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 2010, P = 998244353;

int n, m, k, s, t, x, f[N][N][2];
vector<int> e[N]; 

int main() {
	scanf("%d%d%d%d%d%d", &n, &m, &k, &s, &t, &x);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	}
	
	f[0][s][0] = 1;
	for (int i = 1; i <= k; ++i) {
		for (int j = 1; j <= n; ++j) {
			for (auto u : e[j]) {
				if (u == x) (f[i][j][0] += f[i-1][u][1]) %= P, (f[i][j][1] += f[i-1][u][0]) %= P;
				else (f[i][j][0] += f[i-1][u][0]) %= P, (f[i][j][1] += f[i-1][u][1]) %= P;
			}
		}
	}
	printf("%d\n", f[k][t][0]);
	return 0;
}

ABC245F. Shortest Good Path:bfs(绿)

我们令一个状态 \(\{state,u\}\) 表示 \(S=state\),走到 \(S\) 的最后一个点为 \(u\) 的最小步数。从 \(\{0,0\}\) 开始 bfs 即可。最后取 \(\min_{1\le u\le n}\{\{state,u\}\}\) 即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;

const int N = 20, M = 1<<17;

int n, m, g[N][N]; ll ans;
int f[N][M];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		g[u][v] = g[v][u] = 1;
	}
	
	memset(f, 0x3f, sizeof f); 
	queue<pii> q; q.push({0, 0}), f[0][0] = 0;
	while (q.size()) {
		auto t = q.front(); q.pop();
		int u = t.first, now = t.second;
		for (int v = 1; v <= n; ++v) {
			if (g[u][v] || !u) {
				int to = now ^ (1 << v-1);
				if (f[v][to] == 0x3f3f3f3f) f[v][to] = f[u][now]+1, q.push({v, to});
			}
		}
	}
	
	for (int i = 1; i < (1<<n); ++i) {
		int now = 0x3f3f3f3f;
		for (int j = 1; j <= n; ++j) now = min(now, f[j][i]);
		ans += now;
	}
	printf("%lld\n", ans);
	return 0;
}

ABC245G. Construct Good Path:构造(蓝)

直接用 dfs 序建树然后模拟即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 1e5+10;

int n, m, cnt[N], sch[N], s[N]; bool st[N];
vector<int> e[N], ee[N], res;

bool dfs(int u) {
	bool now = s[u]; st[u] = 1;
	for (auto v : e[u]) {
		if (st[v]) continue;
		now |= dfs(v), ee[u].push_back(v);
	}
	return sch[u]=now;
}

void get_path(int u, int fa) {
	res.push_back(u), cnt[u] ^= 1;
	for (auto v : ee[u]) {
		if (v == fa || !sch[v]) continue;
		get_path(v, u); res.push_back(u), cnt[u] ^= 1;
	}
	if (cnt[u] ^ s[u]) {
		if (u != 1) res.push_back(fa), res.push_back(u), cnt[fa] ^= 1, cnt[u] ^= 1;
		else res.pop_back();
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	}
	for (int i = 1; i <= n; ++i) scanf("%1d", &s[i]);
	
	dfs(1), get_path(1, 0);
	printf("%d\n", res.size());
	for (auto x : res) printf("%d ", x); puts("");
	return 0;
}

10.11

ABC243D. Moves on Binary Tree:模拟(黄)

由于结果在 long long 范围内,将原数转化为二进制后用 vector 模拟即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

#define int long long

const int N = 1e6+10;

int n, x, res; char s[N];
vector<int> nums;

signed main() {
	cin >> n >> x >> s+1;
	while (x) {
		if (x & 1) nums.push_back(1);
		else nums.push_back(0);
		x >>= 1;
	}
	reverse(nums.begin(), nums.end());
	
	for (int i = 1; i <= n; ++i) {
		if (s[i] == 'U') nums.pop_back();
		else if (s[i] == 'L') nums.push_back(0);
		else nums.push_back(1);
	}
	
	reverse(nums.begin(), nums.end());
	for (int i = 0; i < nums.size(); ++i) res += (1ll<<i) * nums[i];
	cout << res << '\n';
	return 0;
}

ABC243E. Edge Deletion:Floyd(绿)

Floyd 预处理两点间最短路,过程中计算是否存在一条路径 \(u\rightarrow v\) 满足 \(dist(u,v)\le w(u,v)\),且这条路径不与边 \((u,v)\) 重合。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>

using namespace std;

#define int long long

const int N = 310;

int n, m, cnt, f[N][N]; bool check[N][N];

struct Edge {
	int u, v, w;
};

vector<Edge> edge;

void floyd() {
	for (int k = 1; k <= n; ++k) {
		for (int i = 1; i <= n; ++i) 
			for (int j = 1; j <= n; ++j) {
			    if (f[i][j] >= f[i][k]+f[k][j] && i != k && j != k) 
			      f[i][j] = f[i][k]+f[k][j], check[i][j] = 1;
			}
	}
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> m;
	memset(f, 0x3f, sizeof f);
	for (int i = 1; i <= n; ++i) f[i][i] = 0;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		f[u][v] = f[v][u] = w, edge.push_back({u, v, w});
	}
	
	floyd();

	for (auto [u, v, w] : edge) {
		if (check[u][v])
			cnt ++;
	}
	cout << cnt << '\n';
	return 0;
}

ABC243F. Lottery:概率 dp,数学(蓝)

\(k\) 次,每个数抽到 \(c_i\) 次的概率为:

\[\dfrac{k!}{\prod_{i=1}^nc_i!} \prod_{i=1}^np_i^{c_i} \]

那么这些数一共能组成 \(\dfrac{k!}{\prod_{i=1}^nc_i!}\) 种排列。

考虑 dp,令 \(f_{i,j,k}\) 表示考虑前 \(i\) 个物品,选出 \(j\) 个,选了 \(k\) 次的概率。考虑不选 \(i\) 和选 \(i\) 两种情况:

\[f_{i,j,k}=f_{i-1,j,k}+\sum_{x=1}^k \dfrac{p_i^x}{x!}f_{i-1,j-1,k-x} \]

最终答案为 \(f_{n,m,k}\times k!\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 55, P = 998244353;

int n, m, k, sum, inv, p[N], fac[N], infac[N], pow[N][N], f[N][N][N];

int power(int a, int b) {
  int res = 1;
  while (b) {
    if (b & 1) res = 1ll * res * a % P;
    a = 1ll * a * a % P, b >>= 1;
  }
  return res;
}

int main() {
  scanf("%d%d%d", &n, &m, &k);
  for (int i = 1; i <= n; ++i) scanf("%d", &p[i]), sum += p[i];
  
  inv = power(sum, P-2);
  for (int i = 1; i <= n; ++i) {
    p[i] = 1ll * p[i] * inv % P;
    for (int j = 1; j <= k; ++j) pow[i][j] = power(p[i], j);
  }
  fac[0] = 1; for (int i = 1; i <= k; ++i) fac[i] = 1ll * fac[i-1] * i % P;
  infac[k] = power(fac[k], P-2); for (int i = k-1; i >= 0; --i) infac[i] = 1ll * infac[i+1] * (i+1) % P;
  
  for (int i = 0; i <= n; ++i) f[i][0][0] = 1;
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j)
      for (int t = 1; t <= k; ++t) {
        f[i][j][t] = f[i-1][j][t];
        for (int x = 1; x <= t-j+1; ++x)
          (f[i][j][t] += 1ll * f[i-1][j-1][t-x] * pow[i][x] % P * infac[x] % P) %= P;
      }
  }
  printf("%d\n", 1ll*f[n][m][k]*fac[k]%P);
  return 0;
}

10.12

P2325 [SCOI2005] 王室联邦:dfs,树分块(蓝)

考虑如下的构造方法:

  1. dfs 整棵树,处理每个节点时,将其一部分子节点分块,将未被分块的子节点返回到上一层;
  2. 枚举 \(u\) 的每个子节点 \(v\),递归处理完子树后,将每个子节点返回的未被分块的节点添加至集合 \(S\) 中,若 \(|S|\ge B\) 就将 \(S\) 作为一个新的块并将 \(u\) 作为省会,然后清空 \(S\)
  3. 处理完所有子树后,将 \(u\) 也加入 \(S\) 中。显然有 \(|S|\le B\)

由于 \(|S|\le B\),且一个子树返回的大小最多会增加 \(B-1\) 个节点,那么此时每块的大小 \(\in [B,2B)\)

最后,\(S\) 中可能仍有 \(\le B\) 个节点。我们将这 \(B\) 个节点并入以根节点为省会的最后一个块中,则这个块的大小 \(\in [B,3B)\)

时间复杂度 \(O(n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <stack>

using namespace std;

const int N = 1010;

int n, B, idx, root[N], id[N]; vector<int> e[N];
stack<int> s;

void dfs(int u, int fa) {
    int now = s.size();
    for (auto v : e[u]) {
        if (v == fa) continue; dfs(v, u);
        if (s.size()-now >= B) {
            root[++ idx] = u;
            while (s.size() > now) id[s.top()] = idx, s.pop();
        }
    }
    s.push(u);
}

int main() {
    scanf("%d%d", &n, &B);
    for (int i = 1; i < n; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }

    dfs(1, 0);
    if (!idx) root[++ idx] = 1;
    while (s.size()) id[s.top()] = idx, s.pop();
    printf("%d\n", idx);
    for (int i = 1; i <= n; ++i) printf("%d ", id[i]); puts("");
    for (int i = 1; i <= idx; ++i) printf("%d ", root[i]); puts("");
    return 0;
}

ABC243G. Sqrt:dp,数学(蓝)

\(f_i\) 表示当前末尾为 \(i\) 的序列数量。显然 \(f_1=1\),状态转移方程为:

\[f_{i^2}=\sum_{j=1}^if_j \]

那么:

\[\begin{aligned} f_{i^4}=&\sum_{j=1}^{i^2}f_j\\ =&\sum_{j=1}^i (i^2-j^2+1)f_j\\ =&(i^2+1)\sum_{j=1}^i f_i-\sum_{j=1}^ij^2f_j \end{aligned} \]

注意此题开根号时要用 long double。时间复杂度 \(O(n^{1/4})\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 6e4+10;

#define int long long

int T, n, f[N], s1[N], s2[N];

signed main() {
    scanf("%lld", &T);
    f[1] = 1;
    for (int i = 2; i <= 60000; ++i) {
        for (int j = 1; j <= i/j; ++j)
            f[i] += f[j];
    }
    for (int i = 1; i <= 60000; ++i) s1[i] = s1[i-1]+f[i], s2[i] = s2[i-1]+f[i]*i*i;

    while (T -- ) {
        scanf("%lld", &n);
        int x = pow((long double)n, 0.5), y = pow((long double)n, 0.25);
        printf("%lld\n", (x+1)*s1[y]-s2[y]);
    }
    return 0;
}

10.15

ABC324A. Same:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int n, a[110];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 2; i <= n; ++i) if (a[i] != a[i-1]) puts("No"), exit(0); 
	puts("Yes");
	return 0;
} 

ABC324B. 3-smooth Numbers:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

long long n;

int main() {
	cin >> n;
	while (n % 2 == 0) n /= 2;
	while (n % 3 == 0) n /= 3;
	if (n > 1) puts("No"); else puts("Yes");
	return 0;
}

ABC324C. Error Correction:枚举(橙)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int N = 5e5+10; 

int n; string s[N];
vector<int> nums;

bool check(int k) {
	int siz = s[0].size(), nsiz = s[k].size();
	if (siz == nsiz) {
		bool check = 1;
		for (int i = 0; i < siz; ++i) {
			if (!check && s[0][i] != s[k][i]) return 0; 
			if (check && s[0][i] != s[k][i]) check = 0;
		}
	} else if (siz == nsiz+1) {
		bool check = 1;
		for (int i = 0, j = 0; i < siz; ++i, ++j) {
			if (!check && s[0][i] != s[k][j]) return 0;
			if (check && s[0][i] != s[k][j]) check = 0, ++i; 
			if (s[0][i] != s[k][j]) return 0;
		}
	} else if (siz == nsiz-1) {
		bool check = 1; 
		for (int i = 0, j = 0; i < siz; ++i, ++j) {
			if (!check && s[0][i] != s[k][j]) return 0;
			if (check && s[0][i] != s[k][j]) check = 0, ++j;
			if (s[0][i] != s[k][j]) return 0;
		}
	} else return 0;
	return 1;
} 

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> s[0];
	for (int i = 1; i <= n; ++i) cin >> s[i];
	for (int i = 1; i <= n; ++i) if (check(i)) nums.push_back(i);
	printf("%d\n", nums.size()); for (auto x : nums) printf("%d ", x); puts("");
	return 0;
}

ABC324D. Square Permutation:枚举(黄)

直接枚举排列会超时。发现 \(\sqrt{10^{13}}<4\times 10^6\),于是枚举底数 \(\in [0,4\times 10^6]\) 即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;

#define int long long

int n, ans; char s[15]; vector<int> A, B;

bool cmp() {
	for (int i = 0; i < A.size(); ++i) if (A[i] != B[i]) return 0;
	return 1;
}

signed main() {
	cin >> n >> s+1; 
	for (int i = 1; i <= n; ++i) A.push_back(s[i]-'0'); sort(A.begin(), A.end());
	for (int i = 0; i <= 4000000; ++i) {
		int x = i*i;
		B.clear();
		for (int j = 1; j < n; ++j) B.push_back((int)(x/pow(10, j-1))%10);
		B.push_back(x/pow(10, n-1)); sort(B.begin(), B.end());
		if (cmp()) ans ++;
	}
	printf("%lld\n", ans);
	return 0;
} 

ABC324E. Joint Two Strings:双指针,桶,枚举(黄)

双指针处理出 \(S_i\)\(T\) 的最长公共前后缀长度 \(pre_i\)\(suf_i\)。对于 \(S_i\),其答案即为满足 \(suf_j\ge\text{len}(T)-pre_i\)\(S_j\) 数量。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 5e5+10;

int n, pre[N], suf[N], num[N]; ll ans;
string s[N];

int solve(string &A, string &B) {
    int i = 0, j = -1;
    while (i < A.size() && j+1 < B.size()) {
        if (A[i] == B[j+1]) j ++;
        i ++;
    }   
    return j+1;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> s[0];
    for (int i = 1; i <= n; ++i) cin >> s[i];

    for (int i = 1; i <= n; ++i) pre[i] = solve(s[i], s[0]), reverse(s[i].begin(), s[i].end());
    reverse(s[0].begin(), s[0].end());
    for (int i = 1; i <= n; ++i) suf[i] = solve(s[i], s[0]), num[suf[i]] ++;
    for (int i = s[0].size()-1; ~i; --i) num[i] += num[i+1];

    for (int i = 1; i <= n; ++i) ans += num[s[0].size()-pre[i]];
    printf("%lld\n", ans);
    return 0;
}

ABC324F. Beautiful Path:01分数规划,二分,bfs(拓扑排序),dp(绿)

假设我们依次经过边 \(e_1,e_2,\cdots,e_k\),则我们要求的即为:

\[\dfrac{\sum_{i=1}^{k} b_{e_i} }{\sum_{i=1}^k c_{e_i}} \]

的最大值。这是一个经典的 01 规划问题,可以通过二分解决。

假设 \(\dfrac{\sum_{i=1}^{k} b_{e_i} }{\sum_{i=1}^k c_{e_i}} \ge X\),那么有 \(\sum_{i=1}^{k} (b_{e_i}-Xc_{e_i})\ge 0\)。我们令 \(w_i=b_i-Xc_{e_i}\),在图上跑一遍 dp,计算出 \(1\) 号点到 \(n\) 号点的最大权值之和,若 \(f_n\ge 0\) 则说明答案 \(\ge X\)

时间复杂度 \(O((n+m)\log W)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

typedef pair<int, double> pid;

const int N = 2e5+10;
const double eps = 1e-12, INF = 1e9;

struct Edge {
    int u, v, b, c;
};

int n, m, deg[N]; double f[N];
vector<pid> e[N]; vector<Edge> Edg;

void bfs() {
    for (int i = 2; i <= n; ++i) f[i] = -INF;
    queue<int> q;
    for (int i = 1; i <= n; ++i) if (!deg[i]) q.push(i);
    while (q.size()) {
        int u = q.front(); q.pop();
        for (auto [v, w] : e[u]) {
            deg[v] --, f[v] = max(f[v], f[u]+w);
            if (!deg[v]) q.push(v);
        }
    }
}

bool check(double x) {
    for (int i = 1; i <= n; ++i) e[i].clear();
    for (auto [u, v, b, c] : Edg) {
        double w = b - x*c;
        e[u].push_back({v, w}), deg[v] ++;
    }
    bfs(); 
    return (f[n] >= 0);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v, b, c; scanf("%d%d%d%d", &u, &v, &b, &c);
        Edg.push_back({u, v, b, c});
    }

    double l = 0, r = 2e9;
    while (l+eps < r) {
        double mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    printf("%.12f\n", l);
    return 0;  
}

10.16

P2553 [AHOI2001] 多项式乘法:模拟(蓝)

难点在读入字符串。时间复杂度 \(O(n^2)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 80;

int a[N], b[N], c[N];
string s; 

int main() {
    while (getline(cin, s)) {
        memset(a, 0, sizeof a), memset(b, 0, sizeof b), memset(c, 0, sizeof c);
        
        bool ismul = 0; 
        for (auto c : s) if (c == '*') ismul = 1;
        if (!ismul) {
            for (auto c : s) if (isdigit(c) || c == '+' || c == '*') cout << c;
            cout << '\n';
            continue; 
        } else {
            bool ispow = 0; int i, num = 0, power = 0;
            for (i = 0; i < s.size(); ++i) {
                char c = s[i];
                if (!isdigit(c)) {
                    if (c == '(' || c == '*') continue;
                    else if (c == '^') ispow = 1;
                    else if (c == '+' || c == ')') {
                        a[power] += num, num = 0, power = 0, ispow = 0;
                        if (c == ')') break;
                    }
                } else {
                    if (!ispow) num = num * 10 + c - '0';
                    else power = power * 10 + c - '0';
                }
            }
            for (i = i+1 ; i < s.size(); ++i) {
                char c = s[i];
                if (!isdigit(c)) {
                    if (c == '(' || c == '*') continue;
                    else if (c == '^') ispow = 1;
                    else if (c == '+' || c == ')') {
                        b[power] += num, num = 0, power = 0, ispow = 0;
                        if (c == ')') break;
                    }
                } else {
                    if (!ispow) num = num * 10 + c - '0';
                    else power = power * 10 + c - '0';
                }
            }
            
            for (int i = 0; i <= 30; ++i) {
                for (int j = 0; j <= 30; ++j)
                    c[i+j] += a[i]*b[j];
            }

            bool check = 0;
            for (int i = 60; i >= 0; --i) {
                if (c[i]) {
                    if (check) printf("+");
                    printf("%d", c[i]);
                    if (i) printf("a^%d", i);
                    check = 1;
                }
            }
            puts("");
        }
    }
    return 0;
}

ABC242D. ABC Transform:模拟,递归,思维(黄)

\(f(t,k)\) 表示 \(S^{(t)}\) 的第 \(k\) 个字母。显然 \(f(0,k)=S^{(0)}_k\)\(f(t,0)=(S^{(0)}_0+t)\bmod 3\)。递归的过程是显然的:

\[f(t,k)=\left\{\begin{aligned} &(f(t-1,k/2)+2)\bmod 3 &(k\bmod 2=0)\\ &(f(t-1,(k+1)/2)+1)\bmod 3 &(k\bmod 2=1) \end{aligned}\right. \]

\(A,B,C\) 分别为 \(0,1,2\)

时间复杂度 \(O(q\log k)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 1e5+10;

int q; char s[N];

char f(ll t, ll k) {
    if (!t) return s[k];
    if (k == 1) return 'A' + (s[1]-'A'+t) % 3;
    return 'A' + (f(t-1, (k+1)/2)-'A'+(k&1^1)+1) % 3; 
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> s+1 >> q;

    ll t, k;
    while (q -- ) {
        cin >> t >> k;
        cout << f(t, k) << '\n';
    }
    return 0;
}

ABC242E. (∀x∀):组合数学(绿)

由于是回文串,我们只考虑前 \(\left\lfloor \dfrac{n+1}{2} \right\rfloor\) 个字符即可。对于 \(X_i\),若 \(X_i<S_i\),则后面 \(\left\lfloor \dfrac{n+1}{2} \right\rfloor-i\) 个字符可以随便选,共有 \((S_i-\texttt{A})\times 26^{\left\lfloor (n+1)/2 \right\rfloor-i}\) 种选择(\(\forall 1\le j<i,X_j=S_j\))。

最后还需要考虑 \(X\) 的前半部分和 \(S\) 的前半部分相等的情况。注意预处理 \(26\) 的幂。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e6+10, P = 998244353;

int T, n, power[N]; char s[N];

void solve() {
    int ans = 0; bool check = 1;
    cin >> n >> s+1;
    for (int i = 1; i <= (n+1)/2; ++i) (ans += 1ll * (s[i]-'A') * power[(n+1)/2-i] % P) %= P;
    for (int i = n/2; i >= 1; --i) {
      if (s[i] < s[n-i+1]) {check = 1; break;}
      else if (s[i] > s[n-i+1]) {check = 0; break;}
    }
    cout << (ans+check)%P << '\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    power[0] = 1;
    for (int i = 1; i <= 1000000; ++i) power[i] = 26ll * power[i-1] % P;

    cin >> T;
    while (T -- ) solve();
    return 0;
}

10.20

P1229 遍历问题:树,数学(黄)

注意到对于一个节点 \(u\),若其有且仅有一个子节点 \(v\),则 \(v\) 可以作为 \(u\) 的左儿子或右儿子。假设一共有 \(cnt\) 个只有一个儿子的节点,则答案为 \(2^{cnt}\)

根据前后序遍历的性质,若 \(pre_i=suf_j\)\(pre_{i+1}=suf_{j-1}\),则 \(pre_i\) 只有一个儿子 \(pre_{i+1}\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 5010;

int n, cnt;
char pre[N], suf[N];

int main() {
    cin >> pre+1 >> suf+1; n = strlen(pre+1);
    for (int i = 1; i <= n; ++i) {
        for (int j = 2; j <= n; ++j)
            if (pre[i] == suf[j] && pre[i+1] == suf[j-1]) 
                cnt ++;
    }
    cout << pow(2, cnt) << '\n';
    return 0;
}

P5186 [COCI2009-2010#4] OGRADA:单调队列(绿)

(双倍经验:P7697 [COCI2009-2010#4] OGRADA

对于第一问,首先求出每一次刷最大能刷的值,这个可以用单调队列求;然后对于每个栅栏,求出每个覆盖它的部分的最大值,也可以用单调队列求。用总和减去每个栅栏能刷到的最大值之和即可。

对于第二问,假设区间 \([l,r]\) 的栅栏都被粉刷为了相同的高度,那么这一段区间对答案的贡献即为 \(\left\lceil\dfrac{r-l+1}{x}\right\rceil\)

时间复杂度 \(O(n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <deque>

using namespace std;

const int N = 1e6+10;

int n, x, cnt; long long sum, ans;
int h[N], p[N], col[N];

int main() {
    scanf("%d%d", &n, &x);
    for (int i = 1; i <= n; ++i) scanf("%d", &h[i]), sum += h[i];

    // 预处理每一段区间的最小值
    deque<int> q;
    for (int i = 1; i < x; ++i) {
        while (q.size() && h[q.back()] > h[i]) q.pop_back();
        q.push_back(i);
    }
    for (int i = x; i <= n; ++i) {
        while (q.size() && q.front() < i-x+1) q.pop_front();
        while (q.size() && h[q.back()] > h[i]) q.pop_back();
        q.push_back(i), p[i-x+1] = h[q.front()];
    }

    // 找到每一次上色的最大值
    q.clear(); 
    for (int i = 1; i <= n; ++i) {   
        while (q.size() && q.front() < i-x+1) q.pop_front();
        while (q.size() && i <= n-x+1 && p[q.back()] < p[i]) q.pop_back();
        if (i <= n-x+1) q.push_back(i);
        ans += col[i] = p[q.front()]; 
    }
    printf("%lld\n", sum-ans);

    // 找到连续段
    int siz = 0;
    for (int i = 1; i <= n; ++i) {
        if (col[i] != col[i-1]) cnt += ceil((double)siz/x), siz = 1;
        else siz ++;
    }
    cnt += ceil((double)siz/x);
    printf("%d\n", cnt);
    return 0;
}

P1841 [JSOI2007] 重要的城市:Floyd,dp(蓝)

在计算最短路的同时,计算最短路的条数。对于点 \(i\),若存在点 \(j,k\) 满足 \(g(j,k)=g(j,i)+g(i,k)\)\(num(j,k)=num(j,i)\times num(i,k)\),则 \(i\)\(j\rightarrow k\) 路径上的必经点。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 210;

int n, m, cnt, g[N][N], num[N][N];
vector<int> e[N];

void floyd() {
    for (int k = 1; k <= n; ++k) {
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j) {
                if (g[i][j] > g[i][k]+g[k][j]) g[i][j] = g[i][k]+g[k][j], num[i][j] = num[i][k]*num[k][j];
                else if (g[i][j] == g[i][k]+g[k][j]) num[i][j] += num[i][k]*num[k][j];
            }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j)
            g[i][j] = (i == j) ? 0 : 1e9;
    }
    for (int i = 1; i <= m; ++i) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        g[u][v] = g[v][u] = min(g[u][v], w), num[u][v] = num[v][u] = 1, e[u].push_back(v);
    }

    floyd();

    for (int i = 1; i <= n; ++i) {
        bool check = 0; 
        for (int j = 1; j <= n; ++j) {
            for (int k = 1; k <= n; ++k) {
                if (i == j || i == k || j == k) continue;
                if (g[j][k] == g[j][i]+g[i][k] && num[j][k] == num[j][i]*num[i][k])
                    check = 1;
            }
        }
        if (check) printf("%d ", i), cnt ++;
    }
    if (!cnt) puts("No important cities.");
    return 0;
}

CF1100E. Andrew and Taxi:二分,拓扑排序(紫)

答案显然具有单调性,即若 \(x\) 符合题意,\(x+1\) 必定也符合题意。所以我们可以二分解决问题。

如何判断 \(x\) 是否合法呢?我们可以把所有边 \((u,v,w)\) 分为两类:

  • \(w>x\):这一部分边是不能调整方向的,可以通过拓扑排序判断是否有环。若有环则 \(x\) 一定不是合法解。
  • \(w\le x\):这一部分边是可以调整方向的。若 \(u\) 的拓扑序大于 \(v\) 的拓扑序,则会产生环,需要把这条边反过来。

时间复杂度 \(O((n+m)\log w)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

const int N = 1e5+10;

struct Edge {
    int u, v, w;
} edge[N];

int n, m, idx, deg[N], topo[N]; 
vector<int> e[N], ans;

bool toposort() {
    idx = 0; queue<int> q; int cnt = 0;
    for (int i = 1; i <= n; ++i) if (!deg[i]) q.push(i);
    while (q.size()) {
        int u = q.front(); q.pop();
        topo[u] = ++ idx, cnt ++;
        for (auto v : e[u]) {
            deg[v] --;
            if (!deg[v]) q.push(v);
        }
    }
    return (cnt == n);
}

bool check(int x) {
    for (int i = 1; i <= n; ++i) e[i].clear(), deg[i] = 0;
    for (int i = 1; i <= m; ++i) {
        int u = edge[i].u, v = edge[i].v, w = edge[i].w;
        if (w > x) e[u].push_back(v), deg[v] ++;
    }

    if (!toposort()) return 0;
    ans.clear();
    for (int i = 1; i <= m; ++i) {
        int u = edge[i].u, v = edge[i].v, w = edge[i].w;
        if (w <= x && topo[u] > topo[v]) ans.push_back(i);
    }
    return 1;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        edge[i] = {u, v, w};
    }

    int l = 0, r = 1e9+1;
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid+1;
    }
    printf("%d %d\n", l, ans.size());
    for (auto u : ans) printf("%d ", u); puts("");
    return 0;
}

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G:有向图强连通分量(tarjan)(绿)

缩点后,若只存在一个出度为 \(0\) 的 SCC,则答案即为这个 SCC 内点的数量。否则答案为 \(0\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 1e4+10;

int n, m; vector<int> e[N];
int idx, top, cnt, dfn[N], low[N], stk[N], ins[N], c[N], siz[N], deg[N];

void tarjan(int u) {
    dfn[u] = low[u] = ++ idx, stk[++ top] = u, ins[u] = 1;
    for (auto v : e[u]) {
        if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
        else low[u] = min(low[u], dfn[v]);
    }

    if (dfn[u] == low[u]) {
        cnt ++; int t;
        do t = stk[top --], ins[t] = 0, c[t] = cnt, siz[cnt] ++;
        while (u != t);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        e[u].push_back(v);
    }

    for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);

    for (int i = 1; i <= n; ++i) {
        for (auto v : e[i]) {
            if (c[i] != c[v]) 
                deg[c[i]] ++;
        }
    }

    int now = 0;
    for (int i = 1; i <= cnt; ++i) {
        if (!deg[i]) {
            if (now) puts("0"), exit(0);
            else now = i;
        } 
    }
    printf("%d\n", siz[now]);
    return 0;
}