Advertisement

2019ICPC南昌邀请赛题解

阅读量:

The 2019 ICPC China Nanchang National Invitational and International Silk-Road Programming Contest

B. Polynomial

Description

定义 f_i=\sum_{i=0}^{n}a_ix^{i},然后给定f_{0} ~ f_{n},现在给出 m 次询问,每次询问给定 LR,问你 \sum_{i=L}^{R}f_{i} 是多少

Tutorial

拉格朗日插值裸题,正好复习一下

拉格朗日插值是用来对任意曲线进行多项式拟合的,假设现在给定某一曲线中的任意 n + 1 个点 (x_i,y_i),那么我就可以使用拉格朗日插值求出该曲线的最高幂次为 n 近似多项式函数,其公式如下:
f(k)=\sum_{i=1}^{n}y_i\prod_{j \neq i}\frac{k-x_j}{x_i-x_j}
那么我们可以使用上式在 O(n^2) 的时间内求出该曲线内的任意一点的近似值

在本题中,显然 f_i 是一个最高幂次为 n 多项式函数,那么我们可以使用给定的 n + 1 个点来求出任意一点的值

但是 O(n^2) 的时间显然不能满足我们的需求,考虑优化

可以发现题目中的 x 是连续的,即 x = 1, 2, 3, ...,那么上式可以写成:
f(k)=\sum_{i=1}^{n}y_i\prod_{j \neq i}\frac{k-j}{i-j}

发现乘积部分的分母其实可以写成阶乘形式,而分子也可以化简

f(k)=\sum_{i=1}^{n}y_i\frac{pre_{i-1} \times suf_{i+1}}{fac_i \times (\pm fac_{n-i})}

其中

fac_i=i!

pre_i=\prod_{j=0}^{i}k-j

suf_i=\prod_{j=i}^{n}k-j

n - i 为奇数时 fac_{n-i} 需要取符号

这样我们预处理 fac 或其逆元,presuf 即可在 O(n) 时间内求出任意 x 的函数值

但是这样还是不够的,因为如果暴力求解 L ~ R 上的每个 f 时间肯定不够,还需要继续优化

百度告诉我最高幂次为 ni 项和的函数是一个最高幂次为 n + 1 的多项式,那么我们可以利用插值分别求出 L - 1R 处的前缀和做差即可

但是注意 n 个点只能确定最高幂次为 n - 1 的多项式,所以我们要先将 f_{n+1} 插出来再进行前缀和的插值求解

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

AC Code

复制代码
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 2001;
    const ll mod = 9999991;
    ll pre[maxn], suf[maxn];
    ll f[maxn], fac[maxn], inv[maxn];
    ll qpow(ll n, ll m) {
    ll res = 1;
    while(m) {
        if(m & 1) res = res * n % mod;
        n = n * n % mod;
        m >>= 1;
    }
    return res;
    }
    void init() {
    fac[0] = 1;
    for(int i = 1; i < maxn; ++i) fac[i] = fac[i - 1] * i % mod;
    inv[maxn - 1] = qpow(fac[maxn - 1], mod - 2);
    for(int i = maxn - 2; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
    }
    ll cal(int x, int s, int t) {
    int n = t - s;
    x = ((x % mod) + mod) % mod;
    pre[0] = ((x - s) + mod) % mod;
    suf[n] = ((x - t) % mod + mod) % mod;
    for(int i = 1; i <= n; ++i) pre[i] = (pre[i - 1] * (x - (i + s)) % mod + mod) % mod;
    for(int i = n - 1; i >= 0; --i) suf[i] = (suf[i + 1] * (x - (i + s)) % mod + mod) % mod;
    ll res = 0;
    for(int i = 0; i <= n; ++i) {
        ll tmp = f[i + s];
        if(i != 0) tmp = tmp * pre[i - 1] % mod;
        if(i != n) tmp = tmp * suf[i + 1] % mod;
        tmp = tmp * inv[i] % mod * inv[n - i] % mod;
        if((n - i) & 1) res = (res - tmp + mod) % mod;
        else res = (res + tmp) % mod;
    }
    return res;
    }
    int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    init();
    int T; cin >> T;
    while(T--) {
        int n, m; cin >> n >> m;
        for(int i = 0; i <= n; ++i) cin >> f[i];
        f[n + 1] = cal(n + 1, 0, n);
        for(int i = 1; i <= n + 1; ++i) f[i] = (f[i] + f[i - 1]) % mod;
        while(m--) {
            int l, r; cin >> l >> r;
            cout << ((cal(r, 0, n + 1) - cal(l - 1, 0, n + 1)) % mod + mod) % mod << '\n';
        }
    }
    return 0;
    }

