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] << " \n"[j == n];
    return 0;
}

D.Distance

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

  • \(x_1-x_2,x_1\leq x_2\)
  • \(-x_1+x_2,x_1\geq x_2\)

因为树状数组维护的是前缀最大值,而第二种情况中限制为\(x_1\geq 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 << '\n';
        }
    }
    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 << '\n';
        }
    }
    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 << '\n';
    }
    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] << '\n';
    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;
}

posted @ 2019-08-13 20:32  heyuhhh  阅读(347)  评论(0编辑  收藏  举报