Featured image of post Codeforces Round #1086(Div.2)

Codeforces Round #1086(Div.2)

很有意思的一场,上大分啦~

B

题目大意: 有 $n$ 张牌放在一个队列中,每张牌的值为 $a_i$ ,在任何时刻,都只能打出队列中前 $k$ 位的牌,然后把同一张牌放回牌堆底部。

有一张起初位置位于 $p$ 的牌,打出就胜利,但是要求所有牌出牌的总花费不能超过 $m$ ,现在要问你这张牌最多能打出多少次?

数据范围: $1 \leq k,p \leq n \leq 5000, 1 \leq m \leq 5000,1 \leq a_i \leq m$

思路:容易知道如果前 $k$ 个中不含胜利牌,则每次从其中取出最小的,如果含胜利牌,那么肯定取出它,因此可以将它视为 $-1$ ,用一个优先队列维护。

然后,后面的部分就用一个队列维护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void solve() {
    int n, k, p, m;
    cin >> n >> k >> p >> m;
    p--;
    vi a(n);
    rep(i, 0, n - 1) cin >> a[i];
    int ans = 0;
    int tot = a[p];
    int tem = 0;
    a[p] = -1;
    deque<int> d;
    priority_queue<int, vector<int>, greater<>> q;
    rep(i, 0, k - 1) q.push(a[i]);
    rep(i, k, n - 1) d.push_back(a[i]);
    while (tem <= m) {
        auto node = q.top();
        q.pop();
        if (node == -1) {
            if (tem + tot > m) break;
            ans++;
            tem += tot;
            d.push_back(-1);
            auto tem2 = d.front();
            d.pop_front();
            q.push(tem2);
        } else {
            if (tem + node > m) break;
            tem += node;
            d.push_back(node);
            auto tem2 = d.front();
            d.pop_front();
            q.push(tem2);
        }
    }
    cout << ans << endl;
    return;
}

C

题目大意:给定 $n$ 个任务,起初你的体力为 $1$ ,对于每个任务,它的整数值为 $c_i$ ,难度为 $p_i$ ,如果你不完成它,那么什么都不会发生,如果完成它,可以拿到 $S \cdot c_i$ 分, $S$ 为当前体力,随后 $S$ 会变成 $S \cdot(1-\frac{p_i}{100})$ ,求能达到的最大得分。

数据范围: $1 \leq n \leq 10^5,1 \leq c_i \leq 100,0 \leq p_i \leq 100$ 。

思路:首先观察到,无论当前的 $S$ 如何,后续能获取的最大值是固定的,也就是 $S \cdot k$ ,这里 $k$ 是与 $S$ 无关的某个常数。

也就是说,完全可以假设 $dp[i]$ 为从这里开始的强度,然后倒着遍历,有 $dp[i]=max(dp[i+1],c[i]+(1-\frac{p[i]}{100})dp[i+1])$ 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void solve() {
    int n;
    cin >> n;
    vector<pii> ma(n);
    rep(i, 0, n - 1) cin >> ma[i].first >> ma[i].second;
    double ans = 0.0;
    frep(i, n - 1, 0) { ans = max(ans, (double)ma[i].first + (1.0 - (double)(ma[i].second) / 100.0) * ans); }
    cout << fixed << setprecision(10) << ans << endl;
    return;
}

D1

题目大意:给定一棵带有 $n$ 个节点的无向树,现在给每个边加了一个方向,给定所有元素之间能不能互相到达的矩阵,问是否能还原出加方向后树的结构?

数据范围: $2 \leq n\leq 500, 1 \leq sum(n^3) \leq 500^3$

思路:看到这个必然考虑弗洛伊德,这里有一个反向的思路,就是对于一个已经处理好弗洛伊德的矩阵,怎么把它还原回原来的矩阵,那么就是如果一个点是间接连上的,那么就把它还原。