F. Sequence

Description

定义 f(l,r)=a_{l}\oplus a_{l+1} \oplus a_{l+2} \oplus ... \oplus a_{r}

F(l,r) = f(l,l) \oplus f(l,l+1) \oplus ... \oplus f(l,r) \oplus f(l + 1, l + 1) \oplus f(l + 1, l + 2) \oplus ... \oplus f(l+1, r) \oplus ... \oplus f(r,r)

有两个操作

  1. 0 x ya_x=y
  2. 1 l r 查询 F(l,r)

Tutorial

单点修改 + 区间查询,应该用线段树或者树状数组都行,考虑维护什么,这里需要用到一个异或的性质

a \oplus a = 0

观察上式发现当区间长度为偶数时答案为 0,否则答案为 L ~ R 区间内下标与 L 奇偶相同的异或和

那么用线段树维护区间内奇偶异或和即可

时间复杂度 O(T \cdot nlog_{2}^{n})

AC Code

复制代码
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define ls (root << 1)
    #define rs (root << 1 | 1)
    const int maxn = 1e6 + 10;
    struct Tree {
    int l, r;
    ll w[2];
    } tree[maxn];
    void pushup(int root) {
    tree[root].w[0] = (tree[ls].w[0] ^ tree[rs].w[0]);
    tree[root].w[1] = (tree[ls].w[1] ^ tree[rs].w[1]);
    }
    void build(int root, int l, int r) {
    tree[root] = { l, r, 0, 0 };
    if(l == r) {
        cin >> tree[root].w[l & 1];
        return;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(root);
    }
    void update(int root, int pos, int w) {
    if(tree[root].l == pos && tree[root].r == pos) {
        tree[root].w[pos & 1] = w;
        return;
    }
    int mid = (tree[root].l + tree[root].r) >> 1;
    if(pos <= mid) update(ls, pos, w);
    else update(rs, pos, w);
    pushup(root);
    }
    ll query(int root, int l, int r, int k) {
    if(l <= tree[root].l && tree[root].r <= r) return tree[root].w[k];
    int mid = (tree[root].l + tree[root].r) >> 1;
    if(r <= mid) return query(ls, l, r, k);
    else if(l > mid) return query(rs, l, r, k);
    else return (query(ls, l, mid, k) ^ query(rs, mid + 1, r, k));
    }
    int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    for(int cas = 1; cas <= T; ++cas) {
        int n, m;
        cin >> n >> m;
        build(1, 1, n);
        cout << "Case #" << cas << ": \n";
        while(m--) {
            int op, x, y;
            cin >> op >> x >> y;
            if(!op) update(1, x, y);
            else {
                if((y - x) & 1) cout << 0 << '\n';
                else cout << query(1, x, y, x & 1) << '\n';
            }
        }
    }
    return 0;
    }

G. Winner

Description

n 个人进行比赛,一共比三轮,每轮每个人有一个能力值,能力值大的人可以淘汰能力小的人,m 次询问,每次问你第 x 个人在自己安排比赛次序的情况下是否有可能胜出

Tutorial

每轮都对所有人按能力值排序,将能力大的人向能力小的人连一条边,然后用 Tarjan 算法进行缩点,入度为 0 的强联通分量中的人可以获胜

注意入度为 0 的强联通分量一定包含三种模式中能力值最大的三个人,因为在缩点前他们的入度为 0

时间复杂度 O(n + m)

AC Code

