The 2021 ICPC Asia Regionals Online Contest (II)

比赛链接

A. Sort

暴力

  1. k = 1 k=1 k=1 检查数组是否有序;
  2. k = 2 k=2 k=2 相当于再环上找个起点使得数组有序,直接判断;
  3. k ≥ 3 k\ge 3 k3 考虑插入排序,每次暴力找到第 i i i 小的数的位置 p i p_i pi ,构造 0   p i − 1   p i   n 0\ p_i-1\ p_i\ n 0 pi1 pi n 和排列 1   3   2 1\ 3\ 2 1 3 2 将第 i i i 小的放到最后面,重复 n n n 次即可,段数和 ≤ 3 n \le 3n 3n 满足要求。

O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>

using namespace std;
const int N = 1e3 + 5;
int n, k, a[N], b[N];
void Solve() {
    for (int i = 1; i <= n; ++i) scanf("%d", a + i), b[i] = a[i];
    if (is_sorted(a + 1, a + n + 1))
        return puts("0"), void();
    if (k == 1) return puts("-2"), void();
    if (k == 2) {
        int i = n;
        for (; i; --i) if (a[i - 1] > a[i]) break;
        if (a[n] > a[1] || !is_sorted(a + 1, a + i)) puts("-2");
        else printf("1\n2\n0 %d %d\n2 1\n", i - 1, n);
        return;
    }
    vector<int> v;
    int *c = b + 1;
    sort(b + 1, b + n + 1);
    for (int i = 1, j; i <= n; ++i, ++c) {
        for (j = 1; j <= n - i + 1; ++j) if (a[j] == *c) break;
        if (j == n) continue;
        v.push_back(j);
        for (int t = j; t <= n - i; ++t) a[t] = a[t + 1];
    }
    printf("%d\n", (int) v.size());
    for (auto x: v)
        if (x == 1) printf("2\n0 %d %d\n2 1\n", x, n);
        else printf("3\n0 %d %d %d\n1 3 2\n", x - 1, x, n);
}
int main() {
    int T; scanf("%d", &T);
    while (T--) scanf("%d%d", &n, &k), Solve();
    return 0;
}

树状数组

先预处理出最初第 i i i 小的数的位置 p i p_i pi ,将 i i i 挪到最后相当于对所有 p j > p i p_j>p_i pj>pi 减 1 ,那么第 i i i 小的数的真实位置应该为 p i − ∑ j < i [ p j < p i ] \displaystyle p_i-\sum_{j<i}[p_j<p_i] pij<i[pj<pi] 。用树状数组维护顺序对即可。

O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;
int n, k, a[N], c[N], p[N];
void mdy(int i, int w) { for (; i <= n; i += i & -i) c[i] += w; }
int qry(int i, int w = 0) { for (; i; i -= i & -i) w += c[i]; return w; }
void Solve() {
    for (int i = 1; i <= n; ++i) scanf("%d", a + i), p[i] = i, c[i] = 0;
    if (is_sorted(a + 1, a + n + 1))
        return puts("0"), void();
    if (k == 1) return puts("-2"), void();
    if (k == 2) {
        int i = n;
        for (; i; --i) if (a[i - 1] > a[i]) break;
        if (a[n] > a[1] || !is_sorted(a + 1, a + i)) puts("-2");
        else printf("1\n2\n0 %d %d\n2 1\n", i - 1, n);
        return;
    }
    vector<int> v;
    sort(p + 1, p + n + 1, [&](int x, int y) { return a[x] < a[y]; });
    for (int i = 1, j; i <= n; ++i) {
        j = p[i] + qry(p[i]);
        if (j == n) continue;
        v.push_back(j), mdy(p[i], -1);
    }
    printf("%d\n", (int) v.size());
    for (auto x: v)
        if (x == 1) printf("2\n0 %d %d\n2 1\n", x, n);
        else printf("3\n0 %d %d %d\n1 3 2\n", x - 1, x, n);
}
int main() {
    int T; scanf("%d", &T);
    while (T--) scanf("%d%d", &n, &k), Solve();
    return 0;
}

B. Mailman

经过的路径总是一条欧拉回路,所以题目可以转化为动态修改某个点度数,并询问将所有点度数变为偶数的最小花费。

由于 n ≤ 3 n\le 3 n3 ,考虑对列建线段树,每个节点维护中间节点度数全为偶数,且左右侧节点度数状态分别为 S , T S,T S,T 的最小花费。

合并的时候 O ( 2 3 n ) O(2^{3n}) O(23n) 枚举左右和中间的状态,只用横向边连接。

注意当 L + 1 = R   /   m i d + 1 = R L+1=R\ /\ mid+1=R L+1=R / mid+1=R 的时候,中间连的边会改变左右端点的状态。

修改点度直接递归到叶子暴力修改然后向上合并即可。

O ( 2 3 n ( m + q ) log ⁡ m ) O(2^{3n}(m+q)\log m) O(23n(m+q)logm)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using ll = int64_t;
const ll Inf = 1e18;
const int N = 5e4 + 5;
int k, n, q, a[N][2]; ll b[N][8]; // 3 * 1e9 > int
void cmin(ll &x, ll y) { x > y ? x = y : 0; }
struct Seg {
    ll tr[N << 2][8][8];
#define lc (p << 1)
#define rc (p << 1 | 1)
    void Up(int p, int L, int R) {
        int m = (L + R) >> 1;
        fp(i, 0, k) fp(j, 0, k) tr[p][i][j] = Inf;
        if (L + 1 == R) fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i ^ t][j ^ t], tr[lc][i][i] + b[m][t] + tr[rc][j][j]);
        else if (m + 1 == R) fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i][j ^ t], tr[lc][i][t] + b[m][t] + tr[rc][j][j]);
        else fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i][j], tr[lc][i][t] + b[m][t] + tr[rc][t][j]);
//        int x = m == L ? k : 0, y = m + 1 == R ? k : 0; // Unified, but twice as slow
//        fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i ^ (x & t)][j ^ (y & t)], tr[lc][i][x ? i : t] + b[m][t] + tr[rc][y ? j : t][j]);
    }
    void Build(int p, int L, int R) {
        if (L == R) {
            auto &w = tr[p];
            int x = a[L][0], y = a[L][1];
            fp(i, 0, k) fp(j, 0, k) w[i][j] = Inf;
            if (k == 3) {
                if (L == 1 || L == n) w[3][3] = x, w[0][0] = 0;
                else w[0][0] = x, w[3][3] = 0;
            } else {
                if (L == 1 || L == n)
                    w[7][7] = x + y, w[4][4] = y, w[2][2] = 0, w[1][1] = x;
                else w[0][0] = x + y, w[3][3] = y, w[5][5] = 0, w[6][6] = x;
            }
            return;
        }
        int m = (L + R) >> 1;
        Build(lc, L, m), Build(rc, m + 1, R), Up(p, L, R);
    }
    void mdy(int p, int L,int R, int x,int y){
        if (L == R) {
            static ll w[8];
            x = 1 << (x - 1);
            fp(i, 0, k) w[i ^ x] = tr[p][i][i];
            fp(i, 0, k) tr[p][i][i] = w[i];
            return;
        }
        int m = (L + R) >> 1;
        y <= m ? mdy(lc, L, m, x, y) : mdy(rc, m + 1, R, x, y), Up(p, L, R);
    }
}T;

