10 月 16 日模拟赛总结

· · 个人记录

Before

本文章在博客园同步发布

Contest-Link

预期 30 + 0 + 0 + 20 = 50

实际 30 + 0 +100+ 60 = 190

挂分 -140

rk2/totrk7,行。

看 T1,单调栈,不会,写暴力溜。看 T2,挺线段树的,但好像维护不了,写暴力溜。看 T3,不会怎么还不会啊,挺 K 的一个树形 DP,没拿暴力 30 分是最大的失误。然后就只有 1h 了,感觉今天要爆零啊。然后开 T4,啊啊这不板子题吗,怎么最简单啊,这么牛。你直接坐坐坐坐坐坐坐下!结果被坐下了,剩下 15min 还没调出来,我恼恼恼恼恼恼恼。然后本来想输出样例的,但不能放弃,然后我调调调调调调,然后是并查集写错了,看了半小时,哈哈。只剩 5min 了大样例还是错的,我急急急急急急急。哈哈。我当场感觉今天寄了啊,垫底了啊。哈哈哈哈哈哈哈。

结果非常炸裂,我 T4 过了。经典西西弗数据啊。而且初一组就我一人 AC 了。原来是我样例复制少了,最后 3min 惊险调出来了,哈哈哈哈哈哈哈。

然后就寄了。

T1

Description

给定一个排列,每一轮对于每个数,如果它左边相邻的数比它大则删除,求几轮后不会再删除数。

## Solution 这不显然的单调栈吗~~我考试时怎么没想出来啊~~。 只要不是逆序对塞进去然后答案计数即可。 考场想法:暴力。 考场寄因:暴力。 时间复杂度 $O(n)$,空间复杂度 $O(n)$。 ## Code $\text{100pts:}
// 2023/10/16 PikachuQAQ

#include <iostream>

using namespace std;

const int kMaxN = 1e6 + 7;

int n, a[kMaxN], ans;
int stk[kMaxN], top;

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = n, cnt; i; i--) {
        for (cnt = 0; top && stk[top] < a[i]; ) {
            top--, cnt++;
        }
        stk[++top] = a[i], ans = max(ans, cnt);
    }
    cout << ans;

    return 0;
}

T2

Description

给定 1n 的数轴上每个点最多能够被覆盖的次数 a_i,以及 m 个区间,求最多可以选择几个区间。

## Solution 可以证明,按右端点排序来覆盖是最优的。 于是我们使用线段树贪心的覆盖,能塞就塞。 时间复杂度 $O(n\log n)$,空间复杂度 $O(n)$。 ## Code $\text{100pts:}
// 2023/10/16 PikachuQAQ

#include <iostream>
#include <algorithm>

using namespace std;

const int kMaxN = 1e6 + 7, kMaxM = 4e6 + 7, INF = 1e9;

struct E {
    int l, r;
} a[kMaxN];

bool cmp(E x, E y) {
    return x.r < y.r;
}

int n, m, ans;
int seg[kMaxM], add[kMaxM];

int M(int l, int r) { return l + r >> 1; } 
int L(int p) { return p << 1; } 
int R(int p) { return p << 1 | 1; } 
void tag(int p, int d) { seg[p] += d, add[p] += d; } 
void pushup(int p) { seg[p] = min(seg[L(p)], seg[R(p)]); }
void pushdown(int p) { 
    tag(L(p), add[p]), tag(R(p), add[p]);
    add[p] = 0;
}

void Update(int s, int t, int l, int r, int p, int d) {
    if (s <= l && r <= t) {
        tag(p, d);
    } else {
        pushdown(p);
        if (s <= M(l, r)) {
            Update(s, t, l, M(l, r), L(p), d);
        }
        if (t >= M(l, r) + 1) {
            Update(s, t, M(l, r) + 1, r, R(p), d);
        }
        pushup(p);        
    }
}

