• 训练赛


    A B C D E F G H I J K L
    1 0 2 1 0 2 2 0 2 0 0 1

    0:未完成

    1:赛时做出

    2:赛后补

    总结:

    3题铁牌。比赛时负责C和G,想法的方向是正确的,但是想的过于浮于表面,没有深入地去优化。签到题常规题基本都是 复杂度高做法+使劲优化 = 正解。想好写的做法,多寻找问题所拥有的特别性质,观察数据范围有助于发现突破口。

    A - Accelerator(分治fft)

    即求类似(a_1a_2a_3 + a_2a_3+a_3)这样的式子。关键是求每一项的总和。就是每一项取与不取的,即

    [prod_{i=1} ^{n}{(1+a_ix)} ]

    然后多项式每一项的系数就是需要的值。还需要乘上每一项对应的序列前面的组合个数。

    有点卡时间,可以预处理出原根的次幂,减少常数。

    #include <bits/stdc++.h>
    typedef long long ll;
    #define endl '
    '
    using namespace std;
    const int N = 3e5 + 10;
    const int M = 998244353;
    
    inline ll qpow(ll a ,ll b, ll m) {
        ll res = 1;
        while(b) {
            if(b & 1) {
                res = (res * a) % m;
            }
            a = (a * a) % m;
            b >>= 1;
        }
        return res;
    }
    
    int rev[N];
    void change(vector<int> &y, int len) {
        for(int i = 0; i < len; i++) {
            rev[i] = rev[i >> 1] >> 1; 
            if(i & 1) {
                rev[i] |= len >> 1;
            }
        }
        for(int i = 0; i < len; i++) {
            if(i < rev[i]) {
                swap(y[i], y[rev[i]]);
            }
        }
        return ;
    }
    int ngn[N];
    int rgn[N];
    int inv[N];
    
    void ntt(vector<int> &y, int len, int on) {
        change(y, len);
        for(int h = 2; h <= len; h <<= 1) {
            int gn = ngn[h];
            if(on == -1) {
                gn = rgn[h];
            }
            for(int j = 0; j < len; j += h) {
                ll g = 1;
                for(int k = j; k < j + h / 2; k++) {
                    int u = y[k];
                    int t = g * y[k + h / 2] % M;
                    y[k] = (u + t) % M;
                    y[k + h / 2] = (u - t + M) % M;
                    g = g * gn % M;
                }
            }
        }
        if(on == -1) {
            int iv = inv[len];
            for(int i = 0; i < len; i++) {
                y[i] = 1ll * y[i] * iv % M;
            }
        }
    }
    
    int get(int x) {
        int res = 1;
        while(res < x) {
            res <<= 1;
        }
        return res;
    }
    
    int arr[N];
    int fact[N];
    
    vector<int> solve(int l, int r) {
        if(l == r) {
            vector<int> res(2);
            res[0] = 1;
            res[1] = arr[l];
            return res;
        }
        int mid = (l + r) / 2;
        vector<int> f = solve(l, mid);
        vector<int> g = solve(mid + 1, r);
        int tdeg = f.size() + g.size() - 2;
        int len = get(tdeg + 1);
        f.resize(len, 0);
        g.resize(len, 0);
        ntt(f, len, 1);
        ntt(g, len, 1);
        for(int i = 0; i < len; i++) {
            f[i] = 1ll * f[i] * g[i] % M;
        }
        ntt(f, len, -1);
        f.resize(tdeg + 1);
        return move(f);
    }
    
    int main() {
        fact[0] = 1;
        for(int i = 1; i < N; i++) {
            fact[i] = 1ll * fact[i - 1] * i % M;
            inv[i] = qpow(i, M - 2 ,M);
        }
        for(int i = 1; i < N; i <<= 1) {
            ngn[i] = qpow(3, (M - 1) / i, M);
            rgn[i] = qpow(ngn[i], M - 2, M);
        }
        int t;
        scanf("%d", &t);
        while(t--) {
            int n;
            scanf("%d", &n);
            for(int i = 1; i <= n; i++) scanf("%d", &arr[i]);
            vector<int> res = solve(1, n);
            ll ans = 0;
            for(int i = 1; i <= n; i++) {
                ans += 1ll * fact[i] * fact[n - i] % M * res[i] % M;
            }
            ans %= M;
            printf("%d
    ", ans * qpow(fact[n] , M - 2, M) % M);
        }
    }
    

    C - Club Assignment(分治,暴力)

    将所有数排序,然后枚举最高位,每次可以分为最高位为0/1两部分。可以发现,横跨两组的数之间异或起来一定比组内的之间异或的大,即横跨两组的数之间异或对答案没有贡献(因为不会是最小值),即两组之间没有分配的限制,不用管。于是就可以继续分成两部分处理。

    一直分下去,如果都是相同的数,相同的数大于2,说明答案为0;否则将它们分到不同的集合。否则最后一定会分成小于等于4的集合。对于每个小于等于4的集合,直接暴力枚举每一种分配的组合,从中选择结果最大的分配。最后答案就是所有这些结果的最小值。

    这个最优的分配方案和异或最小生成树也有关。

    #include <bits/stdc++.h>
    typedef long long ll;
    #define endl '
    '
    using namespace std;
    const int N = 3e5 + 10;
    const int M = 998244353;
    #define INF 0x3f3f3f3f3f3f3f3f
    typedef pair<int, int> PII;
    PII arr[N];
    int ans[N];
    
    ll solve(int l, int r, int cur) {
        if(r - l + 1 == 1) {
            ans[arr[l].second] = 1;
            return INF;
        }
        if(r - l + 1 <= 4) {
            if(r - l + 1 == 2) {
                ans[arr[l].second] = 1;
                ans[arr[r].second] = 2;
                return INF;
            }
            vector<PII> s1, s2;
            ll mx = 0;
            for(int i = l; i <= r; i++) {
                for(int j = i + 1; j <= r; j++) {
                    vector<PII> a, b;
                    a.push_back(arr[i]);
                    a.push_back(arr[j]);
                    for(int k = l; k <= r; k++) {
                        if(k == i || k == j) continue;
                        b.push_back(arr[k]);
                    }
                    ll va = INF, vb = INF;
                    va = a.front().first ^ a.back().first;
                    if(b.size() > 1) vb = b.front().first ^ b.back().first;
                    if(min(va, vb) >= mx) {
                        s1 = move(a);
                        s2 = move(b);
                        mx = min(va, vb);
                    }
                }
            }
            for(auto p : s1) ans[p.second] = 1;
            for(auto p : s2) ans[p.second] = 2;
            return mx;
        }
        if(cur < 0) {
            if(r - l + 1 >= 3) {
                for(int i = l; i <= r; i++) ans[arr[i].second] = 1;
                return 0;
            }
            if(r - l + 1 == 2) {
                ans[arr[l].second] = 1;
                ans[arr[r].second] = 2;
            } else {
                ans[arr[l].second] = 1;
            }
            return INF;
        }
        int p = l;
        while(p <= r) {
            if((arr[p].first & (1 << cur))) {
                break;
            }
            p++; 
        }
        ll mi = INF;
        if(p > l) mi = min(mi, solve(l, p - 1, cur - 1));
        if(p <= r) mi = min(mi, solve(p, r, cur - 1));
        return mi;
    }
    
    int main() {
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int t;
        cin >> t;
        while(t--) {
            int n;
            cin >> n;
            for(int i = 1; i <= n; i++) {
                int x;
                cin >> x;
                arr[i] = {x, i};
            }
            sort(arr + 1, arr + 1 + n);
            cout << solve(1, n, 30) << endl;
            for(int i = 1; i <= n; i++) {
                cout << ans[i];
            }
            cout << endl;
        }
    }
    

    G - Game on Sequence(暴力)

    (f(i)=1),代表棋子到位置(i)是必胜的,反之亦然。然后就有很简单的转移方程

    [f(i)=operatorname{NOT}{(operatorname{AND} f(j))} ]

    (j)代表(i)能转移到的位置。显然这样时间复杂度太高。观察发现,如果位置(i)的值为(A),在它之后也有一个位置(j)值为(A),那么(f(i)=1)。因为如果(f(j)=0),有(f(i)=1);如果(f(j)=1),说明(j)之后有个位置(k)(f(k)=0),那么就有(f(i)=1)。因此只需维护最后面的不同的(A)的对应位置的(f)值即可。(A)的值域只有255,直接暴力计算即可。时限为6s,非常充裕。

    #include <bits/stdc++.h>
    
    #define endl '
    '
    #define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
    #define mp make_pair
    #define seteps(N) fixed << setprecision(N) 
    typedef long long ll;
    
    using namespace std;
    /*-----------------------------------------------------------------*/
    
    ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
    #define INF 0x3f3f3f3f
    
    const int N = 5e5 + 10;
    const int M = 500;
    const double eps = 1e-5;
    
    int pos[500];
    int num[500];
    int npos[500];
    bitset<500> vis;
    bitset<500> flag;
    int arr[N];
    vector<int> np[500];
    int len;
    
    void add(int x, int p) {
        if(!npos[x]) {
            npos[x] = ++len;
            num[len] = x;
        } else {
            for(int i = npos[x]; i + 1 <= len; i++) {
                num[i] = num[i + 1];
                npos[num[i]] = i;
            }
            num[len] = x;
            npos[x] = len;
        }
        pos[x] = p;
        arr[p] = x;
    }
    
    bool que(int p) {
        if(p < pos[arr[p]]) return true;
        vis.reset();
        flag.reset();
        for(int i = len; i >= 1; i--) {
            int x = num[i];
            bool ok = 0;
            for(int nt : np[x]) {
                if(vis[nt]) {
                    ok |= (!flag[nt]);
                }
            }
            flag.set(x, ok);
            vis.set(x);
        }
        return flag[arr[p]];
    }
    
    
    
    bool chk(int a, int b) {
        int c = a ^ b;
        int cnt = 0;
        while(c) {
            if(c & 1) cnt++;
            c >>= 1;
        }
        return cnt <= 1;
    }
    
    int main() {
        IOS;
        for(int i = 0; i < 256; i++) {
            for(int j = 0; j < 256; j++) {
                if(chk(i, j)) np[i].push_back(j);
            }
        }
        int n, m;
        cin >> n >> m;
        for(int i = 1; i <= n; i++) {
            int x;
            cin >> x;
            add(x, i);
        }
        int cur = n + 1;
        while(m--) {
            int op;
            cin >> op;
            if(op == 1) {
                int x;
                cin >> x;
                add(x, cur++);
            } else {
                int p;
                cin >> p;
                if(que(p)) cout << "Grammy" << endl;
                else cout << "Alice" << endl;
            }
        }
    }
    

    I - Nim Cheater(轻重链性质)

    Bob必胜就是石子数异或和为0,那么用简单的背包dp,就可以得到答案。

    每次都在序列尾部加入和删除一个数的一系列操作,可以构成一颗树。加入操作等价于在当前结点下插入一个结点;删除操作相当于返回父亲结点。

    因此构造出这个树,然后直接在树上跑dp即可。空间复杂度为(O(nm)),其中(n)代表操作数(结点数),(m)代表石头数值域。

    显然这样空间会超,因此题解提供一个优秀的解法:找到这个树轻重儿子,因为每个结点最多只有一个重儿子,每条路径最多包含(log n)个轻儿子,因此可以先遍历轻儿子,再遍历重儿子;每次到轻儿子存下当前dp状态,等返回时再还原;重儿子则直接更新dp状态。这样空间复杂度就只有(O(mlog n))

    #include <bits/stdc++.h>
    
    #define endl '
    '
    #define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
    #define mp make_pair
    #define seteps(N) fixed << setprecision(N) 
    typedef long long ll;
    
    using namespace std;
    /*-----------------------------------------------------------------*/
    
    ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
    #define INF 0x3f3f3f3f
    
    const int N = 2e4 + 10;
    const int M = 16385;
    const double eps = 1e-5;
    
    int ans[N];
    int fa[N];
    int val[N], cost[N];
    vector<int> np[N];
    int si;
    bool hson[N];
    
    int dfs(int p) {
        int cnt = 1;
        int mx = 0, tar = -1;
        for(int nt : np[p]) {
            int res = dfs(nt);
            if(res > mx) {
                mx = res;
                tar = nt;
            }
            cnt += res;
        }
        if(tar >= 0) hson[tar] = 1;
        return cnt;
    }
    
    int res[M];
    void solve(int p, int tot) {
        int *bk;
        if(!hson[p]) {
            bk = new int[M];
            for(int i = 0; i < M; i++) bk[i] = res[i];
        }
        for(int i = 0; i < M; i++) {
            res[i ^ val[p]] = min(res[i ^ val[p]], res[i] + cost[p]);
        }
        ans[p] = res[tot ^ val[p]];
        int tar = -1;
        for(int nt : np[p]) {
            if(hson[nt]) {
                tar = nt;
                continue;
            }
            solve(nt, tot ^ val[p]);
        }
        if(tar >= 0) solve(tar, tot ^ val[p]);
        if(!hson[p]) {
            for(int i = 0; i < M; i++) res[i] = bk[i];
            delete [] bk;
        }
    }
    
    int num;
    void printans(int p) {
        if(!num) return ;
        if(p) {
            cout << ans[p] << endl;
            num--;
        }
        for(int nt : np[p]) {
            if(!num) return ;
            printans(nt);
            if(!num) return ;
            cout << ans[p] << endl;
            num--;
        }
    }
    
    int main() {
        IOS;
        int n;
        cin >> n;
        int cur = 0;
        for(int i = 1; i <= n; i++) {
            string op;
            cin >> op;
            if(op == "ADD") {
                si++;
                cin >> val[si] >> cost[si];
                np[cur].push_back(si);
                fa[si] = cur;
                cur = si;
            } else {
                cur = fa[cur];
            }
        }
        memset(res, INF, sizeof res);
        res[0] = 0;
        dfs(0);
        solve(0, 0);
        num = n;
        printans(0);
    }
    
  • 相关阅读:
    【C++基础汇总】参数传递
    常用VC快捷键
    美股交易规则
    xpath 总结1
    【字符集】字符集和编码知识【转】
    【字符集】ASCII 表
    【win32编程学习】常用技巧总结
    【win32编程学习】 调用dll
    【win32编程学习】 创建自己的dll
    充实的生活
  • 原文地址:https://www.cnblogs.com/limil/p/15412456.html
Copyright © 2020-2023  润新知