int main() {
    ll ans = 0;
    scanf("%d%d%d", &k, &n, &q);
    fp(i, 0, k - 2) fp(j, 1, n) scanf("%d", a[j] + i), ans += a[j][i];
    fp(i, 0, k - 1) fp(j, 1, n - 1) scanf("%lld", b[j] + (1 << i)), ans += b[j][1 << i];
    fp(j, 1, n - 1) fp(i, 0, 7) b[j][i] = b[j][i & (i - 1)] + b[j][i & -i];
    k = (1 << k) - 1, T.Build(1, 1, n);
    for (int x1, y1, x2, y2, w; q--;) {
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &w), ans += w;
        T.mdy(1, 1, n, x1, y1), T.mdy(1, 1, n, x2, y2);
        printf("%lld\n", ans + T.tr[1][0][0]);
    }
    return 0;
}

C. Range Subsequence

莫队+分块 1

考虑如何从 [ l + 1 , r ] [l+1,r] [l+1,r] 的答案求出 [ l , r ] [l,r] [l,r] 的答案。

f ( l , r ) f(l,r) f(l,r) 表示 [ l , r ] [l,r] [l,r] 中以 a l a_l al 开头的最长 Range 子序列长度, b i b_i bi 表示 i i i 所在的块。

a n s l , r = a n s l + 1 , r + f ( l , r ) − f ( n x l , r ) ans_{l,r}=ans_{l+1,r}+f(l,r)-f(nx_l,r) ansl,r=ansl+1,r+f(l,r)f(nxl,r) ,其中 n x l nx_l nxl 表示下一个值等于 a l a_l al 的位置。

考虑分块预处理,对于所有 i i i ,暴力求出 f 1 ( i , k ) = f ( i , k n ) , k ≥ b i f_1(i,k)=f(i,k\sqrt n),k\ge b_i f1(i,k)=f(i,kn ),kbi f 2 ( i , j ) = f ( i , i + j ) f_2(i,j)=f(i,i+j) f2(i,j)=f(i,i+j) ,其中 i , i + j i,i+j i,i+j 在同一个块中。

对于每个块 t t t 维护块内每个数第一次和最后一次出现的位置 p t , x , q t , x p_{t,x},q_{t,x} pt,x,qt,x ,则有
f 1 ( i , k ) = f 1 ( i , k − 1 ) + f 1 ( p k , a i + f 1 ( i , k − 1 ) , k ) f_1(i,k)=f_1(i,k-1)+f_1(p_{k,a_i+f_1(i,k-1)},k) f1(i,k)=f1(i,k1)+f1(pk,ai+f1(i,k1),k)
同理若 l , r l,r l,r 不在一个块中,则
f ( l , r ) = f 1 ( l , b r − 1 ) + f 2 ( p b r , a l + f 1 ( l , b r − 1 ) , r ) f(l,r)=f_1(l,b_r-1)+f_2(p_{b_r,a_l+f_1(l,b_r-1)},r) f(l,r)=f1(l,br1)+f2(pbr,al+f1(l,br1),r)
对于从 [ l , r − 1 ] [l,r-1] [l,r1] 的答案求出 [ l , r ] [l,r] [l,r] ,对称地维护以 a r a_r ar 结尾的答案 g ( l , r ) g(l,r) g(l,r) ,同样也可以得到 g 1 ( i , k ) = g ( k n , i ) , k < b i g_1(i,k)=g(k\sqrt n,i),k<b_i g1(i,k)=g(kn ,i),k<bi g 2 ( i , j ) = g ( i − j , i ) g_2(i,j)=g(i-j,i) g2(i,j)=g(ij,i)

注意到 f 1 , g 1 f_1,g_1 f1,g1 以及 f 2 , g 2 f_2,g_2 f2,g2 的空间互补,所以可以压缩空间到 2 n n ≈ 241   M B 2n\sqrt n\approx 241{\ \rm MB} 2nn 241 MB ,所以 p , q p,q p,q 可以直接开成两个 n n n\sqrt n nn 的桶(因为 u n o r d e r e d _ m a p \rm unordered\_map unordered_map c o u n t \rm count count 太慢过不了),总空间约 500   M B 500\ \rm MB 500 MB

O ( n n + n m ) O(n\sqrt n+n\sqrt m) O(nn +nm )

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
using ll = int64_t;
const int N = 1e5 + 5;
struct qry { int l, r, id; } q[N];
int a[N], b[N], pr[N], nx[N];
vector<int> f1[N], f2[N], f3[N], p1[315], p2[315];
int main() {
    int n, m, B, S, T; ll val = 0;
    scanf("%d%d", &n, &m), B = sqrt(n) + 1, S = n / sqrt(m) + 1, T = n / B + 1;
    vector<int> pos(n); vector<ll> ans(m);
    fp(i, 1, n) scanf("%d", a + i), --a[i];
    fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    sort(q, q + m, [&](qry a, qry b) { return a.l / S == b.l / S ? (a.l / S & 1 ? a.r > b.r : a.r < b.r) : a.l < b.l; });
    fp(t, 0, T - 1) {
        int L = t * B + 1, R = min(n, L + B - 1);
        p1[t].assign(n, 0), p2[t] = p1[t];
        fp(i, L, R) {
            auto &f = f2[i];
            pr[i] = pos[a[i]], pos[a[i]] = i, b[i] = t;
            f.assign(R - i + 1, 0), f[0] = 1, p2[t][a[i]] = i;
            fp(j, i + 1, R) f[j - i] = f[j - i - 1] + (a[j] == a[i] + f[j - i - 1]);
            f1[i].assign(T + 1, 0), f1[i][t + 1] = f[R - i];
        }
        fd(i, R, L) {
            auto &f = f3[i];
            f.assign(i - L + 1, 0), f[0] = 1, p1[t][a[i]] = i;
            fd(j, i - 1, L) f[i - j] = f[i - j - 1] + (a[j] == a[i] - f[i - j - 1]);
            f1[i][t] = f[i - L];
            fd(k, b[i] - 1, 0) f1[i][k] = f1[i][k + 1] + (a[i] >= f1[i][k + 1] && p2[k][a[i] - f1[i][k + 1]] ? f3[p2[k][a[i] - f1[i][k + 1]]].back() : 0);
        }
    }
    pos.assign(n, n + 1);
    fd(i, n, 1) {
        nx[i] = pos[a[i]], pos[a[i]] = i;
        fp(k, b[i] + 1, T - 1) f1[i][k + 1] = f1[i][k] + (a[i] + f1[i][k] < n && p1[k][a[i] + f1[i][k]] ? f2[p1[k][a[i] + f1[i][k]]].back() : 0);
    }
    auto f = [&](int L, int R) {
        if (L > R) return 0;
        if (b[L] == b[R]) return f2[L][R - L];
        int w = f1[L][b[R]], nx = a[L] + w, p;
        if (nx < n && (p = p1[b[R]][nx]) && p <= R) w += f2[p][R - p];
        return w;
    };
    auto g = [&](int L, int R) {
        if (L > R) return 0;
        if (b[L] == b[R]) return f3[R][R - L];
        int w = f1[R][b[L] + 1], nx = a[R] - w, p;
        if (nx >= 0 && (p = p2[b[L]][nx]) && p >= L) w += f3[p][p - L];
        return w;
    };
    int L = 1, R = 0;
    fp(i, 0, m - 1) {
        int ql = q[i].l, qr = q[i].r;
        while (L > ql) --L, val += f(L, R) - f(nx[L], R);
        while (R < qr) ++R, val += g(L, R) - g(L, pr[R]);
        while (L < ql) val -= f(L, R) - f(nx[L], R), L++;
        while (R > qr) val -= g(L, R) - g(L, pr[R]), R--;
        ans[q[i].id] = val;
    }
    for (auto w: ans) printf("%lld\n", w);
    return 0;
}