int Query(int s, int t, int l, int r, int p) {
    int res = INF;
    if (s <= l && r <= t) {
        return seg[p];
    }
    pushdown(p);
    if (s <= M(l, r)) {
        res = min(Query(s, t, l, M(l, r), L(p)), res);
    } 
    if (t >= M(l, r) + 1) {
        res = min(res, Query(s, t, M(l, r) + 1, r, R(p)));
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1, x; i <= n; i++) {
        cin >> x;
        Update(i, i, 1, n, 1, x);
    }
    for (int i = 1; i <= m; i++) {
        cin >> a[i].l >> a[i].r;
    }
    sort(a + 1, a + m + 1, cmp);
    for (int i = 1; i <= m; i++) {
        if (Query(a[i].l, a[i].r, 1, n, 1)) {
            ans++;
            Update(a[i].l, a[i].r, 1, n, 1, -1);
        }
    }
    cout << ans;

    return 0;
}

T3

Description

给定一棵树,对边进行黑白染色,不能有两条黑边相邻,求最多黑边数量以及对应的方案数。

## Solution 有 $f_{i,0/1}$ 表示 $i$ 不选择或选择时的能匹配的最多点,$g_{i,0/1}$ 表示 $f_i$ 匹配点的方案数,$p_i$ 表示 $i$ 不匹配的状况下,$j$ 匹配的方案数。 当这个点不连接时,能匹配的点自然是它儿子最多能选雨最多不选中的最大值,也就是: $$f_{u,0}=\sum^m_{v=1} \max(f_{v,0},f_{v,1})$$ 然后我们考虑选择。对于当前的能匹配的点,我们减去重复的 $\max(f_{v,0},f_{v,1})$,也有可能下面的儿子直接不匹配了,所以点加上 $f_{v,0}+1$。也就是: $$f_{u,1}=\max^m_{i=1}(f_{u,0} - \max(f_{v,0}, f_{v,1})+f_{v,0}+1)$$ 然后我们统计方案数。我们只考虑转移合法的,从下往上转移即可。 令 $x=[f_{v,0}\ge f_{v,1}],y=[f_{v,0}\le f_{v,1}]$,有 $g_{u,0}$ 转移: $$g_{u,0}=\prod^m_{j=1}(g_{v,0} \times x + g_{v,1})$$ 然后就有了选择的 $g_{u,1}$ 转移: $$g_{u,1}=\dfrac{g_{u,0}}{h_j}\times g_{v,0}$$ 然后 DP 数组会炸,高精度一下,就做完了。 时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。 ## Code $\text{100pts:}
// 2023/10/16 PikachuQAQ

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>

using namespace std;

const int kMaxN = 1007, INF = 1e9;

typedef long long ll;

struct BigInt {
    static const int BASE = 100000000, WIDTH = 8; 
    vector<ll> s;

    BigInt() {
        *this = 0;
    }

    BigInt(const int& num) {
        *this = num;
    }

    BigInt operator = (int num) {
        s.clear();
        do {
            s.push_back(num % BASE);
            num /= BASE;
        } while (num > 0);
        return *this;
    }

    BigInt operator = (const string& str) {
        s.clear();
        int x, len = (str.length() - 1) / WIDTH + 1;
        for (int i = 0; i < len; i++) {
            int end = str.length() - i * WIDTH;
            int start = max(0, end - WIDTH);
            sscanf(str.substr(start, end - start).c_str(), "%lld", &x);
            s.push_back(x);
        }
        return *this;
    }
    bool operator<(const BigInt& b) {
        if (s.size() < b.s.size()) return true;
        if (s.size() > b.s.size()) return false;
        for (int i = s.size() - 1; i >= 0; i--) {
            if (s[i] < b.s[i]) return true;
            if (s[i] > b.s[i]) return false;
        }
        return false;
    }

    bool operator >= (const BigInt& b) {
        return !(*this < b);
    }

    bool operator == (const BigInt& b) {
        if (s.size() != b.s.size()) return 0;
        for (int i = 0; i < s.size(); i++)
            if (s[i] != b.s[i]) return 0;
        return 1;
    }

    BigInt operator + (const BigInt& b) {
        BigInt c;
        c.s.clear();
        for (int i = 0, g = 0;; i++) {
            if (g == 0 && i >= s.size() && i >= b.s.size()) break;
            int x = g;
            if (i < s.size()) x += s[i];
            if (i < b.s.size()) x += b.s[i];
            c.s.push_back(x % BASE);
            g = x / BASE;
        }
        return c;
    }

