codeforces.com 解题报告 (难度1500-1900, 题目1-50)

939 阅读31分钟

1. (158A) Next Round

熟悉面向对象的程序设计。

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N, K;
    vector<int> A;

    void Solve() {
        scanf("%d%d", &N, &K);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            if (0 < A[i] && A[K - 1] <= A[i]) ++answer;
        }
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

2. (158B) Taxi

常用的简化技巧:计算每个数出现了多少次。

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> A;

    void Solve() {
        scanf("%d", &N);
        A.resize(5);
        for (int i = 0; i < N; ++i) {
            int s;
            scanf("%d", &s);
            ++A[s];
        }
        int answer = A[4] + A[3] + (A[2] + 1) / 2;
        A[1] -= A[3];
        if (A[2] % 2 != 0) A[1] -= 2;
        if (A[1] < 0) A[1] = 0;
        answer += (A[1] + 3) / 4;
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

3. (455A) Boredom

先使用简化技巧,再注意到其实就是从一堆数里面选,但是不能选相邻的。记录 f(i) 为从 i 开始能达到的最大收益,则有递推式

f(i)=\begin{cases}
0, & i=0;\\
\max\begin{cases}
f(i-1)\\
f(i-2)+iB_{i}
\end{cases}, & i>0;
\end{cases}
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> A, B;

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        B.resize(1 + *max_element(A.begin(), A.end()));
        for (int i = 0; i < N; ++i) ++B[A[i]];
        vector<long long> f(B.size());
        f[0] = 0;
        for (int i = 1; i < f.size(); ++i) {
            long long f1 = 2 <= i ? f[i - 2] : 0;
            f[i] = max(f[i - 1], f1 + (long long)B[i] * i);
        }
        printf("%lld\n", f.back());
    }
};

int main() {
    Solution().Solve();
    return 0;
}

4. (4C) Registration System

练习 map 的用法。

#include <cstdio>
#include <map>
using namespace std;

struct Solution {
    int N;
    map<string, int> H;

    string UniqueName(const string& s) {
        map<string, int>::iterator it = H.find(s);
        if (it == H.end()) {
            H.insert(it, make_pair(s, 0));
            return "OK";
        }
        char buffer[80];
        sprintf(buffer, "%d", ++it->second);
        return s + buffer;
    }

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            char s[33];
            scanf("%s", s);
            printf("%s\n", UniqueName(s).c_str());
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

5. (466C) Number of Ways

像这种题,一般是两边分别计算。先定义

\begin{align*}
B_{1}(i) & =\#\left\{ j\Big|j\le i,\left(\sum_{k=1}^{j}A_{k}\right)=\text{Sum}/3\right\} ;\\
B_{2}(i) & =\#\left\{ j\Big|j=i,\left(\sum_{k=j}^{N}A_{k}\right)=\text{Sum}/3\right\} .
\end{align*}

则答案为 \sum_{k} B_{1}(k-2)\cdot B_{2}(k) .

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> A, B1, B2;

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        long long sum = 0, prefixSum = 0, suffixSum = 0;
        for (int i = 0; i < N; ++i) {
            scanf("%d", &A[i]);
            sum += A[i];
        }
        if (sum % 3 != 0) {
            printf("0\n");
            return;
        }
        B1.resize(N);
        for (int i = 0; i < N; ++i) {
            prefixSum += A[i];
            B1[i] = prefixSum == sum / 3;
        }
        B2.resize(N);
        for (int i = N - 1; i >= 0; --i) {
            suffixSum += A[i];
            B2[i] = suffixSum == sum / 3;
        }
        for (int i = 1; i < N; ++i) B1[i] += B1[i - 1];
        long long answer = 0;
        for (int i = 2; i < N; ++i) {
            answer += B1[i - 2] * B2[i];
        }
        printf("%lld\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

6. (550A) Two Substrings

熟悉 strstr() 的用法,以及字符串即字符指针的各种操作。

#include <cstdio>
#include <cstring>
using namespace std;

struct Solution {
    bool Find(const char* s, const char* s1, const char* s2) {
        const char *s3 = strstr(s, s1);
        if (!s3) return false;
        s3 += strlen(s1);
        const char *s4 = strstr(s3, s2);
        return s4;
    }

    void Solve() {
        char s[100001];
        scanf("%s", s);
        bool answer = false;
        if (Find(s, "AB", "BA")) answer = true;
        if (Find(s, "BA", "AB")) answer = true;
        printf(answer ? "YES\n" : "NO\n");
    }
};

int main() {
    Solution().Solve();
    return 0;
}

7. (580C) Kefa and Park

熟悉树的表示,以及树上的动态规划方法。一般是先下传信息,再上传信息。这道题,先下传在每个节点见过几只猫,再上传有多少个叶子节点可达。

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N, M;
    vector<vector<int> > Out;
    vector<bool> HasCat;

    void Hang(int no, int parent = -1) {
        int newSize = 0;
        for (int i = 0; i < Out[no].size(); ++i) {
            if (Out[no][i] != parent) Out[no][newSize++] = Out[no][i];
        }
        Out[no].resize(newSize);
        for (int i = 0; i < Out[no].size(); ++i) {
            Hang(Out[no][i], no);
        }
    }

    int Kefa(int no, int cat) {
        cat = HasCat[no] ? cat + 1 : 0;
        if (M < cat) return 0;
        int answer = 0;
        bool isLeaf = true;
        for (int i = 0; i < Out[no].size(); ++i) {
            answer += Kefa(Out[no][i], cat);
            isLeaf = false;
        }
        return isLeaf ? 1 : answer;
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N);
        HasCat.resize(N);
        for (int i = 0; i < N; ++i) {
            int hasCat;
            scanf("%d", &hasCat);
            HasCat[i] = hasCat;
        }
        for (int i = 0; i < N - 1; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1 - 1].push_back(v2 - 1);
            Out[v2 - 1].push_back(v1 - 1);
        }
        Hang(0);
        printf("%d\n", Kefa(0, 0));
    }
};

int main() {
    Solution().Solve();
    return 0;
}

8. (479C) Exams

考虑右端点在最左边的区间,如果能选左端点,我们就选左端点(这一定是最优的,因为没有影响别的区间),否则选右端点。这样就构成了一个贪心算法。注意,如果右端点相等的情况,我们先处理左端点靠左的线段。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

struct Exam {
    int A, B;

    bool operator<(const Exam& that) {
        return A < that.A || (A == that.A && B < that.B);
    }
};

struct Solution {
    int N;
    vector<Exam> Exams;