莫队+分块 2

上面那种写法太麻烦了,而且还卡空间,卡常。

考虑将 f 1 ( i , k ) f_1(i,k) f1(i,k) 的定义改为值 a i + f ( i , k n ) a_i+f(i,k\sqrt n) ai+f(i,kn ) 的位置,则 f ( i , k n ) = a f 1 ( i , k ) − a i + 1 f(i,k\sqrt n)=a_{f_1(i,k)}-a_i+1 f(i,kn )=af1(i,k)ai+1。;记 n x t i nxt_i nxti 表示下一个 a i + 1 a_i+1 ai+1 出现的位置。

从大到小枚举 i i i ,从 i i i 开始,在块内不断跳 n x t nxt nxt 得到 f 1 ( i , b i + 1 ) f_1(i,b_i+1) f1(i,bi+1) ,然后再跳一次 n x t nxt nxt 得到下一个位置 p ≥ i p\ge i pi ,则 f 1 ( p , k ) f_1(p,k) f1(p,k) 已经求出,于是有

f 1 ( i , k ) = { f 1 ( i , b i + 1 ) b p ≥ k f 1 ( p , k ) b p < k f_1(i,k)=\left\{\begin{aligned} &f_1(i,b_i+1) & b_p\ge k\\ &f_1(p,k) & b_p<k \end{aligned}\right. f1(i,k)={f1(i,bi+1)f1(p,k)bpkbp<k

其他数组也可以做同样的简化。

O ( n n + n m ) O(n\sqrt n+n\sqrt m) O(nn +nm )

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
const int N = 1e5 + 5;
using ll = int64_t;
using arr = int[N];
struct qry { int l, r, id; } q[N];
arr a, b, p1, n1, p2, n2;
vector<int> f1[N];
vector<short> f2[N], f3[N];
int f(int L, int R) {
    if (L > R) return 0;
    if (b[L] == b[R]) return f2[L][R - L];
    int p = f1[L][b[R]], w = a[p] - a[L] + 1;
    if ((p = n2[p]) <= R) w += f2[p][R - p];
    return w;
}
int g(int L, int R) {
    if (L > R) return 0;
    if (b[L] == b[R]) return f3[R][R - L];
    int p = f1[R][b[L] + 1], w = a[R] - a[p] + 1;
    if ((p = p2[p]) >= L) w += f3[p][p - L];
    return w;
}
int main() {
    int n, m, B, S, T; ll val = 0;
    scanf("%d%d", &n, &m);
    b[0] = -1, b[n + 1] = n + 1;
    vector<int> pos(n + 1); vector<ll> ans(m);
    B = sqrt(n) + 1, S = n / sqrt(m) + 1, T = n / B + 1;
    fp(i, 1, n) scanf("%d", a + i), f1[i].assign(T + 1, 0);
    fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    sort(q, q + m, [&](qry a, qry b) { return a.l / S == b.l / S ? (a.l / S & 1 ? a.r > b.r : a.r < b.r) : a.l < b.l; });
    fp(t, 0, T - 1) {
        int L = t * B + 1, R = min(n, L + B - 1), x, p;
        fp(i, L, R) {
            f3[i].assign(i - L + 1, 1), b[i] = t;
            p1[i] = pos[x = a[i]], pos[x] = i, p = p2[i] = pos[x - 1];
            fd(j, p, L) f3[i][i - j] = f3[p][p - j] + 1;
            for (p = i; p >= L; p = p2[p]) f1[i][t] = p;
            fd(k, t - 1, 0) f1[i][k] = b[p] >= k ? f1[p][k] : f1[i][t];
        }
    }
    pos.assign(n + 2, n + 1);
    fd(t, T - 1, 0) {
        int L = t * B + 1, R = min(n, L + B - 1), x, p;
        fd(i, R, L) {
            f2[i].assign(R - i + 1, 1);
            n1[i] = pos[x = a[i]], pos[x] = i, p = n2[i] = pos[x + 1];
            fp(j, p, R) f2[i][j - i] = f2[p][j - p] + 1;
            for (p = i; p <= R; p = n2[p]) f1[i][t + 1] = p;
            fp(k, t + 2, T - 1) f1[i][k] = b[p] < k ? f1[p][k] : f1[i][t + 1];
        }
    }
    int L = 1, R = 0;
    fp(i, 0, m - 1) {
        int ql = q[i].l, qr = q[i].r;
        while (L > ql) --L, val += f(L, R) - f(n1[L], R);
        while (R < qr) ++R, val += g(L, R) - g(L, p1[R]);
        while (L < ql) val -= f(L, R) - f(n1[L], R), L++;
        while (R > qr) val -= g(L, R) - g(L, p1[R]), R--;
        ans[q[i].id] = val;
    }
    for (auto w: ans) printf("%lld\n", w);
    return 0;
}

回滚莫队

考虑只加不删的回滚莫队。

f i f_i fi 表示对于以 i i i 结尾的最长子序列为 f i , f i + 1 , . . . , i = d e f [ f i , i ] f_i,f_i+1,...,i\overset{\rm def}{=}[f_i,i] fi,fi+1,...,i=def[fi,i] c i = ∑ j [ f j = i ] \displaystyle c_i=\sum_j [f_j=i] ci=j[fj=i]

若在右边加入一个值 x x x ,且 f x > f x − 1 f_x>f_{x-1} fx>fx1 ,则可以多出 f x − f x − 1 f_x-f_{x-1} fxfx1 个不同的连续子序列,即 [ f x − 1 , x ] , [ f x − 1 + 1 , x ] , . . . , [ f x − 1 , x ] [f_{x-1},x],[f_{x-1}+1,x],...,[f_x-1,x] [fx1,x],[fx1+1,x],...,[fx1,x] ,同时更新 f x , c f x , c f x − 1 f_x,c_{f_x},c_{f_{x-1}} fx,cfx,cfx1 的值即可。

考虑右端点固定的情况下再左边插入一个值 x x x ,那么所有 f i = x + 1 f_i=x+1 fi=x+1 的连续序列都可以向前拓展一位到 x x x ,对答案贡献为 c x + 1 c_{x+1} cx+1 ,同时更新 c x , c x + 1 c_x,c_{x+1} cx,cx+1 即可。

O ( n n + m n ) O(n\sqrt n+m\sqrt n) O(nn +mn )

#include <bits/stdc++.h>

#define fp(i, a, b) for(int (i) = (a), i##_ = (b) + 1; (i) < i##_; ++(i))
#define fd(i, a, b) for(int (i) = (a), i##_ = (b) - 1; (i) > i##_; --(i))
using namespace std;
const int N = 1e5 + 5;
using ll = long long;
int n, m, B, a[N], b[N];
struct Qry { int l, r, id; } q[N];
struct addMoTeam {
    int tp{}, f[N]{};
    ll ans{}, c[N]{};
    pair<ll *, ll> s[N];
    void init() { ans = 0; fp(i, 0, n) f[i] = i + 1, c[i + 1] = 1; }
    int calc(int l, int r) {
        int res = 0, p;
        fp(i, l, r) if (p = a[i], f[p] > f[p - 1])
            res += f[p] - f[p - 1], f[p] = f[p - 1];
        fp(i, l, r) f[a[i]] = a[i] + 1;
        return res;
    }
    void addR(int x) {
        if (f[x] <= f[x - 1]) return;
        ans += f[x] - f[x - 1], --c[f[x]], ++c[f[x - 1]], f[x] = f[x - 1];
    }
    void addL(int x) {
        if (!c[x + 1]) return;
        mdy(ans, ans + c[x + 1]), mdy(c[x], c[x] + c[x + 1]), mdy(c[x + 1], 0);
    }
    void mdy(ll &x, ll y) { s[++tp] = {&x, x}, x = y; }
    void roll() { for (; tp; tp--) *s[tp].first = s[tp].second; }
} D;
int main() {
    scanf("%d%d", &n, &m), B = sqrt(n) + 1;
    fp(i, 1, n) scanf("%d", a + i), b[i] = i / B;
    fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    sort(q, q + m, [](Qry x, Qry y) { return b[x.l] == b[y.l] ? x.r < y.r : x.l < y.l; });
    vector<ll> ans(m);
    for (int R = B - 1, i = 0; R - B < n; R += B) {
        D.init();
        for (; i < m && q[i].r <= R; ++i) ans[q[i].id] = D.calc(q[i].l, q[i].r);
        for (int p = R; i < m && q[i].l <= R; ++i) {
            while (p < q[i].r) D.addR(a[++p]);
            fd(l, R, q[i].l) D.addL(a[l]);
            ans[q[i].id] = D.ans, D.roll();
        }
    }
    for (auto x: ans) printf("%lld\n", x);
    return 0;
}

D. Linear Algebra

g ( x ) = ∑ i = 0 2 n c i x i , f ( x ) = ∑ i = 0 n a i x i \displaystyle g(x)=\sum_{i=0}^{2n}c_ix^i, f(x)=\sum_{i=0}^na_ix^i g(x)=i=02ncixi,f(x)=i=0naixi ,则 f ( x + 1 ) = ∑ i = 0 n x i ∑ j ≥ i ( j i ) a j = d e f ∑ i = 0 n b i x i \displaystyle f(x+1)=\sum_{i=0}^nx^i\sum_{j\ge i}{j\choose i}a_j\overset{\rm def}{=}\sum_{i=0}^nb_ix^i f(x+1)=i=0nxiji(ij)aj=defi=0nbixi 。由 c 2 n = 1 c_{2n}=1 c2n=1 可得 a n = b n = 1 a_n=b_n=1 an=bn=1 。考虑 c n + k c_{n+k} cn+k
c n + k = [ x n + k ] f ( x ) f ( x + 1 ) = ∑ i = 0 n − k a n − i b k + i = a n b k + a k b n + ∑ i = 1 n − k − 1 a n − i b k + i = 2 a n a k + a n ∑ i > k ( i k ) a i + ∑ i = k + 1 n − 1 a n − i + k b i ⇒ { k ! ( b k − a k ) = ∑ i = k + 1 n i ! a i ⋅ 1 ( i − k ) ! 2 a k − c n + k = − ( b k − a k ) − ∑ i = k + 1 n − 1 a n − i + k b i \begin{aligned} c_{n+k}=&[x^{n+k}]f(x)f(x+1)\\ &=\sum_{i=0}^{n-k}a_{n-i}b_{k+i}\\ &=a_nb_k+a_kb_n+\sum_{i=1}^{n-k-1}a_{n-i}b_{k+i}\\ &=2a_na_k+a_n\sum_{i>k}{i\choose k}a_i+\sum_{i=k+1}^{n-1}a_{n-i+k}b_i \end{aligned}\\ \Rightarrow\left\{ \begin{aligned} &k!(b_k-a_k)=\sum_{i=k+1}^ni!a_i\cdot\frac1{(i-k)!}\\ &2a_k-c_{n+k}=-(b_k-a_k)-\sum_{i=k+1}^{n-1}a_{n-i+k}b_i\\ \end{aligned}\right.\\ cn+k=[xn+k]f(x)f(x+1)=i=0nkanibk+i=anbk+akbn+i=1nk1anibk+i=2anak+ani>k(ki)ai+i=k+1n1ani+kbik!(bkak)=i=k+1ni!ai(ik)!12akcn+k=(bkak)i=k+1n1ani+kbi

考虑分治,其中 b k − a k b_k-a_k bkak 的形式是标准的分治 F F T \rm FFT FFT 形式,可以直接算。

考虑 ∑ i = k + 1 n − 1 a n − i + k b i \displaystyle\sum_{i=k+1}^{n-1}a_{n-i+k}b_i i=k+1n1ani+kbi ,对于区间 [ L , R ) [L,R) [L,R) ,令 k = max ⁡ ( m ,   n − R + L ) k=\max(m,\ n-R+L) k=max(m, nR+L) 。考虑 [ m , R ) [m,R) [m,R) [ L , m ) [L,m) [L,m) 的贡献,可以分成 3 3 3 个部分: a [ m , R ) × b [ k , n ) ,   b [ m , R ) × a [ k , n ) a_{[m,R)}\times b_{[k,n)},\ b_{[m,R)}\times a_{[k,n)} a[m,R)×b[k,n), b[m,R)×a[k,n) ,以及减去前两者重复的部分 a [ k , R ) × b [ k , R ) a_{[k,R)}\times b_{[k,R)} a[k,R)×b[k,R)

O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
const int N = 1e5 + 5, P = 998244353, I2 = (P + 1) >> 1;
using ll = int64_t;
#define MUL(a, b) ((ll) (a) * (b) % P)
#define ADD(a, b) (((a) += (b)) >= P ? (a) -=P : 0) // (a += b) %= P
#define SUB(a, b) (((a) -= (b)) < 0 ? (a) += P: 0)  // ((a -= b) += P) %= P
int POW(ll a, int b = P - 2, ll x = 1) { for (; b; b >>= 1, a = a * a % P) if (b & 1) x = x * a % P; return x; }
namespace NTT {
    const int g = 3;
    vector<int> Omega(int L) {
        int wn = POW(g, P / L);
        vector<int> w(L); w[L >> 1] = 1;
        fp(i, L / 2 + 1, L - 1) w[i] = MUL(w[i - 1], wn);
        fd(i, L / 2 - 1, 1) w[i] = w[i << 1];
        return w;
    }
    auto W = Omega(1 << 18); // NOLINT
    void calc1(int &x, int &y, ll z) { z = (x - y + P) * z % P, ADD(x, y), y = z;}
    void calc2(int &x, int &y, ll z) { z = y * z % P, y = x, SUB(y, z), ADD(x, z); }
    void DIF(int *a, int n) {
        for (int k = n >> 1; k; k >>= 1)
            for (int i = 0; i < n; i += k << 1)
                fp(j, 0, k - 1) calc1(a[i + j], a[i + j + k], W[j + k]);
    }
    void IDIT(int *a, int n){
        for (int k = 1; k < n; k <<= 1)
            for (int i = 0; i < n; i += k << 1)
                fp(j, 0, k - 1) calc2(a[i + j], a[i + j + k], W[j + k]);
        int Inv = P - (P - 1) / n;
        fp(i, 0, n - 1) a[i] = MUL(a[i], Inv);
        reverse(a + 1, a + n);
    }
}
namespace Polynomial {
    using Poly = std::vector<int>;
    void DFT(Poly &a) { NTT::DIF(a.data(), a.size()); }
    void IDFT(Poly &a) { NTT::IDIT(a.data(), a.size()); }
    int getLen(int n) { return 1 << (32 - __builtin_clz(n)); }
    Poly &dot(Poly &a, Poly &b) {
        fp(i, 0, a.size() - 1) a[i] = MUL(a[i], b[i]);
        return a;
    }
    Poly operator*(Poly a, Poly b) {
        int n = a.size() + b.size() - 1;
        if (a.size() <= 8 || b.size() <= 8) {
            Poly c(n);
            fp(i, 0, a.size() - 1) fp(j, 0, b.size() - 1)
                c[i + j] = (c[i + j] + (ll) a[i] * b[j]) % P;
            return c;
        }
        int L = getLen(n);
        a.resize(L), b.resize(L);
        DFT(a), DFT(b), dot(a, b), IDFT(a);
        return a.resize(n), a;
    }
    Poly dcov(Poly &a, Poly b) { return reverse(b.begin(), b.end()), a * b; }
}
using namespace Polynomial;
int n, fac[N], ifac[N], a[N], b[N], g[2 * N];
void cdq(int L, int R) {
    if (L + 1 == R) {
        b[L] = MUL(b[L], ifac[L]);
        a[L] = L == n ? 1 : MUL(a[L] - b[L] + P, I2);
        ADD(b[L], a[L]);
        return;
    }
    int m = (L + R) >> 1, k = max(m, n - R + L);
    cdq(m, R);
    Poly A = Poly(a + k, a + n) * Poly(b + m, b + R);
    fp(i, max(L, k + m - n), m - 1) SUB(a[i], A[i - k - m + n]);
    A = Poly(b + k, b + n) * Poly(a + m, a + R);
    fp(i, max(L, k + m - n), m - 1) SUB(a[i], A[i - k - m + n]);
    if (k < R) {
        A = Poly(a + k, a + R) * Poly(b + k, b + R);
        fp(i, max(L, 2 * k - n), min(m, 2 * R - n - 1) - 1) ADD(a[i], A[i - 2 * k + n]);
    }
    A.resize(R - m);
    fp(i, m, R - 1) A[i - m] = MUL(a[i], fac[i]);
    A = dcov(A, Poly(ifac, ifac + R - L));
    fp(i, L, m - 1) ADD(b[i], A[i - m + R - L - 1]);
    cdq(L, m);
}
int main() {
    scanf("%d", &n), fac[0] = 1;
    fp(i, 1, n) fac[i] = MUL(fac[i - 1], i);
    ifac[n] = POW(fac[n]);
    fd(i, n, 1) ifac[i - 1] = MUL(ifac[i], i);
    fp(i, 0, 2 * n) scanf("%d", g + i);
    copy(g + n, g + 2 * n + 1, a), cdq(0, n + 1);
    Poly f = Poly(a, a + n + 1) * Poly(b, b + n + 1);
    fp(i, 0, 2 * n) if (f[i] != g[i]) return printf("No"), 0;
    puts("Yes");
    fp(i, 0, n) printf("%d%c", a[i], " \n"[i == n]);
    return 0;
}

E. Nearest Point

考虑固定一个点 i i i ,并 i i i 为参考系

P r o j ( j , α ) {\rm Proj}(j,\alpha) Proj(j,α) 表示 j j j v α = ( cos ⁡ α , sin ⁡ α ) v_{\alpha}=(\cos\alpha,\sin\alpha) vα=(cosα,sinα) 方向上的投影长。

对于任意两个向量 j , k j,k j,k ,存在 4 个临界角度 α \alpha α 使得 P r o j ( j , α ) = P r o j ( k , α ) {\rm Proj}(j,\alpha)={\rm Proj}(k,\alpha) Proj(j,α)=Proj(k,α) ,易证 v α ⊥ j + k v_\alpha\perp j+k vαj+k v α ⊥ j − k v_\alpha\perp j-k vαjk (画图理解一下)。

一共有 O ( n 2 ) O(n^2) O(n2) 个临界角,把 [ − π , π ) [-\pi,\pi) [π,π) 分成了 O ( n 2 ) O(n^2) O(n2) 段,每一段内 i i i 的最近点是不变的。所以可以每一段内任取一个角度,算出最近点,并把这一段的概率都算到那个点上即可。

O ( n 4 log ⁡ n ) O(n^4\log n) O(n4logn)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using db = double;
const db Pi = acos(-1);
struct Vec {
    int x, y;
    Vec operator+(Vec b) const { return {x + b.x, y + b.y}; }
    Vec operator-(Vec b) const { return {x - b.x, y - b.y}; }
    db v_deg() const { return atan2(x, -y); }
};
int main() {
    int n; scanf("%d", &n);
    vector<db> d, ans(n);
    vector<Vec> a(n), b(n);
    for (auto &p: a) scanf("%d%d", &p.x, &p.y);
    auto Proj = [&](db x, db y, Vec c) { return abs(x * c.x + y * c.y); };
    fp(i, 0, n - 1) {
        ans.assign(n, 0), d = {-Pi, Pi};
        fp(j, 0, n - 1) b[j] = a[j] - a[i];
        fp(j, 0, n - 1) fp(k, j + 1, n - 1)
            d.push_back((b[j] - b[k]).v_deg()), d.push_back((b[j] + b[k]).v_deg());
        for (auto deg: d) d.push_back(deg + (deg < 0 ? Pi : -Pi));
        sort(d.begin(), d.end());
        fp(t, 1, d.size() - 1) {
            if (d[t] - d[t - 1] < 1e-11) continue;
            int k = 0; db L = d[t] - d[t - 1], m = d[t - 1] + L * 0.5, x = cos(m), y = sin(m), mn = 1e18, w;
            fp(j, 0, n - 1) if (j != i && mn - (w = Proj(x, y, b[j])) > 1e-9) mn = w, k = j;
            ans[k] += L;
        }
        fp(j, 0, n - 1) printf("%.9lf%c", i == j ? 0 : ans[j] / Pi * .5, " \n"[j == n - 1]);
    }
    return 0;
}

F. Leapfrog

题目等价于找到一个排列 p p p ,最大化 a p n + ∑ i = 1 n − 1 b p i t p i n − i \displaystyle a_{p_n}+\sum_{i=1}^{n-1}b_{p_i}t_{p_i}^{n-i} apn+i=1n1bpitpini

p ′ p' p 为以 t t t 第一关键字降序, b b b 第二关键字升序, a a a 第三关键字升序排序后的排列。

注意到 b ≤ 1 0 4 , t ∈ { 2 , 3 } b\le10^4,t\in\{2,3\} b104,t{2,3} ,而 1 0 4 × 2 23 < 3 23 10^4\times2^{23}<3^{23} 104×223<323 ,所以 p ′ p' p 与最优排列的前 n − 23 n-23 n23 项一定相同。

考虑用 D P \rm DP DP 来求出最后剩下的 m m m 个物品的最优排列。

f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1 表示放了 i i i 个物品,其中有 j j j t = 2 t=2 t=2 的物品,以及是否有物品放到了最后的最优答案, x i / y i x_i/y_i xi/yi 分别表示第 i i i t = 2 / 3 t=2/3 t=2/3 的物品的位置。

则有
f i , j , 0 = max ⁡ { f i − 1 , j , 0 + b x i − j 2 m − i , f i − 1 , j − 1 , 0 + b y j 3 m − i } f i , j , 1 = max ⁡ { f i − 1 , j , 1 + b x i − j 2 m − i + 1 , f i − 1 , j − 1 , 1 + b y j 3 m − i + 1 , f i − 1 , j , 0 + a x i − j , f i − 1 , j − 1 , 0 + a y j } \begin{aligned} f_{i,j,0}&=\max\{f_{i-1,j,0}+b_{x_{i-j}}2^{m-i}, f_{i-1,j-1,0}+b_{y_j}3^{m-i}\}\\ f_{i,j,1}&=\max\{f_{i-1,j,1}+b_{x_{i-j}}2^{m-i+1}, f_{i-1,j-1,1}+b_{y_j}3^{m-i+1},f_{i-1,j,0}+a_{x_{i-j}}, f_{i-1,j-1,0}+a_{y_j}\}\\ \end{aligned} fi,j,0fi,j,1=max{fi1,j,0+bxij2mi,fi1,j1,0+byj3mi}=max{fi1,j,1+bxij2mi+1,fi1,j1,1+byj3mi+1,fi1,j,0+axij,fi1,j1,0+ayj}
O ( n log ⁡ n + 2 3 2 ) O(n\log n+23^2) O(nlogn+232)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using ll = int64_t;
const int N = 1e5 + 5, P = 998244353;
struct Data { int a, b, t; } p[N];
int n; ll f[24][24][2], p2[N], p3[N];
void cmax(ll &x, ll y) { x < y ? x = y : 0; }
ll dp(vector<Data> &a, vector<Data> &b) {
    int A = a.size(), B = b.size();
    memset(f, 0x80, sizeof f), f[0][0][0] = 0, n = A + B;
    fp(i, 0, n - 1) fp(j, 0, min(i, B)) {
        if (i - j < A) {
            cmax(f[i + 1][j][1], f[i][j][0] + a[i - j].a);
            fp(k, 0, 1) cmax(f[i + 1][j][k], f[i][j][k] + a[i - j].b * p2[n - i - 1 + k]); // + k !!
        }
        if (j == B) continue;
        cmax(f[i + 1][j + 1][1], f[i][j][0] + b[j].a);
        fp(k, 0, 1) cmax(f[i + 1][j + 1][k], f[i][j][k] + b[j].b * p3[n - i - 1 + k]); // + k !!
    }
    return f[n][B][1];
}
void Solve() {
    fp(i, 1, n) scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].t);
    sort(p + 1, p + n + 1, [](Data x, Data y) { return x.t == y.t ? (x.b == y.b ? x.a < y.a : x.b > y.b) : x.t > y.t; });
    ll ans = 0, m = max(0, n - 23); vector<Data>a, b;
    fp(i, 1, m) ans += (ll) p[i].b * (p[i].t == 2 ? p2[n - i] : p3[n - i]) % P;
    fp(i, m + 1, n) p[i].t == 2 ? a.push_back(p[i]) : b.push_back(p[i]);
    printf("%lld\n", (ans + dp(a, b)) % P);
}
int main() {
    p2[0] = p3[0] = 1;
    fp(i, 1, 23) p2[i] = 2 * p2[i - 1], p3[i] = 3 * p3[i - 1];
    fp(i, 24, N - 1) p2[i] = 2 * p2[i - 1] % P, p3[i] = 3 * p3[i - 1] % P;
    int T; scanf("%d", &T);
    while (T--) scanf("%d", &n), Solve();
    return 0;
}

G. Limit

直接泰勒展开,原式可化为
x − t ∑ i = 1 n a i ∑ j ≥ 1 ( − 1 ) j + 1 ( b i x ) j j = ∑ j ≥ 1 1 j x j − t ∑ i = 1 n ( − 1 ) j + 1 a i b i j x^{-t}\sum_{i=1}^na_i\sum_{j\ge1}(-1)^{j+1}\frac{(b_ix)^j}{j}=\sum_{j\ge1}\frac1jx^{j-t}\sum_{i=1}^n(-1)^{j+1}a_ib_i^j xti=1naij1(1)j+1j(bix)j=j1j1xjti=1n(1)j+1aibij
算出所有系数,根据极限定义输出答案即可。

O ( t n ) O(tn) O(tn)

#include<bits/stdc++.h>

using namespace std;
using ll = int64_t;
int main() {
    int n, t;
    scanf("%d%d", &n, &t);
    if (!t)return printf("0"), 0;
    vector<ll> a(6);
    ll x, y, z, g, b = t;
    for (int i = 0; i < n; ++i) {
        scanf("%lld%lld", &x, &y), z = y;
        for (int j = 1; j < 6; ++j) a[j] += (j & 1 ? 1 : -1) * x * z, z *= y;
    }
    for (int i = 1; i < t; ++i)
        if (a[i]) return printf("infinity"), 0;
    g = __gcd(a[t], b), a[t] /= g, b /= g;
    if (abs(b) == 1) printf("%lld", a[t]);
    else printf("%lld/%d", a[t], b);
    return 0;
}

H. Set

随机

每次随机在 S S S 中随机选取 l = ⌈ 512 r ⌉ \displaystyle l=\left\lceil\frac{512}r\right\rceil l=r512 个数即可。

考虑出错的概率,即存在 r r r 个集合,这 r r r 个集合的并至多为 256 256 256 个数中的任意 127 127 127 个,即
p E r r o r ≤ ( k r ) ( 256 127 ) [ ( 127 l ) ( 256 l ) ] r < 3.502 × 1 0 − 88 p_{\rm Error}\le {k\choose r}{256\choose 127}\left[\frac{{127\choose l}}{{256\choose l}}\right]^r<3.502\times10^{-88} pError(rk)(127256)[(l256)(l127)]r<3.502×1088
完全可以通过。

O ( 256 k ) O(256k) O(256k)

#include<bits/stdc++.h>

using namespace std;
int main() {
    int k, r;
    scanf("%d%d", &k, &r), r = (512 + r - 1) / r;
    while (k--) {
        string s(256, '0');
        for (int i = 0; i < r; ++i) s[rand() & 255] = '1';
        puts(s.c_str());
    }
    return 0;
}

构造

I t = { i + ( t − 1 ) x m o d    256 + 1 ∣ 0 ≤ i < l } I_t=\{i+(t-1)x \mod 256 + 1\mid 0\le i< l\} It={i+(t1)xmod256+10i<l} ,任选 r r r 个集合的并集大小最小为 l + ( r − 1 ) x ≥ 128 l+(r-1)x\ge 128 l+(r1)x128 ,取 x ≥ ⌈ 128 − l r − 1 ⌉ \displaystyle x\ge\left\lceil\frac{128-l}{r-1}\right\rceil xr1128l 即可。

实际上可以求出下界为 10 10 10 ,所以只需要任选一个 10 ≤ x ≤ 246 10\le x\le 246 10x246 即可。

O ( 256 k ) O(256k) O(256k)

#include<bits/stdc++.h>

using namespace std;
int main() {
    int k, r, y = 0;
    scanf("%d%d", &k, &r), r = (512 + r - 1) / r;
    for (; k--; y += 10) {
    	string s(256, '0');
        for (int i = 0; i < r; ++i) s[(i + y) & 255] = '1';
        puts(s.c_str());
    }
    return 0;
}

I. Discrete Mathematics

J. Leaking Roof

高格子向四周低格子连边跑拓扑排序即可。

O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 500 + 5, M = N * N;
using ll = int64_t;
using db = double;
using arr = int[N];
int in[M], a[N][N], id[N][N];
vector<int> G[M];

void ADD(int u, int v) { G[u].push_back(v), ++in[v]; }

int main() {
    int n, m, cnt = 0;
    scanf("%d%d", &n, &m);
    fp(i, 1, n)a[i][0] = a[i][n + 1] = a[0][i] = a[n + 1][i] = 1e9;
    fp(i, 1, n)fp(j, 1, n)scanf("%d", a[i] + j), id[i][j] = ++cnt;
    fp(i, 1, n) fp(j, 1, n) {
        if (a[i][j] > a[i - 1][j]) ADD(id[i][j], id[i - 1][j]);
        if (a[i][j] > a[i + 1][j]) ADD(id[i][j], id[i + 1][j]);
        if (a[i][j] > a[i][j - 1]) ADD(id[i][j], id[i][j - 1]);
        if (a[i][j] > a[i][j + 1]) ADD(id[i][j], id[i][j + 1]);
    }
    queue<int> q;
    vector<db> f(cnt + 1, m);
    fp(i, 1, cnt) if (!in[i]) q.push(i);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        if (!G[u].empty()) f[u] /= G[u].size();
        for (auto v: G[u]) {
            f[v] += f[u];
            if (!--in[v]) q.push(v);
        }
    }
    for (int i = 1; i <= n; ++i, puts(""))
        fp(j, 1, n) a[i][j] ? printf("0 ") : printf("%lf ", f[id[i][j]]);
    return 0;
}

K. Meal

实际上题目相当于每个人从当前没有被选的菜中以 a i , j a_{i,j} ai,j 的概率选取一道菜。

所以直接记录当前已经打了的菜的集合为 S S S 的概率 f ( S ) f(S) f(S) ,然后直接枚举下一道菜转移即可。

O ( n 2 n ) O(n2^n) O(n2n)

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 21, M = 1 << 20 | 5, P = 998244353;
using ll = int64_t;
using arr = int[N];
vector<int> inv;
int a[N][N];

int main() {
    inv.resize(2005), inv[1] = 1;
    fp(i, 2, 2e3) inv[i] = (ll) (P - P / i) * inv[P % i] % P;
    int n, m;
    scanf("%d", &n), m = (1 << n) - 1;
    fp(i, 0, n - 1) fp(j, 0, n - 1) scanf("%d", a[i] + j);
    vector<int> f(m + 1), g(m + 1), cnt(m + 1);
    f[0] = 1;
    fp(s, 0, m) cnt[s] = __builtin_popcount(s);
    fp(i, 0, n - 1) {
        vector<ll> ans(n);
        g = f, f.assign(m + 1, 0);
        fp(s, 0, m) if (cnt[s] == i) {
            ll sum = 0, val;
            vector<int> vj;
            for (int t = m ^ s; t; t &= t - 1) vj.push_back(__builtin_ctz(t)), sum += a[i][vj.back()];
            sum = inv[sum];
            for (auto j: vj) val = (ll) g[s] * a[i][j] % P * sum % P, f[s | 1 << j] = (f[s | 1 << j] + val) % P, ans[j] += val;
        }
        fp(j, 0, n - 2) printf("%lld ", ans[j] % P);
        printf("%lld%c", ans.back() % P, "\n\0"[i == n - 1]);
    }
    return 0;
}

L. Euler Function

p p p 是质数,根据欧拉函数的性质可得:

  1. p ∣ q p\mid q pq 时, φ ( p q ) = p φ ( q ) \varphi(pq)=p\varphi(q) φ(pq)=pφ(q)
  2. p ∤ q p\nmid q pq 时, φ ( p q ) = ( p − 1 ) φ ( q ) \varphi(pq)=(p-1)\varphi(q) φ(pq)=(p1)φ(q)

注意到 w ≤ 100 w\le 100 w100 ,最多只有 25 25 25 个质数。

考虑线段树,每个节点开一个长度为 25 25 25 b i t s e t \rm bitset bitset 表示区间内是否所有数都含有这个质因子。

当整个区间的数都含有质因子 p p p 时,可以直接打上一个乘法标记,没有则向下递归。

由于只有 25 25 25 个质数,所以每个节点至多被暴力修改 25 25 25 次。

O ( 25 n + m log ⁡ n ) O(25n+m\log n) O(25n+mlogn)

预处理写得有点长,应该用埃氏筛法。

#include<bits/stdc++.h>

#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 1e5 + 5, A = 101, P = 998244353;
using ll = int64_t;
bitset<A> vis;
int a[N], phi[A], pr[A], id[A];
vector<pair<int, int>> val[A];
bitset<26> G[A];
void sieves() {
    phi[1] = 1;
    fp(i, 2, A - 1) {
        if (!vis[i]) pr[++pr[0]] = i, phi[i] = i - 1, val[i] = {{i, id[i] = pr[0] - 1}}, G[i][pr[0] - 1] = 1;
        for (int j = 1, x, p; j <= pr[0] && (x = i * (p = pr[j])) < A; ++j) {
            vis[x].flip(), val[x] = val[i], G[x] = G[i];
            if (i % pr[j]) phi[x] = phi[i] * (pr[j] - 1), val[x].emplace_back(pr[j], id[p]), G[x][id[p]] = 1;
            else {
                phi[x] = phi[i] * p;
                for (auto &w: val[x])
                    if (w.second == id[p])
                        w.first *= p;
                break;
            }
        }
    }
}
struct Seg {
    int tag[N << 2], tr[N << 2];
    bitset<26> f[N << 2];
#define lc (p << 1)
#define rc (p << 1 | 1)
    void Up(int p) { tr[p] = (tr[lc] + tr[rc]) % P, f[p] = f[lc] & f[rc]; }
    void upd(int p, int w) { tr[p] = (ll) tr[p] * w % P, tag[p] = (ll) tag[p] * w % P; }
    void Down(int p) { int &w = tag[p]; if (w > 1) upd(lc, w), upd(rc, w), w = 1; }
    void Build(int p, int L, int R) {
        tag[p] = 1;
        if (L == R) return f[p] = G[a[L]], tr[p] = phi[a[L]], void();
        int m = (L + R) >> 1;
        Build(lc, L, m), Build(rc, m + 1, R), Up(p);
    }
    void mdy(int p, int L, int R, int a, int b, int x, int i) {
        if (a <= L && R <= b && f[p][i]) return upd(p, x);
        if (L == R) return f[p][i] = 1, tr[p] = (ll) tr[p] * phi[x] % P, void();
        int m = (L + R) >> 1; Down(p);
        if (a <= m) mdy(lc, L, m, a, b, x, i);
        if (b > m) mdy(rc, m + 1, R, a, b, x, i);
        Up(p);
    }
    int qry(int p, int L, int R, int a, int b) {
        if (a <= L && R <= b) return tr[p];
        int m = (L + R) >> 1, w = 0; Down(p);
        if (a <= m) w = qry(lc, L, m, a, b);
        if (b > m) w = (w + qry(rc, m + 1, R, a, b)) % P;
        return w;
    }
} T;

int main() {
    sieves();
    int n, q; scanf("%d%d", &n, &q);
    fp(i, 1, n) scanf("%d", a + i);
    T.Build(1, 1, n);
    for (int op, l, r, w; q--;) {
        scanf("%d%d%d", &op, &l, &r);
        if (!op) {
            scanf("%d", &w);
            for (auto x: val[w])
                T.mdy(1, 1, n, l, r, x.first, x.second);
        } else printf("%d%c", T.qry(1, 1, n, l, r), "\n\0"[!q]);
    }
    return 0;
}

M. Addition

先求出和 c c c ,若 c ≥ 0 c\ge 0 c0 ,则默认符号位全为 1 1 1 ,否则令 c = − c c=-c c=c 并默认符号位为 − 1 -1 1

若当前位为 1 1 1 且实际符号位与默认不符,则高位 + 1 +1 +1 ,即 2 k = 2 k + 1 − 2 k 2^k=2^{k+1}-2^k 2k=2k+12k

O ( n ) O(n) O(n)

#include<bits/stdc++.h>

using namespace std;
using ll = int64_t;

int main() {
    int n; scanf("%d", &n);
    vector<int> sgn(n);
    for (auto &x: sgn)scanf("%d", &x);
    ll a = 0, b = 1;
    for (ll x, i = 0; i < n; ++i) scanf("%lld", &x), a += sgn[i] * (x << i);
    for (ll x, i = 0; i < n; ++i) scanf("%lld", &x), a += sgn[i] * (x << i);
    if (a < 0) b = -1, a = -a;
    for (ll i = 0; i < n; ++i) {
        printf("%d%c", a & 1, " \0"[i == n - 1]);
        a = (a >> 1) + (a & 1 && sgn[i] != b);
    }
    return 0;
}
  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值