zoukankan      html  css  js  c++  java
  • 2019牛客暑期多校训练营(第八场)

    2019牛客暑期多校训练营(第八场)

    传送门

    A.All-one Matrices

    枚举每一行作为极大矩阵的底部,然后枚举列根据(up[i][j])来确定矩阵高度,通过单调栈找到其左右最远扩展位置,之后通过预处理出行(1)个数的前缀和,判断一下下一行对应位置是否全为(1)即可。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 3005;
    int n, m;
    int a[N][N], up[N][N];
    char s[N][N];
    int L[N], R[N];
    int sta[N], sum[N][N];
    set <pair<int, int> > S;
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> m;
        for(int i = 1; i <= n; i++) {
            cin >> s[i] + 1;
            for(int j = 1; j <= m; j++) {
                a[i][j] = s[i][j] - '0';
                sum[i][j] = (a[i][j] == 1 ? sum[i][j - 1] + 1 : 0);
            }
        }
        ll ans = 0;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                up[i][j] = (a[i][j] == 1 ? up[i - 1][j] + 1 : 0);
            }
            up[i][m + 1] = up[i][0] = -1;
            int top = 0;
            for(int j = 1; j <= m + 1; j++) {
                if(top == 0) sta[++top] = j;
                else {
                    while(top && up[i][sta[top]] > up[i][j]) {
                        R[sta[top]] = j; top--;
                    }
                    sta[++top] = j;
                }
            }
            top = 0;
            for(int j = m; j >= 0; j--) {
                if(top == 0) sta[++top] = j;
                else {
                    while(top && up[i][sta[top]] > up[i][j]) {
                        L[sta[top]] = j; top--;
                    }
                    sta[++top] = j;
                }
            }
            S.clear();
            for(int j = 1; j <= m; j++) {
                int l = L[j] + 1, r = R[j] - 1;
                if(l > r || a[i][j] == 0) continue;
                if(sum[i + 1][r] - sum[i + 1][l - 1] != r - l + 1) {
                    S.insert(make_pair(l, r));
                }
            }
            ans += (int)S.size();
        }
        cout << ans;
        return 0;
    }
    /*
    
    */
    

    B.Beauty Values

    对于每一个右端点(r),在其之前的每个数的贡献即为其最后一次出现位置到起点的长度,维护一下即可。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 5;
    int n;
    int a[N], last[N];
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i];
        ll ans = 0, sum = 0;
        for(int i = 1; i <= n; i++) {
            sum -= last[a[i]];
            last[a[i]] = i;
            sum += last[a[i]];
            ans += sum;
        }
        cout << ans;
        return 0;
    }
    
    

    C.CDMA

    样例具有很强的暗示性,根据样例来构造答案即可。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 2048;
    int res[N][N];
    int n;
    void make(int x, int y, int z) {
        if(z ==1) {
            res[x][y] = 1;
            return;
        }
        make(x, y, z / 2);
        make(x + z / 2, y, z / 2);
        make(x, y + z / 2, z / 2);
        make(x + z / 2, y + z / 2, z / 2);
        for(int i = x + z / 2; i <= x + z - 1; i++) {
            for(int j = y + z / 2; j <= y + z - 1; j++) {
                res[i][j] = -res[i][j];
            }
        }
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n;
        make(1, 1, n);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                cout << res[i][j] << " 
    "[j == n];
        return 0;
    }
    
    

    D.Distance

    直接三维树状数组来搞:对于距离公式:(|x_1-x_2|+|y_1-y_2|+|z_1+z_2|),我们将其打开,只有下面两种情况(以(x)举例):

    • (x_1-x_2,x_1leq x_2)
    • (-x_1+x_2,x_1geq x_2)

    因为树状数组维护的是前缀最大值,而第二种情况中限制为(x_1geq x_2),所以我们更新时就直接插入(max-x_1),对于(x_2)的询问,也直接询问(max-x_1)就行。
    可以用一维数组模拟三维,代码如下:

    Code
    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int N = 3e5 + 5;
     
    void gmax(int &x, int y) {
        if(x < y) x = y;
    }
    void gmin(int &x, int y) {
        if(x > y) x = y;
    }
     
    struct BIT{
        int a[N];
        int n, m, h;
        int lowbit(int x) {return x & -x; }
        int id(int i, int j, int k) {
            return i * m * h + j * h + k;
        }
        void init(int _n, int _m, int _h) {
            n = _n, m = _m, h = _h;
            for(int i = 1; i < N; i++) a[i] = -INF;
        }
        void update(int x, int y, int z, int val) {
            for(int i = x; i <= n; i += lowbit(i))
                for(int j = y; j <= m; j += lowbit(j))
                    for(int k = z; k <= h; k += lowbit(k))
                        gmax(a[id(i, j, k)], val);
        }
        int query(int x, int y, int z) {
            int ans = -INF;
            for(int i = x; i; i -= lowbit(i))
                for(int j = y; j; j -= lowbit(j))
                    for(int k = z; k; k -= lowbit(k))
                        gmax(ans, a[id(i, j, k)]);
            return ans;
        }
    }bit[8];
     
    int n, m, h, q;
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> m >> h >> q;
        for(int i = 0; i < 8; i++) bit[i].init(n, m, h);
        while(q--) {
            int op, x, y, z;
            cin >> op >> x >> y >> z;
            if(op == 1) {
                bit[0].update(x, y, z, x + y + z);
                bit[1].update(x, y, h - z + 1, x + y - z);
                bit[2].update(x, m - y + 1, z, x - y + z);
                bit[3].update(x, m - y + 1, h - z + 1, x - y - z);
                bit[4].update(n - x + 1, y, z, -x + y + z);
                bit[5].update(n - x + 1, m - y + 1, z, -x - y + z);
                bit[6].update(n - x + 1, y, h - z + 1, -x + y - z);
                bit[7].update(n - x + 1, m - y + 1, h - z + 1, -x - y - z);
            } else {
                int ans = INF;
                gmin(ans, x + y + z - bit[0].query(x, y, z));
                gmin(ans, x + y - z - bit[1].query(x, y, h - z + 1));
                gmin(ans, x - y + z - bit[2].query(x, m - y + 1, z));
                gmin(ans, x - y - z - bit[3].query(x, m - y + 1, h - z + 1));
                gmin(ans, -x + y + z - bit[4].query(n - x + 1, y, z));
                gmin(ans, -x - y + z - bit[5].query(n - x + 1, m - y + 1, z));
                gmin(ans, -x + y - z - bit[6].query(n - x + 1, y, h - z + 1));
                gmin(ans, -x - y - z - bit[7].query(n - x + 1, m - y + 1, h - z + 1));
                cout << ans << '
    ';
            }
        }
        return 0;
    }
    

    还有一种解法就是定期重构。
    设定一个阀值(E),对于插入点的操作,我们先把所有点装入一个桶中,当点的数量超过了这个阀值,就将点全取出来跑次(bfs)找最短路。
    对于询问就在(dis)和桶中点取(min)即可。
    (E)(sqrt{nmq})时总时间复杂度最小。

    Code
    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int N = 3e5 + 5, E = 666;
    int n, m, h, q;
    int id(int x, int y, int z) {
        return x * m * h + y * h + z;
    }
    struct node{
        int x, y, z;
    };
    vector <node> c;
    int dis[N];
    int dx[6] = {-1, 1, 0, 0, 0, 0};
    int dy[6] = {0, 0, 1, -1, 0, 0};
    int dz[6] = {0, 0, 0, 0, 1, -1};
    bool in(int x, int y, int z) {
        return x <= n && x && y <= m && y && z <= h && z;
    }
    void rebuild() {
        queue <node> q;
        for(auto it : c) {
            q.push({it.x, it.y, it.z});
            dis[id(it.x, it.y, it.z)] = 0;
        }
        while(!q.empty()) {
            node u = q.front(); q.pop();
            for(int i = 0; i < 6; i++) {
                int curx = u.x + dx[i];
                int cury = u.y + dy[i];
                int curz = u.z + dz[i];
                if(in(curx, cury, curz) && dis[id(curx, cury, curz)] > dis[id(u.x, u.y, u.z)] + 1) {
                    dis[id(curx, cury, curz)] = dis[id(u.x, u.y, u.z)] + 1;
                    q.push({curx, cury, curz});
                }
            }
        }
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        memset(dis, INF, sizeof(dis));
        cin >> n >> m >> h >> q;
        while(q--) {
            int op, x, y, z;
            cin >> op >> x >> y >> z;
            if(op == 1) {
                c.push_back({x, y, z});
                if(c.size() >= E) {
                    rebuild();
                    c.clear();
                }
            } else {
                int ans = dis[id(x, y, z)];
                for(auto it : c) {
                    ans = min(ans, abs(x - it.x) + abs(y - it.y) + abs(z - it.z));
                }
                cout << ans << '
    ';
            }
        }
        return 0;
    }
    

    E.Explorer

    考虑最暴力的做法,枚举(size)然后拿出所有能通过的边来判断连通性。
    但题目的数据范围很大,显然这样不行。
    观察发现如果把所有的能通行区间([l,r])拿出来,放在一维线段上,每一个小区间都具有相同的性质,也就是说它们要么都行,要么都不行。
    所以就考虑线段树维护区间信息。因为我们还需要判断连通性,对于每个线段树中的结点,储存一些边,意即结点子树中所有点都可以具有这些边,然后跑一遍(dfs),往下的时候合并判连通性,回溯时撤销相关操作即可。
    因为支持撤销操作,所以就不能路径压缩,就采用启发式合并。
    复杂度(O(nlog^2n))

    Code
    #include <bits/stdc++.h>
    #define MP make_pair
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> pii;
    const int N = 2e5 + 5;
    int n, m;
    struct node{
        int u, v, l, r;
    }a[N];
    int b[N], D;
    vector <int> c[N << 2];
    vector <pair<int, int> > d[100];
    ll ans;
    void insert(int o, int l, int r, int L, int R, int id) {
        if(L <= l && r <= R) {
            c[o].push_back(id);
            return;
        }
        int mid = (l + r) >> 1;
        if(L <= mid) insert(o << 1, l, mid, L, R, id);
        if(R > mid) insert(o << 1|1, mid + 1, r, L, R, id);
    }
    int f[N], sz[N];
    int find(int x) {
        return f[x] == x ? x : find(f[x]);
    }
    void merge(int x, int y, int dep) {
        int fx = find(x), fy = find(y);
        if(fx == fy) return;
        if(sz[fx] > sz[fy]) swap(fx, fy);
        int tmp = 0;
        f[fx] = fy;
        if(sz[fx] == sz[fy]) tmp++;
        sz[fy] += tmp;
        d[dep].push_back(MP(fx, tmp));
    }
    void del(int dep) {
        for(auto it : d[dep]) {
            sz[f[it.first]] -= it.second;
            f[it.first] = it.first;
        }
        d[dep].clear();
    }
    void dfs(int o, int l, int r, int dep) {
        for(auto it : c[o]) {
            merge(a[it].u, a[it].v, dep);
        }
        if(find(1) == find(n)) {
            ans += b[r + 1] - b[l];
        } else if(l < r) {
            int mid = (l + r) >> 1;
            dfs(o << 1, l, mid, dep + 1);
            dfs(o << 1|1, mid + 1, r, dep + 1);
        }
        del(dep);
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> m;
        for(int i = 1; i <= m; i++) {
            cin >> a[i].u >> a[i].v >> a[i].l >> a[i].r;
            b[++D] = a[i].l, b[++D] = a[i].r + 1;
        }
        sort(b + 1, b + D + 1);
        D = unique(b + 1, b + D + 1) - b - 1;
        for(int i = 1; i <= m; i++) {
            a[i].l = lower_bound(b + 1, b + D + 1, a[i].l) - b;
            a[i].r = lower_bound(b + 1, b + D + 1, a[i].r + 1) - b - 1;
            insert(1, 1, D, a[i].l, a[i].r, i);
        }
        for(int i = 1; i <= n; i++) f[i] = i, sz[i] = 1;
        dfs(1, 1, D, 1);
        cout << ans;
        return 0;
    }
    
    

    G.Gemstones

    签到题,用数组模拟栈即可。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 5;
    int n, m;
    char s[N];
    int sta[N];
    bool check(int p) {
        if(p < 3) return false;
        if(sta[p] == sta[p - 1] && sta[p - 1] == sta[p - 2]) return true;
        return false;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> s + 1;
        n = strlen(s + 1);
        int top = 0, ans = 0;
        for(int i = 1; i <= n; i++) {
            if(top == 0) {
                sta[++top] = s[i] - 'A';
            } else {
                sta[++top] = s[i] - 'A';
                if(check(top)) {
                    top -= 3;
                    ans++;
                }
            }
        }
        cout << ans;
        return 0;
    }
    
    

    I.Inner World

    因为题目中给出的所有(u,v)都不相同,那么每个结点的父亲都是确定的,只是存在区间不同。
    所以考虑直接将图建在一棵树上,每个结点有个区间信息,表示这个结点出现在哪些区间中。
    现在要求的其实就是子树中所有区间与([l,r])的交集。
    这里有两种做法,但都是将问题转化为了"区间的区间和"。
    考虑求出(dfs)序,那么子树中的(dfs)序是连续的,维护子树中的信息就可以用主席树来搞,然后主席树上面维护区间和的信息就行了。
    感觉这种写法还是挺有意思的,(insert)操作时因为要求数据更新的正确性,所以对应区间范围会跟着变化;然后维护一个懒标记,懒标记下面的结点就不用建出来了,反正全都要加上(1)。查询时记得加上懒标记的贡献。

    Code
    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define MP make_pair
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> pii;
    const int N = 1000005;
    int n, m, q;
    pii a[N];
    vector <int> g[N];
    int in[N], out[N], mp[N], T;
    void dfs(int u) {
        in[u] = ++T;
        mp[T] = u;
        for(auto v : g[u]) dfs(v);
        out[u] = T;
    }
    int rt[N], ls[N * 20], rs[N * 20], lz[N * 20];
    ll sumv[N * 20];
    int tot;
    void build(int &o, int l, int r) {
        o = ++tot;
        if(l == r) return;
        int mid = (l + r) >> 1;
        build(ls[o], l, mid);
        build(rs[o], mid + 1, r);
    }
    void insert(int &o, int last, int l, int r, int L, int R) {
        lz[o = ++tot] = lz[last];
        ls[o] = ls[last]; rs[o] = rs[last];
        sumv[o] = sumv[last] + R - L + 1;
        if(L <= l && r <= R) {
            lz[o]++;
            return;
        }
        int mid = (l + r) >> 1;
        if(L <= mid) insert(ls[o], ls[last], l, mid, L, min(R, mid));
        if(R > mid) insert(rs[o], rs[last], mid + 1, r, max(mid + 1, L), R);
    }
    ll query(int &o, int last, int l, int r, int L, int R, ll add) {
        if(L <= l && r <= R) return sumv[o] - sumv[last] + (r - l + 1) * add;
        add += lz[o] - lz[last];
        int mid = (l + r) >> 1;
        ll res = 0;
        if(L <= mid) res += query(ls[o], ls[last], l, mid, L, R, add);
        if(R > mid) res += query(rs[o], rs[last], mid + 1, r, L, R, add);
        return res;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> m;
        for(int i = 1; i <= m; i++) {
            int u, v, l, r;
            cin >> u >> v >> l >> r;
            a[v] = MP(l, r);
            g[u].push_back(v);
        }
        a[1] = MP(1, n);
        dfs(1);
        build(rt[0], 1, n);
        for(int i = 1; i <= T; i++) {
            int now = mp[i];
            insert(rt[i], rt[i - 1], 1, n, a[now].fi, a[now].se);
        }
        cin >> q;
        while(q--) {
            int x, l, r; cin >> x >> l >> r;
            ll ans = query(rt[out[x]], rt[in[x] - 1], 1, n, l, r, 0);
            cout << ans << '
    ';
        }
        return 0;
    }
    

    另外还可以直接维护子树增量,进入子树前答案先减去目前([l,r])的和,从子树出来后再把答案加上目前([l,r])的和,这样就可以维护子树中区间([l,r])的增量了。

    Code
    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define MP make_pair
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> pii;
    const int N = 300005;
    int n, m, q;
    pii a[N];
    vector <int> g[N];
    struct Query{
        int op, l, r, id;
    };
    int in[N], out[N], mp[N], T;
    ll res[N];
    vector <Query> vec[N];
    void dfs(int u) {
        in[u] = ++T;
        mp[T] = u;
        for(auto v : g[u]) dfs(v);
        out[u] = T;
    }
    ll sumv[N << 2];
    int lz[N << 2];
    void push_down(int o, int l, int r) {
        if(lz[o]) {
            int mid = (l + r) >> 1;
            lz[o << 1] += lz[o];
            lz[o << 1|1] += lz[o];
            sumv[o << 1] += 1ll * lz[o] * (mid - l + 1);
            sumv[o << 1|1] += 1ll * lz[o] * (r - mid);
            lz[o] = 0;
        }
    }
    void push_up(int o) {
        sumv[o] = sumv[o << 1] + sumv[o << 1|1];
    }
    void update(int o, int l, int r, int L, int R) {
        if(L <= l && r <= R) {
            sumv[o] += r - l + 1;
            lz[o]++; return;
        }
        push_down(o, l, r);
        int mid = (l + r) >> 1;
        if(L <= mid) update(o << 1, l, mid, L, R);
        if(R > mid) update(o << 1|1, mid + 1, r, L, R);
        push_up(o);
    }
    ll query(int o, int l, int r, int L, int R) {
        if(L <= l && r <= R) return sumv[o];
        push_down(o, l, r);
        int mid = (l + r) >> 1;
        ll res = 0;
        if(L <= mid) res += query(o << 1, l, mid, L, R);
        if(R > mid) res += query(o << 1|1, mid + 1, r, L, R);
        return res;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> m;
        for(int i = 1; i <= m; i++) {
            int u, v, l, r;
            cin >> u >> v >> l >> r;
            a[v] = MP(l, r);
            g[u].push_back(v);
        }
        a[1] = MP(1, n);
        dfs(1);
        cin >> q;
        for(int i = 1; i <= q; i++) {
            int x, l, r; cin >> x >> l >> r;
            vec[in[x] - 1].push_back(Query{-1, l, r, i});
            vec[out[x]].push_back(Query{1, l, r, i});
        }
        for(int i = 1; i <= T; i++) {
            update(1, 1, n, a[mp[i]].fi, a[mp[i]].se);
            for(auto it : vec[i]) {
                res[it.id] += it.op * query(1, 1, n, it.l, it.r);
            }
        }
        for(int i = 1; i <= q; i++) cout << res[i] << '
    ';
        return 0;
    }
    

    J.Just Jump

    首先求出(dp[i]),表示跳跃了(i)的距离共有多少种方法。
    然后考虑减去某些不合法的情况。
    这里可以容斥来搞,但还有一种更加简单的办法,就是对于每个(p_i),单独求出其对答案的贡献,然后减去即可。
    详见代码吧:

    Code
    #include <bits/stdc++.h>
    #define fi first
    #define se second
    using namespace std;
    typedef pair<int, int> pii;
    typedef long long ll;
    const int N = 1e7 + 5, M = 3005, MOD = 998244353;
    int l, d, m;
    int add(int x, int y) {
        x = (x + y) % MOD;
        if(x < 0) x += MOD;
        return x;
    }
    int mul(ll x, int y) {
        x = x * y % MOD;
        if(x < 0) x += MOD;
        return x;
    }
    int dp[N], sum[N];
    int fac[N], inv[N];
    int qp(int a, int b) {
        int ans = 1;
        while(b) {
            if(b & 1) ans = mul(ans, a);
            a = mul(a, a);
            b >>= 1;
        }
        return ans;
    }
    void pre() {
        dp[0] = sum[0] = 1;
        for(int i = 1; i <= l; i++) {
            if(i - d >= 0) dp[i] = sum[i - d];
            sum[i] = add(sum[i - 1], dp[i]);
        }
        fac[0] = 1;
        for(int i = 1; i <= l; i++) fac[i] = mul(fac[i - 1], i);
        inv[l] = qp(fac[l], MOD - 2);
        for(int i = l - 1; i >= 0; i--) inv[i] = mul(inv[i + 1], i + 1);
    }
    pii a[M];
    int f[M];
    int C(ll n, int m) {
        if(n < 0 || m < 0 || n < m) return 0;
        return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
    }
    int h(int l, int r, int k) {
        return C(r - l - 1ll * (d - 1) * k - 1, k - 1);
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> l >> d >> m;
        pre();
        for(int i = 1; i <= m; i++) cin >> a[i].fi >> a[i].se;
        sort(a + 1, a + m + 1);
        m = unique(a + 1, a + m + 1) - a - 1;
        int ans = dp[l];
        for(int i = 1; i <= m; i++) {
            f[i] = h(0, a[i].se, a[i].fi);
            for(int j = 1; j < i; j++) f[i] = add(f[i], -mul(f[j], h(a[j].se, a[i].se, a[i].fi - a[j].fi)));
            ans = add(ans, -mul(f[i], dp[l - a[i].se]));
        }
        cout << ans;
        return 0;
    }
    
    
  • 相关阅读:
    MapReduce程序遇见java.net.UnknownHostException
    吐槽下《Hadoop权威指南(第二版)》的翻译
    HFileOutputFormat与TotalOrderPartitioner
    关于hive multi group by的疑惑
    Hive解决 java.io.IOException:SerDeException:LazySimpleSerDe
    一个字符编码引发的血案
    CSS颜色代码大全
    C#中ParameterizedThreadStart和ThreadStart区别
    Sql Server REPLACE函数的使用
    QueryString的用法
  • 原文地址:https://www.cnblogs.com/heyuhhh/p/11348361.html
Copyright © 2011-2022 走看看