复制代码
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 2e6 + 10;
    pair<int, int> p[4][maxn];
    struct Edge {
    int to, next;
    } edge[maxn];
    int head[maxn], tot;
    int Low[maxn], DFN[maxn], Stack[maxn];
    int Index, top;
    int scc;
    bool Instack[maxn];
    int a, b, c;
    vector<int> vv, vvv[maxn];
    bool succ[maxn];
    void addedge(int u, int v) {
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
    }
    void Tarjan(int u) {
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next) {
        v = edge[i].to;
        if(!DFN[v]) {
            Tarjan(v);
            if(Low[u] > Low[v]) Low[u] = Low[v];
        }
        else if(Instack[v] && Low[u] > DFN[v]) Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u]) {
        scc++;
        do {
            v = Stack[--top];
            if(v == a || v == b || v == c) vv.push_back(scc);
            Instack[v] = false;
            vvv[scc].push_back(v);
        } while(v != u);
    }
    }
    int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= 3; ++i) {
        for(int j = 1; j <= n; ++j) {
            int x;
            cin >> x;
            p[i][j] = { x, j };
        }
        sort(p[i] + 1, p[i] + n + 1);
        if(!a) a = p[i][n].second;
        else if(!b) b = p[i][n].second;
        else c = p[i][n].second;
    }
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= 3; ++i) {
        for(int j = n; j >= 2; --j) {
            addedge(p[i][j].second, p[i][j - 1].second);
        }
    }
    for(int i = 1; i <= n; ++i) {
        if(!DFN[i]) Tarjan(i);
    }
    for(int i = 0; i < vv.size(); ++i) {
        for(int j = 0; j < vvv[vv[i]].size(); ++j) {
            succ[vvv[vv[i]][j]] = true;
        }
    }
    while(m--) {
        int x;
        cin >> x;
        cout << (succ[x] ? "YES" : "NO") << '\n';
    }
    return 0;
    }

J. Prefix

Description

定义字母 ch 的难度值为 d_{ch},字符串 str 的难度 D(str)\prod_{i=1}^{|str|}d_{str_i} \bmod m,给定 n 个字符串,问这些字符串的前缀构成集合 P,问你对于第 i 个 字符串 sP 中有多少个前缀 p 满足:

  1. ps 的前缀
  2. D(p) > D(s)

Tutorial

map 记录一下每个前缀在 P 中出现的次数,对于每一个 s 的前缀判断其难度是否大于 s 即可

时间复杂度 O(\sum|str| \cdot log_{2}^{\sum|str|})

AC Code

复制代码
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 2e5 + 10;
    int a[26];
    ll b[maxn];
    string s[maxn];
    int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n;
    ll m;
    cin >> n >> m;
    for(int i = 0; i < 26; ++i) {
        cin >> a[i];
        a[i] %= m;
    }
    map<string, int> mp;
    for(int i = 1; i <= n; ++i) {
        cin >> s[i];
        int len = s[i].length();
        string p = "";
        b[i] = 1;
        for(int j = 0; j < len; ++j) {
            b[i] = b[i] * a[s[i][j] - 'a'] % m;
            p += s[i][j];
            ++mp[p];
        }
    }
    for(int i = 1; i <= n; ++i) {
        int res = 0, len = s[i].length();
        string p = "";
        ll tmp = 1;
        for(int j = 0; j < len; ++j) {
            p += s[i][j];
            tmp = tmp * a[s[i][j] - 'a'] % m;
            if(tmp > b[i]) res += mp[p];
        }
        cout << res << ' ';
    }
    return 0;
    }

K. A Good Game

Description

给定长度为 n 的数列 am 个区间 [L,R],让你将这些区间排序,使得 sum=\sum_{i=1}^{m}i \sum_{j=L_{i}}^{R_{i}}a_{j} 最大

Tutorial

求出前缀和后用差分算出区间和,排序后求和即可

时间复杂度 O(n)

AC Code

复制代码
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5 + 10;
    ll sum[maxn];
    ll a[maxn];
    int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while(T--) {
        int n, m;
        cin >> n >> m;
        for(int i = 1; i <= n; ++i) {
            ll x;
            cin >> x;
            sum[i] = sum[i - 1] + x;
        }
        for(int i = 1; i <= m; ++i) {
            int l, r;
            cin >> l >> r;
            a[i] = sum[r] - sum[l - 1];
        }
        sort(a + 1, a + m + 1);
        ll res = 0;
        for(int i = 1; i <= m; ++i) {
            res += a[i] * i;
        }
        cout << res << '\n';
    }
    return 0;
    }

全部评论 (0)

还没有任何评论哟~