P5270 无论怎样神树大人都会删库跑路

题目地址:P5270 无论怎样神树大人都会删库跑路

第一眼看上去是模拟,似乎是 \(O(n)\) 的

水题

信心满满的写完:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 6;
int n, T, Q, m, c[N], r[N], mx, cnt, d[N], ans, s[N], now;
vector<int> e[N];
queue<int> q;

int main() {
    cin >> n >> T >> Q;
    for (int i = 1; i <= T; i++) {
        int x;
        scanf("%d", &x);
        mx = max(mx, x);
        ++c[x];
    }
    for (int i = 1; i <= n; i++) {
        int len;
        scanf("%d", &len);
        while (len--) {
            int x;
            scanf("%d", &x);
            mx = max(mx, x);
            e[i].push_back(x);
        }
    }
    cin >> m;
    for (int i = 1; i <= m; i++) scanf("%d", &r[i]);
    for (int x = 0; x <= mx; x++) if (c[x] == d[x]) ++cnt;
    for (int i = 1; i <= m && i <= Q; i++) {
        for (unsigned int j = 0; j < e[r[i]].size(); j++) {
            int x = e[r[i]][j];
            q.push(x);
            if (d[x] == c[x]) --cnt;
            ++d[x];
            if (d[x] == c[x]) ++cnt;
        }
        while ((int)q.size() > T) {
            int x = q.front();
            q.pop();
            if (d[x] == c[x]) --cnt;
            --d[x];
            if (d[x] == c[x]) ++cnt;
        }
        if (cnt == mx + 1) ++ans;
    }
    if (Q <= m) {
        cout << ans << endl;
        return 0;
    }
    Q -= m;
    for (int i = 1; i <= m; i++) {
        for (unsigned int j = 0; j < e[r[i]].size(); j++) {
            int x = e[r[i]][j];
            q.push(x);
            if (d[x] == c[x]) --cnt;
            ++d[x];
            if (d[x] == c[x]) ++cnt;
        }
        while ((int)q.size() > T) {
            int x = q.front();
            q.pop();
            if (d[x] == c[x]) --cnt;
            --d[x];
            if (d[x] == c[x]) ++cnt;
        }
        if (cnt == mx + 1) ++now;
        s[i] = now;
    }
    cout << ans + now * (Q / m) + s[Q%m] << endl;
    return 0;
}

Wait!

Q:为什么在模拟完第一遍后要再模拟一遍呢?

A:第一遍初始时没有数,而后面会有剩下的数留到下一轮,因此第一遍对 \(ans\) 的贡献要特判QAQ

一遍AC

然而......

模拟是 \(O(n)\) 的?

抱歉,假了,50分滚粗

康康这组数据:

1 1e5 1e9
1 1 ... 1
1e5 1 1 ... 1
1e5
1 1 ... 1

你会发现

被卡成 \(O(n^2)\) 了!

Q:怎么办?

A:骂毒瘤出题人

我们发现瓶颈在这:

        for (unsigned int j = 0; j < e[r[i]].size(); j++) {
            int x = e[r[i]][j];
            q.push(x);
            if (d[x] == c[x]) --cnt;
            ++d[x];
            if (d[x] == c[x]) ++cnt;
        }
        while ((int)q.size() > T) {
            int x = q.front();
            q.pop();
            if (d[x] == c[x]) --cnt;
            --d[x];
            if (d[x] == c[x]) ++cnt;
        }

这个插入删除是 \(O(n)\) 的!

Q:我们能不能把它搞成 \(O(1)\) 的?

A:可以

考虑 Hash 思想

先看代码:

#include <bits/stdc++.h>
#define ull unsigned long long
#define pii pair<int, int>
using namespace std;
const int N = 1e5 + 6;
int n, T, Q, S[N], R[N], m, mx, len, ans, s[N], kkk;
vector<int> e[N];
struct H {
    ull h[6];
    H() {
        memset(h, 0, sizeof(h));
    }
} Hash[N], numS, now;
vector<H> num[N];
deque<pii> q;

inline ull RA() {
    return (ull)rand() * rand() * rand() * rand() * rand() * rand();
}

inline void add(H &a, H &b) {
    for (int i = 0; i < 6; i++) a.h[i] += b.h[i];
}

inline void del(H &a, H &b) {
    for (int i = 0; i < 6; i++) a.h[i] -= b.h[i];
}

inline bool equ(H a, H b) {
    for (int i = 0; i < 6; i++)
        if (a.h[i] != b.h[i]) return 0;
    return 1;
}

int main() {
    srand((unsigned)time(0));
    cin >> n >> T >> Q;
    for (int i = 1; i <= T; i++) {
        scanf("%d", &S[i]);
        mx = max(mx, S[i]);
    }
    for (int i = 1; i <= n; i++) {
        int len;
        scanf("%d", &len);
        while (len--) {
            int x;
            scanf("%d", &x);
            mx = max(mx, x);
            e[i].push_back(x);
        }
    }
    cin >> m;
    for (int i = 1; i <= m; i++) scanf("%d", &R[i]);
    for (int i = 0; i <= mx; i++)
        for (int j = 0; j < 6; j++)
            Hash[i].h[j] = RA();
    for (int i = 1; i <= T; i++) add(numS, Hash[S[i]]);
    for (int i = 1; i <= n; i++) {
        int len = e[i].size();
        num[i].resize(len);
        for (int j = len - 1; ~j; j--)
            add(num[i][j], Hash[e[i][j]]);
        for (int j = len - 2; ~j; j--)
            add(num[i][j], num[i][j+1]);
    }
    for (int i = 1; i <= m && i <= Q; i++) {
        add(now, num[R[i]][0]);
        len += e[R[i]].size();
        q.push_back({R[i], 0});
        while (len > T) {
            pii x = q.front();
            q.pop_front();
            del(now, num[x.first][x.second]);
            len -= e[x.first].size() - x.second;
            if (len < T) {
                int k = num[x.first].size() - (T - len);
                q.push_front({x.first, k});
                add(now, num[x.first][k]);
                len = T;
            }
        }
        if (equ(now, numS)) ++ans;
    }
    if (Q <= m) {
        cout << ans << endl;
        return 0;
    }
    Q -= m;
    for (int i = 1; i <= m && i <= Q; i++) {
        add(now, num[R[i]][0]);
        len += e[R[i]].size();
        q.push_back({R[i], 0});
        while (len > T) {
            pii x = q.front();
            q.pop_front();
            del(now, num[x.first][x.second]);
            len -= e[x.first].size() - x.second;
            if (len < T) {
                int k = num[x.first].size() - (T - len);
                q.push_front({x.first, k});
                add(now, num[x.first][k]);
                len = T;
            }
        }
        if (equ(now, numS)) ++kkk;
        s[i] = kkk;
    }
    cout << ans + kkk * (Q / m) + s[Q%m] << endl;
    return 0;
}

(小声)不好意思kkk,窝真的是没有变量名可用才用您的QWQ

我们给每个数若干维随机 Hash

    for (int i = 0; i <= mx; i++)
        for (int j = 0; j < 6; j++)
            Hash[i].h[j] = RA();

一串数的 Hash 值定义为每个数的每一维 Hash 分别相加(unsigned long long自然溢出)

如果两个串任意排列后可以相同,这两个串对应的每一维都应该相同

那么插入和删除就都可以做到 \(O(1)\) 了

你卡我随机化鸭

你卡我 Hash 鸭

你卡我常数大鸭

上一篇:【模板】Trie


下一篇:python的bif介绍