    void Solve() {
        scanf("%d", &N);
        Exams.resize(N);
        for (int i = 0; i < N; ++i) {
            scanf("%d%d", &Exams[i].A, &Exams[i].B);
        }
        sort(Exams.begin(), Exams.end());
        int current = 0;
        for (int i = 0; i < N; ++i) {
            if (Exams[i].B < current) {
                current = Exams[i].A;
            } else {
                current = Exams[i].B;
            }
        }
        printf("%d\n", current);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

9. (1B) Spreadsheet

数制转换题,先算出最后一位。然后减掉最后一位,除以 26,继续计算。注意学习 sscanf 和 sprintf 的用法。

#include <algorithm>
#include <cstdio>
#include <string>
using namespace std;

struct Solution {
    int N;

    bool Parse(const string& s, int* r, int* c) {
        return sscanf(s.c_str(), "R%dC%d", r, c) == 2;
    }

    string FromRC(int r, int c) {
        string answer;
        while (c != 0) {
            int c0 = (c - 1) % 26;
            answer += char('A' + c0);
            c = (c - (c0 + 1)) / 26;
        }
        reverse(answer.begin(), answer.end());
        char s[80];
        sprintf(s, "%d", r);
        return answer += s;
    }

    string ToRC(string s) {
        char s0[80];
        int r, c = 0;
        sscanf(s.c_str(), "%[A-Z]%d", s0, &r);
        for (int i = 0; s0[i]; ++i) c = c * 26 + (s0[i] - 'A' + 1);
        sprintf(s0, "R%dC%d", r, c);
        return s0;
    }

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            char s[80];
            scanf("%s", s);
            int r, c;
            if (Parse(s, &r, &c)) {
                printf("%s\n", FromRC(r, c).c_str());
            } else {
                printf("%s\n", ToRC(s).c_str());
            }
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

10. (550C) Divisibility by Eight

注意到能被 8 整除,则后三位能被 8 整除,因此答案长度不必超过 3 。考虑暴力搜索即可。

#include <cstdio>
#include <string>
using namespace std;

struct Solution {
    string S, Result, Chosen;

    bool Put(int pos) {
        if (3 < Chosen.size()) return false;
        if (S.size() < pos) return false;
        if (Put(pos + 1)) return true;
        Chosen.push_back(S[pos]);
        int value;
        bool success = sscanf(Chosen.c_str(), "%d", &value) == 1;
        if (success && value % 8 == 0) {
            Result = Chosen;
            return true;
        }
        if (Put(pos + 1)) return true;
        Chosen.pop_back();
        return false;
    }

    void Solve() {
        char s[101];
        scanf("%s", s);
        S = s;
        if (Put(0)) {
            printf("YES\n%s\n", Result.c_str());
        } else {
            printf("NO\n");
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

11. (545C) Woodcutters

从左向右,每棵树先考虑向左倒,再考虑向右倒(向右倒总不比不倒差,因为向右倒至多导致后面的一棵树不能倒),贪心算法即可。

#include <cstdio>
#include <climits>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> X, H;

    void Solve() {
        scanf("%d", &N);
        X.resize(N);
        H.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d%d", &X[i], &H[i]);
        int last = INT_MIN;
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            int next = i == N - 1 ? INT_MAX : X[i + 1];
            if (last < X[i] - H[i]) {
                ++answer;
                last = X[i];
            } else if (X[i] + H[i] < next) {
                ++answer;
                last = X[i] + H[i];
            } else {
                last = X[i];
            }
        }
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

12. (431C) k-Tree

其实是要求一个由 1\sim k 组成的串,其中至少有一个值 \ge d, 和为 n,问放法数量。令 f(n) 为和为 n 要求有值 \ge d 的放法数量,g(n) 为和为 n 但不要求有值 \ge d 的放法数量。

\begin{align*}
f(n) & =\sum_{i=1}^{d-1}f(n-i)+\sum_{i=d}^{k}g(n-i)\\
g(n) & =\sum_{i=1}^{k}g(n-i)\;.
\end{align*}

F(n)=\sum_{i=1}^{n}f(i)\qquad G(n)=\sum_{i=1}^{n}g(i)\;.

能算出 F(n)G(n) 的递推关系,且不含求和号如下:

\begin{align*}
F(n) & =F(n-1)+\left[F(n-1)-F(n-d)\right]+\left[G(n-d)-G(n-k-1)\right]\\
G(n) & =G(n-1)+\left[G(n-1)-G(n-k-1)\right]
\end{align*}
#include <cstdio>
#include <vector>
using namespace std;

const int M = 1000000007;

struct Solution {
    int N, K, D;
    vector<long long> F, G;

    void Solve() {
        scanf("%d%d%d", &N, &K, &D);
        F.resize(N + 1);
        G.resize(N + 1);
        F[0] = 0;
        G[0] = 1;
        for (int i = 1; i <= N; ++i) {
            long long g1 = i - K >= 1 ? G[i - K - 1] : 0;
            long long g2 = i >= D ? G[i - D] : 0;
            long long f2 = i >= D ? F[i - D] : 0L;
            long long gi = (G[i - 1] + (G[i - 1] - g1)) % M;
            G[i] = (gi + M) % M;
            long long fi = (F[i - 1] + (F[i - 1] - f2) + (g2 - g1)) % M;
            F[i] = (fi + M) % M;
        }
        long long answer = (F[N] - F[N - 1]) % M;
        printf("%lld\n", (answer + M) % M);
    }

};

int main() {
    Solution().Solve();
    return 0;
}

13. (474D) Flowers

动态规划。令 f(n) 表示长度为 n 的花有多少种吃法。则 f(n)=f(n-1)+f(n-k). 为了算出 f 的区间和,不妨求出 f 的前缀和,然后两个前缀和相减就是部分和。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

const int M = 1000000007;

struct Solution {
    int T, K;
    vector<int> A, B;

    void Solve() {
        scanf("%d%d", &T, &K);
        A.resize(T);
        B.resize(T);
        for (int i = 0; i < T; ++i) {
            scanf("%d%d", &A[i], &B[i]);
        }
        int m = *max_element(B.begin(), B.end());
        vector<int> f(m + 1);
        f[0] = 1;
        for (int i = 1; i <= m; ++i) {
            int f1 = K <= i ? f[i - K] : 0;
            f[i] = (f[i - 1] + f1) % M;
        }
        for (int i = 1; i <= m; ++i) f[i] = (f[i] + f[i - 1]) % M;
        for (int i = 0; i < T; ++i) {
            printf("%d\n", (f[B[i]] + M - f[A[i] - 1]) % M);
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

14. (580B) Kefa and Company

先按照钱的多少排序,然后邀请的朋友们一定是连续的一段。计算每个点结尾的友谊因子之和的最大值即可,令 i 表示这个最大值从何处开始,我们可以证明 i 是单调增的。因此得到了一个 O(n) 的算法。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

bool AssignMax(long long* p, long long v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Person {
    int M, S;

    bool operator<(const Person& that) { return M < that.M; }
};

struct Solution {
    int N, D;
    vector<Person> A;

    void Solve() {
        scanf("%d%d", &N, &D);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d%d", &A[i].M, &A[i].S);
        sort(A.begin(), A.end());
        int p1 = 0, p2 = 0;
        long long current = 0, answer = 0;
        while (p2 < A.size()) {
            while (A[p1].M + D <= A[p2].M) current -= A[p1++].S;
            AssignMax(&answer, current += A[p2++].S);
        }
        printf("%lld\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

15. (2A) Winner

简单的模拟题,学习一下如何遍历一个 map。注意合并特殊情况至一般情况,这是信息学常见的简化程序实现的技巧。

#include <cstdio>
#include <map>
#include <string>
#include <vector>
using namespace std;

bool AssignMax(int* p, int v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Solution {
    int N;
    vector<string> Name;
    vector<int> Score;

    void Solve() {
        scanf("%d", &N);
        Name.resize(N);
        Score.resize(N);
        map<string, int> finalScore;
        for (int i = 0; i < N; ++i) {
            char s[33];
            scanf("%s%d", s, &Score[i]);
            Name[i] = s;
            finalScore[Name[i]] += Score[i];
        }
        int maxScore = 0;
        for (
            map<string, int>::iterator it = finalScore.begin();
            it != finalScore.end();
            ++it
        ) AssignMax(&maxScore, it->second);
        map<string, int> currentScore;
        for (int i = 0; i < N; ++i) {
            int score = currentScore[Name[i]] += Score[i];
            if (maxScore <= score && finalScore[Name[i]] == maxScore) {
                printf("%s\n", Name[i].c_str());
                return;
            }
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

16. (377A) Maze

可以想到一个贪心算法,一开始先把度为 1 的点删去,再把新的度为 1 的点删去……可是有环的情况怎么办呢?考虑一个生成树就可以了。不妨对原图做一个 DFS,然后沿着节点序的逆序删除,每次删除的点一定是叶子节点,这样就做完了。

#include <cstdio>
#include <stack>
#include <string>
#include <vector>
using namespace std;

char S[501];

struct Solution {
    int N, M, K;
    vector<string> A;
    vector<pair<int, int> > DFSOrder, Connect4;
    
    Solution() {
        Connect4.push_back(make_pair(1, 0));
        Connect4.push_back(make_pair(0, 1));
        Connect4.push_back(make_pair(-1, 0));
        Connect4.push_back(make_pair(0, -1));
    }
    
    void AnyFreeCell(int* x, int* y) {
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < M; ++j) {
                if (A[i][j] == '.') {
                    *x = i;
                    *y = j;
                    return;
                }
            }
        }
    }
    
    void DFS(int x, int y) {
        stack<pair<int, int> > stack0;
        stack0.push(make_pair(x, y));
        A[x][y] = 'V';
        while (!stack0.empty()) {
            int x = stack0.top().first;
            int y = stack0.top().second;
            stack0.pop();
            DFSOrder.push_back(make_pair(x, y));
            for (int i = 0; i < 4; ++i) {
                int dx = Connect4[i].first;
                int dy = Connect4[i].second;
                int newX = x + dx;
                int newY = y + dy;
                if (newX < 0 || N <= newX) continue;
                if (newY < 0 || M <= newY) continue;
                if (A[newX][newY] == '.') {
                    A[newX][newY] = 'V';
                    stack0.push(make_pair(newX, newY));
                }
            }
        }
    }
    
    void Solve() {
        scanf("%d%d%d", &N, &M, &K);
        A.resize(N);
        for (int i = 0; i < N; ++i) {
            scanf("%s", S);
            A[i] = S;
        }
        int x, y;
        AnyFreeCell(&x, &y);
        DFS(x, y);
        for (int i = 0; i < K; ++i) {
            x = DFSOrder[DFSOrder.size() - 1 - i].first;
            y = DFSOrder[DFSOrder.size() - 1 - i].second;
            A[x][y] = 'X';
        }
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < M; ++j) {
                if (A[i][j] == 'V') A[i][j] = '.';
            }
            printf("%s\n", A[i].c_str());
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

17. (414B) Mashmokh and ACM

容易想到动态规划,令 f(\text{start},\text{length}) 表示从 \text{start} 开始,长度为 \text{length} 有多少种。则

f(\text{start},\text{length})=\sum_{k|\text{start}} f(k,\text{length} - 1)

直接计算的话需要枚举约数,不如直接拿出上一个阶段的每个目标函数,看看能更新哪些目标函数,结论是能更新所有 k 的倍数,这样就方便了。还可以使用滚动数组进一步优化,注意到每个值只会更新 \text{start} 更大的值,因此从大到小更新可以直接在原数组上更改,更方便。

#include <cstdio>
#include <vector>
using namespace std;

const int M = 1000000007;

struct Solution {
    int N, K;
    vector<int> F;
    
    void Solve() {
        scanf("%d%d", &N, &K);
        F.resize(N + 1);
        for (int i = 0; i <= N; ++i) F[i] = 1;
        for (int i = 1; i < K; ++i) {
            for (int j = N; j >= 1; --j) {
                for (int k = j + j; k <= N; k += j) {
                    F[k] = (F[k] + F[j]) % M;
                }
            }
        }
        int answer = 0;
        for (int i = 1; i <= N; ++i) answer = (answer + F[i]) % M;
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

18. (4B) Before an Exam

先满足每天的最小值,剩下的贪心地分配即可。注意输出不要多额外的空格。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int D, SumTime;
    vector<int> MinTime, MaxTime;
    
    void Solve() {
        scanf("%d%d", &D, &SumTime);
        MinTime.resize(D);
        MaxTime.resize(D);
        int sumMinTime = 0, sumMaxTime = 0;
        for (int i = 0; i < D; ++i) {
            scanf("%d%d", &MinTime[i], &MaxTime[i]);
            sumMinTime += MinTime[i];
            sumMaxTime += MaxTime[i];
        }
        if (SumTime < sumMinTime || sumMaxTime < SumTime) {
            printf("NO\n");
            return;
        }
        printf("YES\n");
        bool head = true;
        SumTime -= sumMinTime;
        for (int i = 0; i < D; ++i) {
            int flexibleTime = MaxTime[i] - MinTime[i];
            int actualTime = min(SumTime, flexibleTime);
            SumTime -= actualTime;
            if (!head) putchar(' ');
            head = false;
            printf("%d", actualTime + MinTime[i]);
        }
        printf("\n");
    }
};

int main() {
    Solution().Solve();
    return 0;
}

19. (339D) Xenia and Bit Operations

最简单的一个线段树。注意这个线段树的每层的转移是不一样的,和当前层数的奇偶性有关。

#include <cstdio>
#include <vector>
using namespace std;

struct SegTree {
    int K, M;
    vector<int> Tree;
    
    SegTree(int k) {
        K = k;
        M = 1 << K;
        Tree.resize(M << 1);
    }
    
    void Compute(int p, int k) {
        if ((K - k) & 1) {
            Tree[p] = Tree[p + p] | Tree[p + p + 1];
        } else {
            Tree[p] = Tree[p + p] ^ Tree[p + p + 1];
        }
    }
    
    void Build() {
        for (int i = K - 1; i >= 0; --i) {
            for (int j = 1 << i; j < (1 << (i + 1)); ++j) Compute(j, i);
        }
    }
    
    int Set(int p, int b) {
        p += M;
        Tree[p] = b;
        for (int i = 1; i <= K; ++i) Compute(p >> i, K - i);
        return Tree[1];
    }
};

struct Solution {
    int N, M;
    
    void Solve() {
        scanf("%d%d", &N, &M);
        SegTree tree(N);
        for (int i = 0; i < (1 << N); ++i) scanf("%d", &tree.Tree[tree.M + i]);
        tree.Build();
        for (int i = 0; i < M; ++i) {
            int p, b;
            scanf("%d%d", &p, &b);
            printf("%d\n", tree.Set(p - 1, b));
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

20. (166E) Tetrahedron

f(n) 为走 n 步返回原点的方案数,g(n) 为走 n 步没有返回原点的方案数。则有

\begin{align*}
f(n) &= g(n-1) \\
g(n) &= 3f(n-1)+2g(n-1) \\
\end{align*}

整理得 f(n)=2f(n-1)+3f(n-2),就是动态规划了。如果时限卡更紧,可以数学解通项或者矩阵快速幂计算。

#include <cstdio>
using namespace std;

const int M = 1000000007;

struct Solution {
    int N;
    
    void Solve() {
        scanf("%d", &N);
        long long a0 = 1, a1 = 0;
        for (int i = 2; i <= N; ++i) {
            long long a2 = (2 * a1 + 3 * a0) % M;
            a0 = a1;
            a1 = a2;
        }
        printf("%lld\n", a1);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

21. (277A) Learning Languages

把每个人, 每种语言都当做点,这就是一个二分图,求连通分量个数。需要注意的是,如果所有人都什么语言都不会,是个特殊情况,需要特殊判断。

#include <cstdio>
#include <stack>
#include <vector>
using namespace std;

struct Solution {
    int N, M;
    vector<vector<int> > Out;
    vector<bool> Visited;
    
    void DFS(int no) {
        stack<int> stack0;
        stack0.push(no);
        Visited[no] = true;
        while (!stack0.empty()) {
            int n = stack0.top();
            stack0.pop();
            for (int i = 0; i < Out[n].size(); ++i) {
                int target = Out[n][i];
                if (Visited[target]) continue;
                Visited[target] = true;
                stack0.push(target);
            }
        }
    }
    
    bool NoneKnown() {
        for (int i = 0; i < N; ++i) {
            if (0 < Out[i].size()) return false;
        }
        return true;
    }
    
    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N + M);
        for (int i = 0; i < N; ++i) {
            int k;
            scanf("%d", &k);
            for (int j = 0; j < k; ++j) {
                int language;
                scanf("%d", &language);
                Out[i].push_back(N + language - 1);
                Out[N + language - 1].push_back(i);
            }
        }
        if (NoneKnown()) {
            printf("%d\n", N);
        } else {
            Visited.resize(Out.size());
            int answer = 0;
            for (int i = 0; i < N; ++i) {
                if (Visited[i]) continue;
                ++answer;
                DFS(i);
            }
            printf("%d\n", answer - 1);
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

22. (478C) Table Decorations

如果 r > 2g + 2b,多出的部分是不能用的。反之一定能找到方案,使得总气球除以 3 取整就是答案。证明可能比较复杂,此处不再展开,但结论是直观的。

#include <cstdio>
using namespace std;

bool AssignMin(long long* p, long long v) {
    if (v < *p) return *p = v, true;
    return false;
}

struct Solution {
    long long R, G, B;
    
    void Solve() {
        scanf("%lld%lld%lld", &R, &G, &B);
        AssignMin(&R, G + G + B + B);
        AssignMin(&G, B + B + R + R);
        AssignMin(&B, R + R + G + G);
        printf("%lld\n", (R + G + B) / 3);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

23. (650A) Watchmen

欧氏距离等于曼哈顿距离,则两个点的连线与坐标轴平行。注意相同的两个点可能会被计算两次。这里使用模板来简化程序。

#include <cstdio>
#include <map>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<pair<int, int>> A;

    template <class T, class Get>
    long long Compute(Get get) {
        map<T, int> map0;
        for (int i = 0; i < A.size(); ++i) ++map0[get(A[i])];
        long long answer = 0;
        for (
            typename map<T, int>::iterator it = map0.begin();
            it != map0.end();
            ++it
        ) answer += (long long)it->second * (it->second - 1) / 2;
        return answer;
    }

    static int GetFirst(const pair<int, int>& p) { return p.first; }
    static int GetSecond(const pair<int, int>& p) { return p.second; }
    static const pair<int, int>& GetWhole(const pair<int, int>& p) { return p; }

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) {
            int x, y;
            scanf("%d%d", &x, &y);
            A[i] = make_pair(x, y);
        }
        long long A1 = Compute<int>(GetFirst);
        long long A2 = Compute<int>(GetSecond);
        long long A3 = Compute<pair<int, int> >(GetWhole);
        printf("%lld\n", A1 + A2 - A3);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

24. (276C) Little Girl and Maximum Sum

重点计算每个数组中的数被累加了多少次,然后使用排序不等式即可。这里不需要用树状数组或线段树,只要计算原数组差分以后的值就行。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N, Q;
    vector<int> A, B;

    void Solve() {
        scanf("%d%d", &N, &Q);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        B.resize(N + 2);
        for (int i = 0; i < Q; ++i) {
            int left, right;
            scanf("%d%d", &left, &right);
            ++B[left];
            --B[right + 1];
        }
        for (int i = 0; i <= N; ++i) B[i + 1] += B[i];
        sort(A.begin(), A.end());
        sort(B.begin(), B.end());
        long long answer = 0;
        for (int i = 0; i < N; ++i) {
            answer += (long long)A[i] * B[i + 2];
        }
        printf("%lld\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

25. (550B) Preparing Olympiad

递归穷举搜索,记录当前的最大值,最小值,和,已经有多少数。这个题 N 比较小,而数据范围比较大,因此无法使用动态规划。

#include <algorithm>
#include <climits>
#include <cstdio>
#include <vector>
using namespace std;

bool AssignMin(int* p, int v) {
    if (v < *p) return *p = v, true;
    return false;
}

bool AssignMax(int* p, int v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Solution {
    int N, L, R, X, Answer;
    vector<int> C;

    void Put(int no, int num, int sum, int minimum, int maximum) {
        if (num + N - no < 2) return;
        if (no == N) {
            if (X <= maximum - minimum && L <= sum && sum <= R) ++Answer;
            return;
        }
        Put(no + 1, num, sum, minimum, maximum);
        AssignMin(&minimum, C[no]);
        AssignMax(&maximum, C[no]);
        Put(no + 1, num + 1, sum + C[no], minimum, maximum);
    }

    void Solve() {
        scanf("%d%d%d%d", &N, &L, &R, &X);
        C.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &C[i]);
        Answer = 0;
        Put(0, 0, 0, INT_MAX, INT_MIN);
        printf("%d\n", Answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

26. (687A) NP-Hard Problem

其实就是判断原图是不是一个二分图,使用 DFS 判断即可。

#include <cstdio>
#include <stack>
#include <vector>
using namespace std;

struct Solution {
    int N, M;
    vector<vector<int> > Out;
    vector<int> A[2], Visited;

    bool DFS(int no, int part) {
        stack<int> stack0;
        Visited[no] = part;
        stack0.push(no);
        while (!stack0.empty()) {
            no = stack0.top();
            part = Visited[no];
            A[part - 1].push_back(no);
            stack0.pop();
            for (int i = 0; i < Out[no].size(); ++i) {
                int target = Out[no][i];
                if (Visited[target] == 0) {
                    Visited[target] = 3 - part;
                    stack0.push(target);
                } else if (Visited[target] != 3 - part) {
                    return false;
                }
            }
        }
        return true;
    }

    bool Bipartite() {
        Visited.resize(N);
        for (int i = 0; i < N; ++i) {
            if (!Visited[i] && !DFS(i, 1)) return false;
        }
        return true;
    }

    void Print(const vector<int>& a) {
        printf("%d\n", a.size());
        bool head = true;
        for (int i = 0; i < a.size(); ++i) {
            if (!head) putchar(' ');
            head = false;
            printf("%d", a[i] + 1);
        }
        printf("\n");
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            Out[u - 1].push_back(v - 1);
            Out[v - 1].push_back(u - 1);
        }
        if (Bipartite()) {
            Print(A[0]);
            Print(A[1]);
        } else {
            printf("-1\n");
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

27. (706C) Hard problem

S_i 为第 i 个串,R_i 为第 i 个串翻转。f(i, \text{flip}) 为前 i 个串有序所得到的最小权值,其中第 i 个串在 \text{flip}=0 时不翻转,在 \text{flip}=1 时翻转。状态转移如下,

\begin{align*}
f(i, 0) &= \min\cases{
    f(i - 1, 0)       &if $S_{i-1} \le S_i$ \\
    f(i - 1, 1)       &if $R_{i-1} \le S_i$ \\
} \\
f(i, 1) &= \min\cases{
    f(i - 1, 0) + C_i &if $S_{i-1} \le R_i$ \\
    f(i - 1, 1) + C_i &if $R_{i-1} \le R_i$ \\
} \\
\end{align*}
#include <algorithm>
#include <cstdio>
#include <climits>
#include <string>
#include <vector>
using namespace std;

char Buffer[100001];

bool AssignMin(long long* p, long long v) {
    if (v < *p) return *p = v, true;
    return false;
}

struct Solution {
    int N;
    vector<long long> C, F[2];
    vector<string> S, R;

    void Solve() {
        scanf("%d", &N);
        C.resize(N);
        for (int i = 0; i < N; ++i) scanf("%lld", &C[i]);
        S.resize(N);
        R.resize(N);
        for (int i = 0; i < N; ++i) {
            scanf("%s", Buffer);
            S[i] = Buffer;
            R[i] = Buffer;
            reverse(R[i].begin(), R[i].end());
        }
        for (int i = 0; i < 2; ++i) F[i].resize(N);
        F[0][0] = 0;
        F[1][0] = C[0];
        for (int i = 1; i < N; ++i) {
            F[0][i] = F[1][i] = LONG_LONG_MAX >> 1;
            if (S[i - 1] <= S[i]) AssignMin(&F[0][i], F[0][i - 1]);
            if (S[i - 1] <= R[i]) AssignMin(&F[1][i], F[0][i - 1] + C[i]);
            if (R[i - 1] <= S[i]) AssignMin(&F[0][i], F[1][i - 1]);
            if (R[i - 1] <= R[i]) AssignMin(&F[1][i], F[1][i - 1] + C[i]);
        }
        long long answer = min(F[0][N - 1], F[1][N - 1]);
        printf("%lld\n", answer == LONG_LONG_MAX >> 1 ? -1 : answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

28. (576A) Vasya and Petya's Game

只需判断所有素数的方幂即可断定一个数。反之可以用抽屉原理证明,比较困难,此处不展开。

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> AllPrimes, Answer;
    vector<bool> Composite;

    void Generate() {
        Composite.resize(N + 1);
        for (int i = 2; i <= N; ++i) {
            if (!Composite[i]) {
                AllPrimes.push_back(i);
                for (long long j = (long long)i * i; j <= N; j += i) {
                    Composite[j] = true;
                }
            }
        }
    }

    void Solve() {
        scanf("%d", &N);
        Generate();
        for (int i = 0; i < AllPrimes.size(); ++i) {
            for (int j = AllPrimes[i]; j <= N; j *= AllPrimes[i]) {
                Answer.push_back(j);
            }
        }
        printf("%d\n", Answer.size());
        bool head = true;
        for (int i = 0; i < Answer.size(); ++i) {
            if (!head) putchar(' ');
            head = false;
            printf("%d", Answer[i]);
        }
        printf("\n");
    }
};

int main() {
    Solution().Solve();
    return 0;
}

29. (676C) Vasya and String

计算每个点结尾的最大长度即可,令 i 表示这个最大长度从何处开始,我们可以证明 i 是单调增的。因此得到了一个 O(n) 的算法。

#include <algorithm>
#include <cstdio>
#include <string>
using namespace std;

char Buffer[100001];

bool AssignMax(int* p, int v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Solution {
    int N, K;
    string S;

    int Maximize(char a) {
        int p = 0, q = 0, b = 0, answer = 0;
        while (q < N) {
            if (S[q++] != a) ++b;
            while (K < b) {
                if (S[p++] != a) --b;
            }
            AssignMax(&answer, q - p);
        }
        return answer;
    }

    void Solve() {
        scanf("%d%d", &N, &K);
        scanf("%s", Buffer);
        S = Buffer;
        printf("%d\n", max(Maximize('a'), Maximize('b')));
    }
};

int main() {
    Solution().Solve();
    return 0;
}

30. (559A) Gerald's Hexagon

计算多边形面积,用向量的叉积就可以了。用向量法可以得到每个点的坐标。

#include <cstdio>
#include <complex>
#include <math.h>
using namespace std;

const double PI = acos(0.0) * 2.0;

typedef complex<double> Point;

double Cross(Point a1, Point a2) {
    return a1.real() * a2.imag() - a1.imag() * a2.real();
}

struct Solution {
    int A[6];
    Point P[6];

    double Area() {
        double answer = 0.0;
        for (int i = 0; i < 6; ++i) {
            Point a1 = P[i];
            Point a2 = i == 5 ? P[0] : P[i + 1];
            answer += Cross(a1, a2);
        }
        return answer;
    }

    void Solve() {
        scanf("%d%d%d%d%d%d", &A[0], &A[1], &A[2], &A[3], &A[4], &A[5]);
        for (int i = 0; i < 6; ++i) {
            P[i] = polar((double)A[i], PI / 3.0 * i);
        }
        Point last;
        for (int i = 0; i < 6; ++i) {
            last = P[i] += last;
        }
        printf("%.0lf\n", Area() / (sin(PI / 3.0)));
    }
};

int main() {
    Solution().Solve();
    return 0;
}

31. (437C) The Child and Toy

每条边恰被删去一次,能否保证删去时所付出的权值是小的那边呢?很简单,只要从大到小删就可以了。需要按照权值大小对序号进行排序,写自定义比较器即可。

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

struct Compare {
    const vector<int>* V;

    Compare(const vector<int>& v) { V = &v; }

    bool operator()(int i1, int i2) {
        return V->at(i2) < V->at(i1);
    }
};

struct Solution {
    int N, M;
    vector<int> V, No;
    vector<vector<int> > Out;
    vector<bool> Removed;

    void Solve() {
        scanf("%d%d", &N, &M);
        V.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &V[i]);
        Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            Out[u - 1].push_back(v - 1);
            Out[v - 1].push_back(u - 1);
        }
        No.resize(N);
        for (int i = 0; i < N; ++i) No[i] = i;
        sort(No.begin(), No.end(), Compare(V));
        Removed.resize(N);
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < Out[No[i]].size(); ++j) {
                int target = Out[No[i]][j];
                if (Removed[target]) continue;
                answer += V[target];
            }
            Removed[No[i]] = true;
        }
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

32. (762A) k-th divisor

可以用 \sqrt n 时间算出 n 的所有约数,再从其中找出第 k 大的数。注意,对 n 是完全平方数的情况,不要重复计数 \sqrt n

#include <cstdio>
#include <vector>
using namespace std;

typedef long long int64;

vector<int64> Divisors(int64 n) {
    vector<int64> answer1, answer2;
    int64 i = 1;
    for (; i * i < n; ++i) {
        if (n % i == 0) {
            answer1.push_back(i);
            answer2.push_back(n / i);
        }
    }
    if (i * i == n) answer1.push_back(i);
    for (int i = answer2.size() - 1; i >= 0; --i) {
        answer1.push_back(answer2[i]);
    }
    return answer1;
}

struct Solution {
    int64 N;
    int K;

    void Solve() {
        scanf("%lld%d", &N, &K);
        vector<int64> divisors = Divisors(N);
        printf("%lld\n", K <= divisors.size() ? divisors[K - 1] : -1);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

33. (268C) Beautiful Sets of Points

容易想到用一条 45^\circ 的直线上的点,但是从 (0,0) 出发是不可以的,因此从长方形另外的顶点出发。

#include <algorithm>
#include <cstdio>
using namespace std;

struct Solution {
    int N, M;

    void Solve() {
        scanf("%d%d", &N, &M);
        int k = min(N, M);
        printf("%d\n", k + 1);
        for (int i = 0; i <= k; ++i) printf("%d %d\n", N - i, i);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

34. (446A) DZY Loves Sequences

动态规划。令 f(k) 为以第 k 个数结尾最长上升列的长度,g(k) 为以第 k 个数结尾最长上升列,允许一次修改(不能修改首尾的数)的长度。则

f(k)=\cases{
	f(k - 1) + 1, &if $a_k > a_{k-1};$  \\
    1, &otherwise;  \\
}
\qquad
g(k)=\max\cases{
    g(k - 1) + 1, &if $a_k > a_{k-1};$  \\
    f(k - 2) + 2, &if $a_k > a_{k-2} + 1;$
}

则答案就是

\min\Big\{\max\big\{\max_{k} (g_k), \max_{k} (f_k + 1)\big\}, n\Big\};
#include <cstdio>
#include <vector>
using namespace std;

bool AssignMax(int* p, int v) {
    if (*p < v) return *p = v, true;
    return false;
}

bool AssignMin(int* p, int v) {
    if (v < *p) return *p = v, true;
    return false;
}

struct Solution {
    int N;
    vector<int> A;

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        int f0 = 1, f1 = A[1] > A[0] ? 2 : 1;
        int g0 = 1, g1 = 2;
        int answer = 2;
        for (int i = 2; i < N; ++i) {
            int f2 = 1, g2 = 1;
            if (A[i - 1] < A[i]) f2 = f1 + 1;
            if (A[i - 1] < A[i]) g2 = g1 + 1;
            if (A[i - 2] + 1 < A[i]) AssignMax(&g2, f0 + 2);
            AssignMax(&answer, f2 + 1);
            AssignMax(&answer, g2);
            f0 = f1, f1 = f2;
            g0 = g1, g1 = g2;
        }
        AssignMin(&answer, N);
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

35. (510B) Fox And Two Dots

使用一次 DFS 即可,如果有指向已经访问过的点的边,就是有环。

#include <cstdio>
#include <stack>
#include <string>
#include <vector>
using namespace std;

char Buffer[51];

int DeltaX[] = {1, 0, -1, 0};
int DeltaY[] = {0, 1, 0, -1};

struct Node {
    int X, Y, lastX, lastY;
};

struct Solution {
    int N, M;
    vector<string> A;
    bool Cycle;

    void DFS(int x, int y) {
        char c = A[x][y];
        A[x][y] = '.';
        stack<Node> stack0;
        Node node = {x, y, -1, -1};
        stack0.push(node);
        while (!stack0.empty()) {
            Node node = stack0.top();
            stack0.pop();
            A[node.X][node.Y] = '.';
            for (int i = 0; i < 4; ++i) {
                int newX = node.X + DeltaX[i];
                int newY = node.Y + DeltaY[i];
                if (newX < 0 || N <= newX || newY < 0 || M <= newY) continue;
                if (newX == node.lastX && newY == node.lastY) continue;
                if (A[newX][newY] == '.') Cycle = true;
                if (A[newX][newY] == c) {
                    A[newX][newY] = '.';
                    Node newNode = {newX, newY, node.X, node.Y};
                    stack0.push(newNode);
                }
            }
            A[node.X][node.Y] = '#';
        }
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        A.resize(N);
        for (int i = 0; i < N; ++i) {
            scanf("%s", Buffer);
            A[i] = Buffer;
        }
        Cycle = false;
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < M; ++j) {
                if (A[i][j] != '#') DFS(i, j);
            }
        }
        printf("%s\n", Cycle ? "Yes" : "No");
    }
};

int main() {
    Solution().Solve();
    return 0;
}

36. (522A) Reposts

字典,累加。用不着建图:输入就是按照转发顺序来的,因此直接依次计算就好了。

#include <cctype>
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
using namespace std;

char Buffer1[25], Buffer2[25];

bool AssignMax(int *p, int v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Solution {
    map<string, int> Length;
    int N;

    string Lower(const char* s) {
        string answer;
        answer.reserve(strlen(s));
        for (int i = 0; s[i]; ++i) answer += char(tolower(s[i]));
        return answer;
    }

    void Solve() {
        Length["polycarp"] = 1;
        scanf("%d", &N);
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            scanf("%s reposted %s", Buffer1, Buffer2);
            string s1 = Lower(Buffer1);
            string s2 = Lower(Buffer2);
            int length = Length[s1] = Length[s2] + 1;
            AssignMax(&answer, length);
        }
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

37. (707C) Pythagorean Triples

若输入不是 2 的方幂,那么去掉质因数 2 的数作为一条直角边即可。如果输入是 2 的方幂,只要 \ge 4,其平方可以分解为 2 和另一个偶数的乘积,分别作为 c-bc+b 即可。只有 12 不能构成勾股三角形。

#include <cstdio>
using namespace std;

typedef long long int64;

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        if (N == 1 || N == 2) {
            printf("-1\n");
        } else {
            int lowbit = N & -N;
            N /= lowbit;
            if (N == 1) {
                int64 sum = int64(lowbit) * lowbit / 2;
                int64 difference = 2;
                printf("%lld %lld\n",
                    (sum + difference) / 2, (sum - difference) / 2);
            } else {
                int64 sum = int64(N) * N;
                int64 difference = 1;
                printf("%lld %lld\n",
                    (sum + difference) / 2 * lowbit,
                    (sum - difference) / 2 * lowbit);
            }
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

38. (888A) Local Extrema

直接暴力求解。

#include <cstdio>
#include <vector>
using namespace std;

struct Solution {
    int N;
    vector<int> A;

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        int answer = 0;
        for (int i = 1; i < N - 1; ++i) {
            if (A[i - 1] < A[i] && A[i + 1] < A[i]) ++answer;
            if (A[i] < A[i - 1] && A[i] < A[i + 1]) ++answer;
        }
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

39. (118D) Caesar's Legions

f(n_1,n_2,\text{last}) 为方案数。则

f(n_1,n_2,\text{last})= \cases{
    \sum_{i=1}^{k_1}f(n_1-i,n_2,2), &if $\text{last}=1$;  \\
    \sum_{i=1}^{k_2}f(n_1,n_2-i,1), &if $\text{last}=2$;  \\
}
#include <cstdio>
#include <vector>
using namespace std;

const int M = 100000000;

struct Solution {
    int N1, N2, K1, K2;
    vector<vector<int> > F1, F2;

    void Solve() {
        scanf("%d%d%d%d", &N1, &N2, &K1, &K2);
        F1.resize(N1 + 1);
        F2.resize(N1 + 1);
        for (int i = 0; i <= N1; ++i) {
            F1[i].resize(N2 + 1);
            F2[i].resize(N2 + 1);
            if (i == 0) F1[i][0] = F2[i][0] = 1;
            for (int j = 0; j <= N2; ++j) {
                for (int k = 1; k <= K1; ++k) {
                    if (i - k < 0) continue;
                    F1[i][j] = (F1[i][j] + F2[i - k][j]) % M;
                }
                for (int k = 1; k <= K2; ++k) {
                    if (j - k < 0) continue;
                    F2[i][j] = (F2[i][j] + F1[i][j - k]) % M;
                }
            }
        }
        printf("%d\n", (F1[N1][N2] + F2[N1][N2]) % M);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

40. (27A) Next Test

用 set 存储数,然后暴力枚举即可。

#include <cstdio>
#include <set>
using namespace std;

struct Solution {
    int N;
    set<int> A;

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            int v;
            scanf("%d", &v);
            A.insert(v);
        }
        int answer = 1;
        while (A.count(answer)) ++answer;
        printf("%d\n", answer);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

41. (467C) George and Job

f(\text{end}, \text{segments}) 为到 \text{end} 结束,取 \text{segments} 段最大的和,则答案是 f(n,k)。有状态转移

f(\text{end}, \text{segments}) = 
    \max_{M\le k\le \text{end}} \bigg[
        \bigg(\sum_{i=k-M}^{k-1} A_i \bigg) +
        f(k-M,\text{segments} - 1)
    \bigg]

这样得到一个 O(N^2K) 的算法。重新想一个状态转移

f(\text{end}, \text{segments}) = 
    \max \cases{
    	f(\text{end} - 1, \text{segments}), \\
    	\Big(\sum_{k=\text{end}-M}^{\text{end} - 1}A_i\Big) + 
    	    f(\text{end}-M,\text{segments} - 1)
    }

这样得到一个 O(NK) 的算法。

#include <cstdio>
#include <vector>
using namespace std;

typedef long long int64;

bool AssignMax(int64* p, int64 v) {
    if (*p < v) return *p = v, true;
    return false;
}

struct Solution {
    int N, M, K;
    vector<int64> A;
    vector<vector<int64> > F;

    void Solve() {
        scanf("%d%d%d", &N, &M, &K);
        A.resize(N + 1);
        for (int i = 1; i <= N; ++i) scanf("%lld", &A[i]);
        for (int i = 1; i <= N; ++i) A[i] += A[i - 1];
        F.resize(N + 1);
        F[0].resize(K + 1);
        for (int i = 1; i <= N; ++i) {
            F[i].resize(K + 1);
            for (int j = 1; j <= K; ++j) {
                AssignMax(&F[i][j], F[i - 1][j]);
                if (M <= i) {
                    AssignMax(&F[i][j], A[i] - A[i - M] + F[i - M][j - 1]);
                }
            }
        }
        printf("%lld\n", F[N][K]);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

42. (584B) Kolya and Tanya

总数减不满意的方案数即可:27^n-7^n。快速幂取模。

#include <cstdio>
using namespace std;

typedef long long int64;

const int M = 1000000007;

int PowerMod(int a, int b, int m) {
    int answer = 1;
    while (b != 0) {
        if ((b & 1) != 0) answer = int64(answer) * a % m;
        a = int64(a) * a % m;
        b >>= 1;
    }
    return answer;
}

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        int a1 = PowerMod(27, N, M);
        int a2 = PowerMod(7, N, M);
        printf("%d\n", (a1 + M - a2) % M);
    }
};

int main() {
    Solution().Solve();
    return 0;
}

43. (235A) LCM Challenge

如果数较小,可以手工计算。如果数较大时,对于奇数 n(n-1)(n-2) 即可,对于偶数,如果是 3 的倍数,(n-1)(n-2)(n-3),如果不是,n(n-1)(n-3)

#include <cstdio>
using namespace std;

typedef long long int64;

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        if (N == 1) {
            printf("1\n");
        } else if (N == 2) {
            printf("2\n");
        } else if (N == 3) {
            printf("6\n");
        } else if (N == 4) {
            printf("12\n");
        } else if (N % 2 != 0) {
            printf("%lld\n", int64(N) * (N - 1) * (N - 2));
        } else if (N % 3 != 0) {
            printf("%lld\n", int64(N) * (N - 1) * (N - 3));
        } else {
            printf("%lld\n", int64(N - 1) * (N - 2) * (N - 3));
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

44. (510C) Fox And Names

题目给出一些要求,比如某字母必须排在另一个字母的前面等等,要求给出所有字母的排列。这是一个拓扑排序问题,可以用 DFS 完成。

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
using namespace std;

char Buffer[101];

struct Solution {
    int N;
    string Last, Current, Answer;
    vector<vector<bool> > Less;
    vector<int> Visited;
    bool Failure;

    bool AddEdge() {
        for (int i = 0; ; ++i) {
            if (i == Last.size()) return true;
            if (i == Current.size()) return false;
            if (Last[i] != Current[i]) {
                Less[Last[i] - 'a'][Current[i] - 'a'] = true;
                return true;
            }
        }
    }

    bool DFS(int no) {
        Visited[no] = 1;
        for (int i = 0; i < 26; ++i) {
            if (!Less[no][i]) continue;
            if (Visited[i] == 1) return false;
            if (Visited[i] == 0 && !DFS(i)) return false;
        }
        Visited[no] = 2;
        Answer += char('a' + no);
        return true;
    }

    bool TopoSort() {
        Visited.resize(26);
        for (int i = 0; i < 26; ++i) {
            if (Visited[i] == 0 && !DFS(i)) return false;
        }
        reverse(Answer.begin(), Answer.end());
        return Answer.size() == 26;
    }

    void Solve() {
        Less.resize(26);
        for (int i = 0; i < 26; ++i) Less[i].resize(26);
        scanf("%d", &N);
        Failure = false;
        for (int i = 0; i < N; ++i) {
            scanf("%s", Buffer);
            Current = Buffer;
            if (!AddEdge()) Failure = true;
            Last = Current;
        }
        if (Failure || !TopoSort()) {
            printf("Impossible\n");
        } else {
            printf("%s\n", Answer.c_str());
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

45. (839C) Journey

树上 DP,计算每个点出发向叶子方向路径长度的期望即可。

#include <cstdio>
#include <vector>
using namespace std;
 
struct Solution {
    int N;
    vector<vector<int> > G;
 
    double ExpectLength(int no, int parent) {
        double total = 0.0;
        int leaves = 0;
        for (int i = 0; i < G[no].size(); ++i) {
            if (G[no][i] == parent) continue;
            ++leaves;
            total += ExpectLength(G[no][i], no) + 1.0;
        }
        if (leaves == 0) return 0;
        return total / leaves;
    }
 
    void Solve() {
        scanf("%d", &N);
        G.resize(N);
        for (int i = 1; i < N; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            G[v1 - 1].push_back(v2 - 1);
            G[v2 - 1].push_back(v1 - 1);
        }
        printf("%.12f\n", ExpectLength(0, -1));
    }
};
 
int main() {
    Solution().Solve();
    return 0;
}

46. (735D) Taxes

从分析样例开始。27 只收 3 卢布的税,一定是拆成了 3 个质数。想到任何奇数都能拆成 3 个质数之和,而偶数都能拆成 2 个质数之和(Goldbach 猜想),答案必定不超过 3。剩下就是讨论了。

#include <cstdio>
using namespace std;

bool Prime(int n) {
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) return false;
    }
    return true;
}

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        if (Prime(N)) {
            printf("1\n");
        } else if (N % 2 == 0 || Prime(N - 2)) {
            printf("2\n");
        } else {
            printf("3\n");
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

47. (570C) Replacement

其实就是问有几个“..”。直接在原序列上维护,每次修改序列之前,把曾经有的“..”从答案中删除掉,然后修改之后把新的添上去。

#include <cstdio>
#include <string>
using namespace std;

char Buffer[300001];

struct Solution {
    int N, M;
    string S;

    void Solve() {
        scanf("%d%d", &N, &M);
        scanf("%s", Buffer);
        S = Buffer;
        int answer = 0;
        for (int i = 0; i < N - 1; ++i) {
            if (S[i] == '.' && S[i + 1] == '.') ++answer;
        }
        for (int i = 0; i < M; ++i) {
            int place;
            char newChar;
            scanf("%d %c", &place, &newChar);
            --place;
            if (place + 1 < N && S[place] == '.' && S[place + 1] == '.') {
                --answer;
            }
            if (0 <= place - 1 && S[place - 1] == '.' && S[place] == '.') {
                --answer;
            }
            S[place] = newChar;
            if (place + 1 < N && S[place] == '.' && S[place + 1] == '.') {
                ++answer;
            }
            if (0 <= place - 1 && S[place - 1] == '.' && S[place] == '.') {
                ++answer;
            }
            printf("%d\n", answer);
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

48. (460B) Little Dima and Equation

这题目 x 范围虽然大,但 s(x) 范围不大。枚举 s(x) 的值即可。

#include <cstdio>
#include <vector>
using namespace std;

const int M = 1000000000;

struct Solution {
    int A, B, C;

    int DigitSum(int x) {
        int answer = 0;
        while (x != 0) {
            answer += x % 10;
            x /= 10;
        }
        return answer;
    }

    void Solve() {
        scanf("%d%d%d", &A, &B, &C);
        vector<int> answer;
        for (int i = 1; i <= 81; ++i) {
            long long x = B;
            for (int j = 0; j < A; ++j) x *= i;
            x += C;
            if (x < M && DigitSum(x) == i) answer.push_back(x);
        }
        printf("%d\n", answer.size());
        if (!answer.empty()) {
            bool head = true;
            for (int i = 0; i < answer.size(); ++i) {
                if (!head) putchar(' ');
                head = false;
                printf("%d", answer[i]);
            }
            printf("\n");
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}

49. (771A) Bear and Friendship Condition

其实就是要求图是若干个不联通的团。判断是否是团,只需计数总边数为 n(n-1) 就可以了。

#include <cstdio>
#include <vector>
using namespace std;

typedef long long int64;

struct Solution {
    int N, M;
    vector<vector<int> > Out;
    vector<bool> Visited;

    void DFS(int no, int* nodes, int* edges) {
        Visited[no] = true;
        ++*nodes;
        for (int i = 0; i < Out[no].size(); ++i) {
            ++*edges;
            int target = Out[no][i];
            if (Visited[target]) continue;
            DFS(target, nodes, edges);
        }
    }

    bool Reasonable() {
        Visited.resize(N + 1);
        for (int i = 1; i < N; ++i) {
            if (Visited[i]) continue;
            int nodes = 0, edges = 0;
            DFS(i, &nodes, &edges);
            if (int64(nodes) * (nodes - 1) != edges) return false;
        }
        return true;
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N + 1);
        for (int i = 0; i < M; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1].push_back(v2);
            Out[v2].push_back(v1);
        }
        printf(Reasonable() ? "YES\n" : "NO\n");
    }
};

int main() {
    Solution().Solve();
    return 0;
}

50. (349B) Color the Fence

贪心法,首先选择费用最小的使得总长度最大,然后在保证总长度最大的前提下,选字典序最大的解。

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
using namespace std;

struct Solution {
    int V, MinA, Length;
    vector<int> A;

    string ComputeAnswer() {
        string answer;
        int exceed = V - Length * MinA;
        for (int i = 9; ; --i) {
            while (A[i] - MinA <= exceed) {
                exceed -= A[i] - MinA;
                answer += char('0' + i);
                if (answer.size() == Length) return answer;
            }
        }
    }

    void Solve() {
        scanf("%d", &V);
        A.resize(10);
        for (int i = 1; i < 10; ++i) scanf("%d", &A[i]);
        MinA = *min_element(A.begin() + 1, A.end());
        Length = V / MinA;
        if (Length == 0) {
            printf("-1\n");
        } else {
            printf("%s\n", ComputeAnswer().c_str());
        }
    }
};

int main() {
    Solution().Solve();
    return 0;
}