    BigInt operator - (const BigInt& b) {
        BigInt c;
        c = *this;
        for (int i = 0; i < c.s.size(); i++) {
            int tmp;
            if (i >= b.s.size())
                tmp = 0;
            else
                tmp = b.s[i];
            if (c.s[i] < tmp) {
                c.s[i + 1] -= 1;
                c.s[i] += BASE;
            }
            c.s[i] -= tmp;
        }
        while (c.s.back() == 0 && c.s.size() > 1) c.s.pop_back();
        return c;
    }

    void operator -= (const BigInt& b) {
        *this = *this - b;
    }

    BigInt operator * (const BigInt& b) {
        BigInt c;
        c.s.resize(s.size() + b.s.size());
        for (int i = 0; i < s.size(); i++)
        for (int j = 0; j < b.s.size(); j++) c.s[i + j] += s[i] * b.s[j];
        for (int i = 0; i < c.s.size() - 1; i++) {
            c.s[i + 1] += c.s[i] / BASE;
            c.s[i] %= BASE;
        }
        while (c.s.back() == 0 && c.s.size() > 1) c.s.pop_back();
        return c;
    }

    void operator *= (const BigInt& b) {
        *this = *this * b;
    }

    friend istream& operator >> (istream& input, BigInt& x) {
        string s;
        if (!(input >> s)) return input;
        x = s;
        return input;
    }

    friend ostream& operator << (ostream& output, const BigInt& x) {
        output << x.s.back();
        for (int i = x.s.size() - 2; i >= 0; i--) {
            char buf[20];
            sprintf(buf, "%08d", x.s[i]);
            for (int j = 0; j < strlen(buf); j++) output << buf[j];
        }
        return output;
    }
} mod = 2, ans;

BigInt Copy(const BigInt& b, int x) {
    BigInt t;
    t.s.resize(b.s.size() + x);
    for (int i = 0; i < b.s.size(); i++) t.s[i + x] = b.s[i];
    return t;
}

BigInt Divide(const BigInt& a, const BigInt& b, BigInt& mod) {
    BigInt c;
    c.s.resize(a.s.size() - b.s.size() + 1);
    mod = a;
    int Pow[(int)log2(BigInt::BASE) + 5];
    Pow[0] = 1;
    for (int i = 1; i <= log2(BigInt::BASE); i++) Pow[i] = Pow[i - 1] * 2;
    for (int i = c.s.size() - 1; i >= 0; i--) {
        BigInt t;
        t = Copy(b, i);
        for (int j = log2(BigInt::BASE); j >= 0; j--)
        if (mod >= t * Pow[j]) {
            c.s[i] += Pow[j];
            mod -= t * Pow[j];
        }
    }
    while (c.s.back() == 0 && c.s.size() > 1) c.s.pop_back();
    return c;
}

int n;
vector<int> a[kMaxN];
BigInt g[kMaxN][2], h[kMaxN];
ll f[kMaxN][2];

void DFS(int u, int fa) {
    BigInt res = 1;
    f[u][0] = 0, f[u][1] = -INF;
    g[u][0] = 1;
    for (int v : a[u]) {
        if (v ^ fa) {
            DFS(v, u);
            h[v] = g[v][0] * (f[v][0] >= f[v][1]) + g[v][1] * (f[v][0] <= f[v][1]);
            res *= h[v];
            f[u][0] += f[v][1] * (f[v][0] < f[v][1]) + f[v][0] * (f[v][0] < f[v][1] == 0);
        }
    }
    g[u][0] = res, f[u][1] = -INF;
    for (int v : a[u]) {
        if (v ^ fa) {
            int val = f[u][0] - max(f[v][1] - f[v][0], 0ll) + 1;
            if (f[u][1] < val) {
                f[u][1] = val;
                g[u][1] = Divide(res, h[v], mod) * g[v][0];
            } else if (f[u][1] == val) {
                g[u][1] = Divide(res, h[v], mod) * g[v][0] + g[u][1];
            }
        }
    }
}

int main() {
    for (int i = 1; i <= 1005; i++) {
        mod *= 2;
    }
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1, u, m; i <= n; i++) {
        cin >> u >> m;
        for (int j = 1, v; j <= m; j++) {
            cin >> v;
            a[u].push_back(v);
        }
    }
    DFS(1, 0);
    ans = g[1][0] * (f[1][0] >= f[1][1]) + g[1][1] * (f[1][0] <= f[1][1]);
    cout << max(f[1][0], f[1][1]) << '\n' << ans << '\n';

    return 0;
}