接着,判断一下处理出来的基图是否是一棵树,并且边数跟连通性对不对即可,赛时确实有点蠢。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
void solve() {
    int n;
    cin >> n;
    vector<string> a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vvi ma(n, vi(n));
    rep(i, 0, n - 1) { rep(j, 0, n - 1) ma[i][j] = a[i][j] - '0'; }
    auto ma2 = ma;
    rep(i, 0, n - 1) {
        if (ma[i][i] == 0) {
            cout << "NO" << endl;
            return;
        }
    }
    rep(k, 0, n - 1) {
        rep(i, 0, n - 1) {
            rep(j, 0, n - 1) {
                if (ma[i][k] == 1 && ma[k][j] == 1) {
                    if (k == i || k == j) continue;
                    if (ma[i][j] == 0) {
                        cout << "NO" << endl;
                        return;
                    }
                    if (ma[i][j] == 1) ma2[i][j] = 0;
                }
            }
        }
    }
    vector<pii> res;
    vector<vector<bool>> vis(n, vector<bool>(n, false));
    rep(i, 0, n - 1) vis[i][i] = true;
    auto dfs2 = [&](this auto&& dfs2, int x, int pa) -> void {
        rep(i, 0, n - 1) {
            if (!ma2[x][i] || i == x) continue;
            if (vis[x][i]) continue;
            res.emplace_back(x + 1, i + 1);
            vis[x][i] = true;
            dfs2(i, x);
        }
        return;
    };
    rep(i, 0, n - 1) { dfs2(i, -1); }
    map<int, int> ma3;
    for (auto& p : res) {
        ma3[p.first]++;
        ma3[p.second]++;
    }
    if (sz(res) != n - 1 || sz(ma3) != n) {
        cout << "NO" << endl;
        return;
    }
    vvi ma4(n);
    for (auto& p : res) {
        ma4[p.first - 1].push_back(p.second - 1);
        ma4[p.second - 1].push_back(p.first - 1);
    }
    int ans = 0;
    vector<bool> vis2(n, false);
    auto dfs = [&](this auto&& dfs, int x, int pa) -> void {
        vis2[x] = true;
        for (auto& p : ma4[x]) {
            if (p == pa || vis2[p]) continue;
            dfs(p, x);
        }
        return;
    };
    rep(i, 0, n - 1) {
        if (!vis2[i]) {
            ans++;
            dfs(i, -1);
        }
    }
    if (ans >= 2) {
        cout << "NO" << endl;
        return;
    }
    cout << "YES" << endl;
    for (auto& p : res) cout << p.first << ' ' << p.second << endl;
    return;
}

D2

题目大意与上题相同。

数据范围: $2 \leq n \leq 8000, 1 \leq sum(n^2) \leq 8000^2$ 。

思路:非常显然地,上题的做法过不去,且本题正解应该是 $O(n^2)$ 或者 $O(n^2 \log n)$ ,不过赛时竟然放 $O(\frac{n^3}{w})$ 复杂度的过去了,到底是何意味。

首先考虑 $O(n^2)$ 的算法有哪些,结合树是有向无环图的性质,很容易想到的是拓扑排序,然后这里有一个关键观察:在当前元素能到达的点中,拓扑序最小的那个必定与它相连,而这个相连点连的边就要从原先的点中删除,因为它们是间接的。

这样就可以处理出所有的边了,然后还是需要验一下整个是不是树。

另外,这题卡常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void solve() {
    int n;
    cin >> n;
    vector<string> ma(n);
    rep(i, 0, n - 1) cin >> ma[i];
    rep(i, 0, n - 1) {
        if (ma[i][i] == '0') {
            cout << "NO" << '\n';
            return;
        }
    }
    rep(i, 0, n - 1) {
        rep(j, i + 1, n - 1) {
            if (ma[i][j] == '1' && ma[j][i] == '1') {
                cout << "NO" << '\n';
                return;
            }
        }
    }
    vi deg(n);
    vvi ma2(n);
    rep(i, 0, n - 1) {
        rep(j, 0, n - 1) {
            if (i == j || ma[i][j] == '0') continue;
            ma2[i].push_back(j);
            deg[j]++;
        }
    }
    queue<int> q;
    rep(i, 0, n - 1) {
        if (!deg[i]) q.push(i);
    }
    vi tem;
    while (!q.empty()) {
        auto node = q.front();
        tem.push_back(node);
        q.pop();
        for (int& p : ma2[node]) {
            if (--deg[p] == 0) q.push(p);
        }
    }
    if (sz(tem) != n) {
        cout << "NO" << '\n';
        return;
    }
    vector<pii> res;
    rep(i, 0, n - 1) {
        int t = tem[i];
        rep(j, i + 1, n - 1) {
            int v = tem[j];
            if (ma[t][v] == '1') {
                res.emplace_back(t + 1, v + 1);
                if (sz(res) > n - 1) {
                    cout << "NO" << '\n';
                    return;
                }
                for (int& p : ma2[v]) {
                    if (ma[t][p] == '1') {
                        ma[t][p] = '0';
                    } else {
                        cout << "NO" << '\n';
                        return;
                    }
                }
            }
        }
    }
    if (sz(res) != n - 1) {
        cout << "NO" << '\n';
        return;
    }
    vector<bool> vis(n, false);
    int ans = 0;
    vvi ma3(n);
    for (auto& p : res) {
        ma3[p.first - 1].push_back(p.second - 1);
        ma3[p.second - 1].push_back(p.first - 1);
    }
    auto dfs = [&](this auto&& dfs, int x) -> void {
        vis[x] = true;
        for (auto& p : ma3[x]) {
            if (!vis[p]) dfs(p);
        }
    };
    rep(i, 0, n - 1) {
        if (!vis[i]) {
            ans++;
            dfs(i);
        }
    }
    if (ans >= 2) {
        cout << "NO" << '\n';
        return;
    }
    cout << "YES" << '\n';
    for (auto& p : res) cout << p.first << ' ' << p.second << '\n';
    return;
}