T4

Description

给定 n 个数之间的一些大于、小于、等于关系,关系数为 m,按照数值第一编号第二排序,求是否唯一。

多组数据。

## Solution 很显然,当 $a$ 比 $b$ 大时,我们建一条从 $a$ 到 $b$ 的变,$a$ 比 $b$ 小同理。 然后这个等于处理比较麻烦,我们想到一个点数值一样,那么大小关系也是一样的,我们可以把这些互相等于的连通块缩到一个点在和其他点连边,然后进行操作。这一部分可以用并查集处理。 首先想想 `no enough` 怎么处理。 我们知道,当一个点的入度为零时,我们不知道还有哪些数比他更大,那么当入度为零的点数大于一时,这几个点因为没有比他们互相关系更大的了,我们判断不了他们之间的互相关系,所以这种情况就是条件缺失,并且当出度为零的点数大于一时,我们也互相判断不了这些点的大小关系,同样条件不足。这一部分可以统计缩点后的图的点入出度。 然后想想 `contradiction`,冲突怎么处理。 很显然,一个合法的大小关系一定是一个 DAG,不可能出现以下情况: `A > B and B > A` `A > B and B > C and C > A` 也就是说,不可能图中会有环。一旦有环就会冲突。这一部分可以拓扑排序处理。注意是对缩点后的图进行。 最后,以上两种情况都不是,那么就是 `yes` 了。 考场想法:并查集拓扑排序。 考场寄因:没寄。 时间复杂度 $O(n+m)$,空间复杂度 $O(n+m)$。 ## Code $\text{100pts:}
// 2023/10/16 PikachuQAQ

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

const int kMaxN = 10007;

int n, m, cnt1, cnt2, tot;
vector<int> g[kMaxN], a[kMaxN];
int in[kMaxN], out[kMaxN];
int fa[kMaxN], siz[kMaxN];
char laugh_my_axx_off;
bool ne, co;
queue<int> q;

int findfa(int x) {
    return (fa[x] == x) ? x : (fa[x] = findfa(fa[x]));
}

void uni(int x, int y) {
    int u = findfa(x), v = findfa(y);
    if (siz[u] > siz[v]) {
        swap(u, v);
    }
    fa[u] = v, siz[v] += siz[u];
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    while (cin >> n >> m) {
        for (int i = 0; i <= n; i++) {
            fa[i] = i, siz[i] = 1;
        }
        for (int i = 0, u, v; i < m; i++) {
            cin >> u >> laugh_my_axx_off >> v;
            if (laugh_my_axx_off == '<') {
                g[v].push_back(u);
            }
            if (laugh_my_axx_off == '>') {
                g[u].push_back(v);
            }
            if (laugh_my_axx_off == '=') {
                uni(u, v);
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j : g[i]) {
                a[findfa(i)].push_back(findfa(j));
                in[findfa(j)]++, out[findfa(i)]++;  
            }
        }
        for (int i = 0; i < n; i++) {
            cnt1 += (in[i] == 0 && fa[i] == i);
            cnt2 += (out[i] == 0 && fa[i] == i);
        }
        ne = (cnt1 > 1 || cnt2 > 1);
        for (int i = 0; i < n; i++) {
            if (in[i] == 0 && fa[i] == i) {
                q.push(i);
            }
        }
        while (q.size()) {
            int u = q.front();
            q.pop();
            for (int v : a[u]) {
                --in[v];
                if (in[v] == 0) {
                    q.push(v);
                }
            }
        }
        for (int i = 0; i < n; i++) {
            if (in[i] > 0 && fa[i] == i) {
                co = 1;
            }
        }
        if (co) {
            cout << "contradiction\n";
        } else if (ne) {
            cout << "not enough\n";
        } else {
            cout << "yes\n";
        }
        co = ne = cnt1 = cnt2 = 0;
        for (int i = 0; i < n; i++) {
            in[i] = out[i] = 0;
            g[i].clear(), a[i].clear();
        }
    }

    return 0;
}

Summary

需要掌握的:规划时间