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

897 阅读31分钟

51. (126B) Password

在串上跑 KMP 算法,记得到的数组为 \text{next}

首先若 \text{next}[n] = 0,说明无解。

若存在 \text{next}[i] = \text{next}[n],说明 \text{next}[n] 就是解。

再看 \text{next}[\text{next}[n]],如果为零,说明无解。原因是如果它为零,找不到更短的字符串使得同时为前缀后缀。

如果不为零,这个串出现了至少四次,在开头的开头,开头的结尾,结尾的开头,结尾的结尾:就是答案。

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

char Buffer[1000001];

struct Solution {
    string S;
    vector<int> Next;

    int GetAnswer() {
        if (Next[S.size()] == 0) return 0;
        for (int i = 0; i < S.size(); ++i) {
            if (Next[i] == Next[S.size()]) return Next[S.size()];
        }
        if (Next[Next[S.size()]] == 0) return 0;
        return Next[Next[S.size()]];
    }

    void Solve() {
        scanf("%s", Buffer);
        S = Buffer;
        Next.resize(S.size() + 1);
        int k = Next[0] = -1;
        for (int i = 0; i < S.size(); ++i) {
            while (k >= 0 && S[k] != S[i]) k = Next[k];
            Next[i + 1] = ++k;
        }
        int answer = GetAnswer();
        if (answer == 0) {
            printf("Just a legend\n");
        } else {
            printf("%s\n", S.substr(0, answer).c_str());
        }
    }
};

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

52. (701C) They Are Everywhere

用两个指针的经典方法,维护不同元素的个数即可。

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

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

char Buffer[100001];

struct Bag {
    vector<int> A;
    int NonZero;

    Bag() {
        A.resize(256);
        NonZero = 0;
    }

    void Add(char c) {
        if (A[c]++ == 0) ++NonZero;
    }

    void Remove(char c) {
        if (--A[c] == 0) --NonZero;
    }
};

struct Solution {
    int N;
    string S;

    void Solve() {
        scanf("%d %s", &N, Buffer);
        S = Buffer;
        Bag bag, bag0;
        for (int i = 0; i < S.size(); ++i) bag.Add(S[i]);
        int different = bag.NonZero;
        int p = 0, q = 0, answer = INT_MAX;
        while (true) {
            if (bag0.NonZero < different) {
                if (q == S.size()) break;
                bag0.Add(S[q++]);
            } else {
                AssignMin(&answer, q - p);
                bag0.Remove(S[p++]);
            }
        }
        printf("%d\n", answer);
    }
};

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

53. (493B) Vasya and Wrestling

模拟题,注意简化程序。

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

typedef long long int64;

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

    int64 Sum(const vector<int>& v) {
        int64 answer = 0;
        for (int i = 0; i < v.size(); ++i) {
            answer += v[i];
        }
        return answer;
    }

    string Winner() {
        int64 sum1 = Sum(B1);
        int64 sum2 = Sum(B2);
        if (sum2 < sum1) return "first";
        if (sum1 < sum2) return "second";
        for (int i = 0; ; ++i) {
            if (B1.size() <= i && B2.size() <= i) break;
            if (B1.size() <= i) return "second";
            if (B2.size() <= i) return "first";
            if (B1[i] < B2[i]) return "second";
            if (B2[i] < B1[i]) return "first";
        }
        if (Last == 1) return "first";
        if (Last == 2) return "second";
        return string();
    }

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            int a;
            scanf("%d", &a);
            if (0 < a) {
                B1.push_back(a);
                Last = 1;
            } else {
                B2.push_back(-a);
                Last = 2;
            }
        }
        printf("%s\n", Winner().c_str());
    }
};

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

54. (348A) Mafia

首先总参与量必须合适,其次每个人的参与次数必须小于等于总比赛次数。至于为什么这样就能保证一定有方案,需要反过来想,因为当裁判的人是任意的,所以相当于给定每个人当裁判的总次数,然后要求方案。依次当够总次数即可。

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

typedef long long int64;

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]);
        int64 sum = 0;
        for (int i = 0; i < N; ++i) sum += A[i];
        int64 answer = (sum + N - 2) / (N - 1);
        int64 maxA = *max_element(A.begin(), A.end());
        printf("%lld\n", max(maxA, answer));
    }
};

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

55. (742B) Arpa’s obvious problem and Mehrdad’s terrible solution

用 map 统计每个数出现的次数即可。注意:用 multiset 会超时。

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

typedef long long int64;

struct Solution {
    int N, X;
    vector<int> A;
    map<int, int> S;

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

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

56. (518A) Vitaly and Strings

取字典序恰比第一个串大的串,然后和第二个串比较。

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

char Buffer[101];

struct Solution {
    string S1, S2, S3;

    string Inc(string s) {
        int k = s.size() - 1;
        while (k >= 0) {
            if (s[k] != 'z') {
                ++s[k];
                break;
            }
            --k;
        }
        for (int i = k + 1; i < s.size(); ++i) s[i] = 'a';
        return s;
    }

    void Solve() {
        scanf("%s", Buffer);
        S1 = Buffer;
        scanf("%s", Buffer);
        S2 = Buffer;
        S3 = Inc(S1);
        if (S3 < S2) {
            printf("%s\n", S3.c_str());
        } else {
            printf("%s\n", "No such string");
        }
    }
};

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

57. (601A) The Two Routes

首先考虑从 1n 的路,如果是汽车,那么汽车可以直接到达终点,反之火车可以直接到达终点。所以只要 BFS 算出另一种车到终点的时间就可以了。

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

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

    int BFS(int start, int end, bool rail) {
        vector<int> distance(N);
        for (int i = 0; i < N; ++i) distance[i] = INT_MAX;
        distance[start] = 0;
        queue<int> q;
        q.push(start);
        while (!q.empty()) {
            int front = q.front();
            q.pop();
            for (int i = 0; i < N; ++i) {
                if (Railway[front][i] != rail) continue;
                if (distance[i] != INT_MAX) continue;
                distance[i] = distance[front] + 1;
                q.push(i);
            }
        }
        return distance[end];
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        Railway.resize(N);
        for (int i = 0; i < N; ++i) Railway[i].resize(N);
        for (int i = 0; i < M; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Railway[v1 - 1][v2 - 1] = true;
            Railway[v2 - 1][v1 - 1] = true;
        }
        int answer1 = BFS(0, N - 1, false);
        int answer2 = BFS(0, N - 1, true);
        int answer = max(answer1, answer2);
        printf("%d\n", answer == INT_MAX ? -1 : answer);
    }
};

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

58. (743C) Vladik and fractions

写个搜索找规律得到 n,n+1,n(n+1) 三个数满足条件。

#include <cstdio>
using namespace std;

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        if (N == 1) {
            printf("-1\n");
        } else {
            printf("%d %d %d\n", N, N + 1, N * (N + 1));
        }
    }
};

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

59. (713A) Sonya and Queries

结果只和每个数每位的奇偶有关,因此可以把每个数当作二进制数存。直接一个大数组存下来即可。

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

const int M = 18;

char Buffer[19];

struct Solution {
    int T;
    vector<int> A;
    
    int ToBinary(const string& b) {
        int answer = 0;
        for (int i = 0; i < b.size(); ++i) {
            answer = answer + answer + b[i] % 2;
        }
        return answer;
    }
    
    void Solve() {
        A.resize(1 << M);
        scanf("%d", &T);
        for (int i = 0; i < T; ++i) {
            char op;
            scanf(" %c %s", &op, Buffer);
            int x = ToBinary(Buffer);
            switch (op) {
                case '+': ++A[x]; break;
                case '-': --A[x]; break;
                case '?': printf("%d\n", A[x]); break;
            }
        }
    }
};

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

60. (371C) Hamburgers

先手工做汉堡,把现有的材料用完,然后就相当于是纯买材料制作汉堡了。

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

char Buffer[101];

typedef long long int64;

struct Solution {
    int NeedB, NeedS, NeedC;
    int HaveB, HaveS, HaveC;
    int PriceB, PriceS, PriceC;
    int64 Money;

    void Solve() {
        scanf("%s", Buffer);
        NeedB = NeedS = NeedC = 0;
        for (int i = 0; Buffer[i]; ++i) {
            switch (Buffer[i]) {
                case 'B': ++NeedB; break;
                case 'S': ++NeedS; break;
                case 'C': ++NeedC; break;
            }
        }
        scanf("%d%d%d", &HaveB, &HaveS, &HaveC);
        scanf("%d%d%d", &PriceB, &PriceS, &PriceC);
        scanf("%lld", &Money);
        int64 answer = 0;
        while (HaveB && NeedB || HaveS && NeedS || HaveC && NeedC) {
            int64 needMoneyB = max(NeedB - HaveB, 0) * PriceB;
            int64 needMoneyS = max(NeedS - HaveS, 0) * PriceS;
            int64 needMoneyC = max(NeedC - HaveC, 0) * PriceC;
            int64 needMoney = needMoneyB + needMoneyS + needMoneyC;
            if (Money < needMoney) break;
            ++answer;
            Money -= needMoney;
            HaveB = HaveB + needMoneyB / PriceB - NeedB;
            HaveS = HaveS + needMoneyS / PriceS - NeedS;
            HaveC = HaveC + needMoneyC / PriceC - NeedC;
        }
        int64 eachMoneyB = NeedB * PriceB;
        int64 eachMoneyS = NeedS * PriceS;
        int64 eachMoneyC = NeedC * PriceC;
        int64 eachMoney = eachMoneyB + eachMoneyS + eachMoneyC;
        printf("%lld\n", answer + Money / eachMoney);
    }
};

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

61. (611C) New Year and Domino

每个点 (x,y) 能放横向骨牌,等价于 (x,y)(x+1,y) 都是空。统计每一个前缀矩形能放横向骨牌的个数,然后就可以计算出任意询问。纵向同理。

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

char Buffer[501];

struct Solution {
    int H, W, Q;
    vector<string> Grid, GridT;
    vector<vector<int> > Prefix, PrefixT;

    vector<vector<int> > ComputePrefix(const vector<string>& grid) {
        vector<vector<int> > prefix(grid.size() + 1);
        for (int i = 0; i < prefix.size(); ++i) {
            prefix[i].resize(grid[0].size() + 1);
        }
        for (int i = 0; i < grid.size(); ++i) {
            for (int j = 0; j < grid[i].size() - 1; ++j) {
                prefix[i + 1][j + 1] = 
                    grid[i][j] == '.' && grid[i][j + 1] == '.';
            }
        }
        for (int i = 0; i < prefix.size(); ++i) {
            for (int j = 1; j < prefix[i].size(); ++j) {
                prefix[i][j] += prefix[i][j - 1];
            }
        }
        for (int i = 1; i < prefix.size(); ++i) {
            for (int j = 0; j < prefix[i].size(); ++j) {
                prefix[i][j] += prefix[i - 1][j];
            }
        }
        return prefix;
    }

    int Compute(const vector<vector<int> >& prefix, 
        int r1, int c1, int r2, int c2) {
        int a1 = prefix[r2][c2];
        int a2 = prefix[r2][c1 - 1];
        int a3 = prefix[r1 - 1][c2];
        int a4 = prefix[r1 - 1][c1 - 1];
        return a1 - a2 - a3 + a4;
    }

    void Solve() {
        scanf("%d%d", &H, &W);
        Grid.resize(H);
        for (int i = 0; i < H; ++i) {
            scanf("%s", Buffer);
            Grid[i] = Buffer;
        }
        GridT.resize(W);
        for (int i = 0; i < W; ++i) {
            GridT[i].resize(H);
            for (int j = 0; j < H; ++j) {
                GridT[i][j] = Grid[j][i];
            }
        }
        Prefix = ComputePrefix(Grid);
        PrefixT = ComputePrefix(GridT);
        scanf("%d", &Q);
        for (int i = 0; i < Q; ++i) {
            int r1, c1, r2, c2;
            scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
            int answer1 = Compute(Prefix, r1, c1, r2, c2 - 1);
            int answer2 = Compute(PrefixT, c1, r1, c2, r2 - 1);
            printf("%d\n", answer1 + answer2);
        }
    }
};

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

62. (441C) Valera and Tubes

蛇形输出所有格子,任意切分成若干条管道即可。

#include <cstdio>
using namespace std;

struct Snake {
    int N, M, X, Y, DY;

    Snake(int n, int m) {
        N = n, M = m;
        X = 1, Y = 1, DY = 1;
    }

    void Next() {
        Y += DY;
        if (Y == 0) {
            ++X;
            Y = 1;
            DY = -DY;
        } else if (Y == M + 1) {
            ++X;
            Y = M;
            DY = -DY;
        }
    }
};

struct Solution {
    int N, M, K;

    void Solve() {
        scanf("%d%d%d", &N, &M, &K);
        Snake snake(N, M);
        for (int i = 0; i < K - 1; ++i) {
            printf("2 %d %d ", snake.X, snake.Y);
            snake.Next();
            printf("%d %d\n", snake.X, snake.Y);
            snake.Next();
        }
        int total = N * M - (K + K - 2);
        printf("%d", total);
        for (int i = 0; i < total; ++i) {
            printf(" %d %d", snake.X, snake.Y);
            snake.Next();
        }
        printf("\n");
    }
};

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

63. (559B) Equivalent Strings

对每个字符串,计算一个哈希值:如果长度为奇数,就是要求有序的哈希值;如果长度为偶数,就是前后两段可以无序的哈希值。

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

typedef unsigned long long uint64;

uint64 Fingerprint(uint64 x) {
    const uint64 kMul = 0x9ddfea08eb382d69ULL;
    x *= kMul, x ^= x >> 47;
    x *= kMul, x ^= x >> 47;
    x *= kMul, x ^= x >> 47;
    return x * kMul;
}

char Buffer[200001];

struct Solution {
    string S1, S2;

    uint64 Hash(int size, const char* s) {
        if (size % 2 == 0) {
            uint64 finger1 = Fingerprint(Hash(size / 2, s));
            uint64 finger2 = Fingerprint(Hash(size / 2, s + size / 2));
            return finger1 + finger2;
        }
        uint64 answer = 0;
        for (int i = 0; i < size; ++i) {
            answer = Fingerprint(answer + s[i]);
        }
        return answer;
    }

    void Solve() {
        scanf("%s", Buffer);
        S1 = Buffer;
        scanf("%s", Buffer);
        S2 = Buffer;
        if (Hash(S1.size(), S1.c_str()) == Hash(S2.size(), S2.c_str())) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }
};

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

64. (486C) Palindrome Transformation

如果 p 在后一半,我们对称到前一半。然后,有两种方法:

  1. 先把 p 移到 0,再移到 n/2-1
  2. 先把 p 移到 n/2-1,再移到 0

取其中答案较小的即可。

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

char Buffer[100001];

int Difference(char c1, char c2) {
    int d1 = (c1 - c2 + 26) % 26;
    int d2 = (c2 - c1 + 26) % 26;
    return min(d1, d2);
}

struct ToPalindrome {
    string S;
    int N, P, Answer;

    void GoLeft() {
        int target = 0;
        while (target <= P && S[target] == S[N - 1 - target]) ++target;
        while (true) {
            Answer += Difference(S[P], S[N - 1 - P]);
            S[P] = S[N - 1 - P];
            if (P <= target) break;
            ++Answer;
            --P;
        }
    }

    void GoRight() {
        int target = N / 2 - 1;
        while (P <= target && S[target] == S[N - 1 - target]) --target;
        while (true) {
            Answer += Difference(S[P], S[N - 1 - P]);
            S[P] = S[N - 1 - P];
            if (target <= P) break;
            ++Answer;
            ++P;
        }
    }

    int GetAnswer1() {
        GoRight();
        GoLeft();
        return Answer;
    }

    int GetAnswer2() {
        GoLeft();
        GoRight();
        return Answer;
    }
};

struct Solution {
    int N, P;
    string S;

    void Solve() {
        scanf("%d%d%s", &N, &P, Buffer), --P;
        S = Buffer;
        if (N / 2 < P) {
            P = N - 1 - P;
            reverse(S.begin(), S.end());
        }
        ToPalindrome to1 = {S, N, P, 0};
        ToPalindrome to2 = {S, N, P, 0};
        printf("%d\n", min(to1.GetAnswer1(), to2.GetAnswer2()));
    }
};

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

65. (295A) Greg and Array

把原数组差分,这样可以常数时间修改一个区间。需要统计每个操作进行的次数,同样,采用差分的方法来统计。把每个操作的 D_i 扩大次数倍,然后直接修改就可以了。

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

typedef long long int64;

struct Operation {
    int L, R;
    int64 D;
};

struct Query {
    int X, Y;
};

struct Solution {
    int N, M, K;
    vector<int64> A;
    vector<Operation> Ops;
    vector<int> OpsTime;
    vector<Query> Queries;

    void Solve() {
        scanf("%d%d%d", &N, &M, &K);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        Ops.resize(M);
        for (int i = 0; i < M; ++i) {
            scanf("%d%d%lld", &Ops[i].L, &Ops[i].R, &Ops[i].D);
            --Ops[i].L, --Ops[i].R;
        }
        Queries.resize(K);
        for (int i = 0; i < K; ++i) {
            scanf("%d%d", &Queries[i].X, &Queries[i].Y);
            --Queries[i].X, --Queries[i].Y;
        }
        OpsTime.resize(M + 1);
        for (int i = 0; i < K; ++i) {
            ++OpsTime[Queries[i].X];
            --OpsTime[Queries[i].Y + 1];
        }
        for (int i = N - 1; i > 0; --i) A[i] -= A[i - 1];
        A.push_back(0);
        for (int i = 0; i < M; ++i) {
            OpsTime[i + 1] += OpsTime[i];
            Ops[i].D *= OpsTime[i];
            A[Ops[i].L] += Ops[i].D;
            A[Ops[i].R + 1] -= Ops[i].D;
        }
        for (int i = 1; i < N; ++i) A[i] += A[i - 1];
        bool head = true;
        for (int i = 0; i < N; ++i) {
            if (!head) putchar(' ');
            head = false;
            printf("%lld", A[i]);
        }
        printf("\n");
    }
};

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

66. (1114B) Yet Another Array Partitioning Task

首先答案一定是最大的 mk 个数。然后把最大的 mk 个数设置成 1,其余数设置成 0,从左向右扫描一遍就可以得到所有的分界点。

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

typedef long long int64;

struct LessBy {
	const vector<int>* A;
	
	LessBy(const vector<int>& a) {A = &a;}
	
	bool operator()(int a1, int a2) {
		return A->at(a1) < A->at(a2);
	}
};

struct Solution {
	int N, M, K;
	vector<int> A;
	
	void Solve() {
		scanf("%d%d%d", &N, &M, &K);
		A.resize(N);
		for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
		vector<int> b(N);
		for (int i = 0; i < N; ++i) b[i] = i;
		sort(b.begin(), b.end(), LessBy(A));
		int64 answer = 0;
		for (int i = N - M * K; i < N; ++i) answer += A[b[i]];
		printf("%lld\n", answer);
		for (int i = 0; i < N; ++i) A[i] = 0;
		for (int i = N - M * K; i < N; ++i) A[b[i]] = 1;
		bool head = true;
		int p = 0;
		for (int i = 1; i < K; ++i) {
			for (int j = 0; j < M; ++j) {
				while (A[p] == 0) ++p;
				++p;
			}
			if (!head) putchar(' ');
			head = false;
			printf("%d", p);
		}
		printf("\n");
	}
};

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

67. (500B) New Year Permutation

如果两个位置可交换,那么这两个位置就是等价的。在每个等价类里,都可以对原排列排序。如何根据等价关系得到等价类呢?其实就是求连通分量就可以了。注意程序中对数组的一部分进行排序的写法。

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

struct Solution {
    int N;
    vector<int> A;
    vector<vector<char> > Adjacent;
    vector<bool> Visited;
    vector<int> CurrentComponent;

    void DFS(int no) {
        Visited[no] = true;
        CurrentComponent.push_back(no);
        for (int i = 0; i < N; ++i) {
            if (Adjacent[no][i] == '1' && !Visited[i]) DFS(i); 
        }
    }

    void SortCurrentComponent() {
        sort(CurrentComponent.begin(), CurrentComponent.end());
        vector<int> b(CurrentComponent.size());
        for (int i = 0; i < b.size(); ++i) b[i] = A[CurrentComponent[i]];
        sort(b.begin(), b.end());
        for (int i = 0; i < b.size(); ++i) A[CurrentComponent[i]] = b[i];
    }

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        Adjacent.resize(N);
        for (int i = 0; i < N; ++i) {
            Adjacent[i].resize(N);
            for (int j = 0; j < N; ++j) {
                scanf(" %c", &Adjacent[i][j]);
            }
        }
        Visited.resize(N);
        for (int i = 0; i < N; ++i) {
            if (!Visited[i]) {
                DFS(i);
                SortCurrentComponent();
                CurrentComponent.clear();
            }
        }
        bool head = true;
        for (int i = 0; i < N; ++i) {
            if (!head) putchar(' ');
            head = false;
            printf("%d", A[i]);
        }
        printf("\n");
    }
};

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

68. (1190A) Tokitsukaze and Discard Items

模拟。从前往后扫描一遍就可以算出每个点删除的时候在第几页,然后能顺便带走同一页的点。

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

typedef long long int64;

struct Solution {
    int64 N, M, K;
    vector<int64> P;

    void Solve() {
        scanf("%lld%lld%lld", &N, &M, &K);
        P.resize(M);
        for (int i = 0; i < M; ++i) scanf("%lld", &P[i]);
        int answer = 0;
        for (int i = 0; i < M; ) {
            int64 page = (P[i] - i - 1) / K;
            int j = i + 1;
            for (; j < M; ++j) {
                int64 page1 = (P[j] - i - 1) / K;
                if (page1 != page) break;
            }
            ++answer;
            i = j;
        }
        printf("%d\n", answer);
    }
};

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

69. (1106D) Lunar New Year and a Wander

有点像最小生成树的 Prim 算法:贪心选择所有与当前连通部分相邻的,还没加入连通部分的,标号最小的点加入连通部分。

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

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

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1 - 1].push_back(v2 - 1);
            Out[v2 - 1].push_back(v1 - 1);
        }
        Visited.resize(N);
        Visited[0] = true;
        Q.insert(0);
        bool head = true;
        while (!Q.empty()) {
            int front = *Q.begin();
            Q.erase(Q.begin());
            if (!head) putchar(' ');
            head = false;
            printf("%d", front + 1);
            for (int i = 0; i < Out[front].size(); ++i) {
                int target = Out[front][i];
                if (!Visited[target]) {
                    Visited[target] = true;
                    Q.insert(target);
                }
            }
        }
        printf("\n");
    }
};

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

70. (525B) Pasha and String

求出翻转次数的前缀和,就是特定的一位是否被翻转过。

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

char Buffer[200001];

struct Solution {
    string S;
    vector<bool> Reversed;
    int M, A;

    void Solve() {
        scanf("%s%d", Buffer, &M);
        S = Buffer;
        Reversed.resize(S.size() / 2);
        for (int i = 0; i < M; ++i) {
            scanf("%d", &A);
            Reversed[A - 1] = Reversed[A - 1] ^ 1;
        }
        for (int i = 1; i < Reversed.size(); ++i) {
            Reversed[i] = Reversed[i] ^ Reversed[i - 1];
        }
        for (int i1 = 0; i1 < S.size(); ++i1) {
            int i2 = S.size() - 1 - i1;
            int iMin = min(i1, i2);
            if (iMin < Reversed.size() && Reversed[iMin]) {
                putchar(S[i2]);
            } else {
                putchar(S[i1]);
            }
        }
        printf("\n");
    }
};

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

71. (484A) Bits

先把询问变成开区间,然后如果左端点二进制最后一位是 0,处理它的兄弟;如果右端点二进制最后一位是 1,处理它的兄弟。然后左右儿子各往上一层。很像是 zkw 线段树的处理方法。

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

typedef long long int64;

bool AssignMin(int64* p, int64 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;
    int64 L, R;

    int PopCount(int64 x) {
        int answer = 0;
        while (x != 0) {
            x &= x - 1;
            ++answer;
        }
        return answer;
    }

    int64 MaxPopCount(int64 boundL, int64 boundR) {
        int depth = 0, answer = 0;
        int64 arg = 0;
        int popCountL = PopCount(boundL);
        int popCountR = PopCount(boundR);
        while ((boundL ^ boundR) != 1) {
            if ((boundL & 1) == 0) {
                int answer1 = depth + popCountL + 1;
                int64 arg1 = ((boundL + 1 + 1) << depth) - 1;
                if (AssignMax(&answer, answer1)) {
                    arg = arg1;
                } else if (answer == answer1) {
                    AssignMin(&arg, arg1);
                }
            }
            if ((boundR & 1) != 0) {
                int answer1 = depth + popCountR - 1;
                int64 arg1 = ((boundR - 1 + 1) << depth) - 1;
                if (AssignMax(&answer, answer1)) {
                    arg = arg1;
                } else if (answer == answer1) {
                    AssignMin(&arg, arg1);
                }
            }
            if (boundL & 1) --popCountL;
            boundL >>= 1;
            if (boundR & 1) --popCountR;
            boundR >>= 1;
            ++depth;
        }
        return arg;
    }

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            scanf("%lld%lld", &L, &R);
            printf("%lld\n", MaxPopCount(max(0LL, L - 1), R + 1));
        }
    }
};

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

72. (1201B) Zero Array

如果总和是奇数,不可能。如果最大的数比剩余的数加起来还大,不可能。否则可以把最大的数和第二大的数减一,保持前两个性质不变,最终得到零。

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

typedef long long int64;

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

    bool CanDecrease() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        int64 sum = 0, max = *max_element(A.begin(), A.end());
        for (int i = 0; i < N; ++i) sum += A[i];
        if (sum % 2 != 0) return false;
        if (max + max > sum) return false;
        return true;
    }

    void Solve() {
        printf(CanDecrease() ? "YES\n" : "NO\n");
    }
};

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

73. (812C) Sagheer and Nubian Market

k 件商品可行,那么买 k - 1 件商品也可行。因此可以二分答案,查找最小的不可行的 k,然后减一就是最大可行的 k,再算需要花的钱。

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

typedef long long int64;

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

    int64 FindSum(int k) {
        vector<int64> price(N);
        for (int i = 0; i < N; ++i) price[i] = A[i] + k * int64(i + 1);
        sort(price.begin(), price.end());
        int64 sum = 0;
        for (int i = 0; i < k; ++i) sum += price[i];
        return sum;
    }

    int BinarySearch(int lower, int upper) {
        while (lower < upper) {
            int mid = lower + (upper - lower) / 2;
            if (FindSum(mid) <= S) {
                lower = mid + 1;
            } else {
                upper = mid;
            }
        }
        return lower;
    }

    void Solve() {
        scanf("%d%d", &N, &S);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        int items = BinarySearch(1, N + 1) - 1;
        int64 sum = FindSum(items);
        printf("%d %lld\n", items, sum);
    }
};

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

74. (165C) Another Problem on Strings

把 1 看作分隔符,把原数组整理成最开始的 1 前面,每两个 1 之间,和最后的 1 后面有多少个 0 的形式。然后答案是显而易见的。注意,k=0 是特殊情况。

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

typedef long long int64;

char Buffer[1000001];

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

    void Solve() {
        scanf("%d%s", &N, Buffer);
        S = Buffer;
        A.push_back(0);
        for (int i = 0; i < S.size(); ++i) {
            if (S[i] == '0') {
                ++A.back();
            } else {
                A.push_back(0);
            }
        }
        int64 answer = 0;
        if (N == 0) {
            for (int i = 0; i < A.size(); ++i) {
                answer += int64(A[i] + 1) * A[i] / 2;
            }
        } else {
            for (int i = 0; i + N < A.size(); ++i) {
                answer += int64(A[i] + 1) * (A[i + N] + 1);
            }
        }
        printf("%lld\n", answer);
    }
};

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

75. (778A) String Game

如果一个串不包含指定子串,删除若干字符后依然不会包含。因此可以二分答案,计算最小的操作次数,使得操作之后不包含指定子串,然后减一就是答案。

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

char Buffer[200001];

struct Solution {
    string S, S1;
    vector<int> P;

    bool OK(int k) {
        vector<bool> Removed(S.size());
        for (int i = 0; i < k; ++i) Removed[P[i]] = true;
        int p = 0;
        for (int i = 0; i < S.size(); ++i) {
            if (Removed[i]) continue;
            if (S[i] == S1[p]) ++p;
            if (p == S1.size()) return true;
        }
        return false;
    }

    int BinarySearch(int lower, int upper) {
        while (lower < upper) {
            int mid = lower + (upper - lower) / 2;
            if (OK(mid)) {
                lower = mid + 1;
            } else {
                upper = mid;
            }
        }
        return lower;
    }

    void Solve() {
        scanf("%s", Buffer);
        S = Buffer;
        scanf("%s", Buffer);
        S1 = Buffer;
        P.resize(S.size());
        for (int i = 0; i < P.size(); ++i) {
            scanf("%d", &P[i]);
            --P[i];
        }
        int maxOK = BinarySearch(0, P.size() + 1) - 1;
        printf("%d\n", maxOK);
    }
};

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

76. (9C) Hexadecimal's Numbers

本题比较简单,可以枚举,但是作为一般的方法,可以考虑类似 zkw 线段树的方法,先变成开区间,然后判断最后一位,之后去掉最后一位再讨论。如果这题目改成九进制数,枚举就不好用了。

#include <cstdio>
using namespace std;

struct Solution {
    int N;

    int InvalidNum(int n) {
        int answer = 0;
        while (n != 0) {
            answer += (1 < n % 10);
            n /= 10;
        }
        return answer;
    }

    void Solve() {
        scanf("%d", &N);
        ++N;
        int invalidNum = InvalidNum(N);
        int depth = 0, answer = 0;
        while (N != 0) {
            invalidNum -= (1 < N % 10);
            if (invalidNum == 0 && 0 < N % 10) answer += 1 << depth;
            if (invalidNum == 0 && 1 < N % 10) answer += 1 << depth;
            N /= 10;
            ++depth;
        }
        printf("%d\n", answer - 1);
    }
};

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

77. (977E) Cyclic Components

判断环,就是一个连通分量的每个点度都是 2 即可。

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

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

    void DFS(int no) {
        Visited[no] = true;
        CurrentComponent.push_back(no);
        for (int i = 0; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            if (!Visited[target]) DFS(target);
        }
    }

    bool IsCycle() {
        for (int i = 0; i < CurrentComponent.size(); ++i) {
            if (Out[CurrentComponent[i]].size() != 2) return false;
        }
        return true;
    }

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1 - 1].push_back(v2 - 1);
            Out[v2 - 1].push_back(v1 - 1);
        }
        Visited.resize(N);
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            if (!Visited[i]) {
                DFS(i);
                if (IsCycle()) ++answer;
                CurrentComponent.clear();
            }
        }
        printf("%d\n", answer);
    }
};

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

78. (158C) Cd and pwd commands

使用 vector<string> 存储路径即可。需要编写一个按照斜杠分割路径的子程序。

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

char Buffer[201];

struct Solution {
    vector<string> Path;
    int N;

    vector<string> SplitPath(const string& path) {
        vector<string> answer;
        int p = 0;
        while (p < path.size()) {
            if (path[p] == '/') ++p;
            int nchar = 0;
            if (sscanf(&path[p], "%[^/]%n", Buffer, &nchar) == 1) {
                answer.push_back(Buffer);
                p += nchar;
            }
        }
        return answer;
    }

    void Solve() {
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            scanf("%s", Buffer);
            string command = Buffer;
            if (command == "pwd") {
                for (int i = 0; i < Path.size(); ++i) {
                    printf("/%s", Path[i].c_str());
                }
                printf("/\n");
            } else {
                scanf("%s", Buffer);
                string newPath = Buffer;
                if (newPath[0] == '/') Path.clear();
                vector<string> newSplitted = SplitPath(newPath);
                for (int i = 0; i < newSplitted.size(); ++i) {
                    if (newSplitted[i] == "..") {
                        Path.pop_back();
                    } else {
                        Path.push_back(newSplitted[i]);
                    }
                }
            }
        }
    }
};

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

79. (788A) Functions again

先把原数组差分,取绝对值,转换成一个类似最大连续子段和的动态规划问题。

#include <cstdio>
#include <cmath>
#include <climits>
#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;
    vector<int> A;

    void Solve() {
        scanf("%d", &N);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        for (int i = 0; i < N - 1; ++i) A[i] = abs(A[i + 1] - A[i]);
        int64 answer = INT_MIN;
        if (1 <= N - 1) {
            int64 answer1 = A[0], current1 = A[0];
            for (int i = 2; i < N - 1; i += 2) {
                current1 -= A[i - 1];
                current1 = current1 > 0 ? current1 + A[i] : A[i];
                AssignMax(&answer1, current1);
            }
            AssignMax(&answer, answer1);
        }
        if (2 <= N - 1) {
            int64 answer2 = A[1], current2 = A[1];
            for (int i = 3; i < N - 1; i += 2) {
                current2 -= A[i - 1];
                current2 = current2 > 0 ? current2 + A[i] : A[i];
                AssignMax(&answer2, current2);
            }
            AssignMax(&answer, answer2);
        }
        printf("%lld\n", answer);
    }
};

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

80. (165B) Burning Midnight Oil

二分答案即可,其余计算过程均可暴力计算。

#include <cstdio>
using namespace std;

struct Solution {
    int N, K;

    int ComputeLines(int init) {
        int answer = init;
        while (init != 0) {
            init /= K;
            answer += init;
        }
        return answer;
    }

    int BinarySearch(int lower, int upper) {
        while (lower < upper) {
            int mid = lower + (upper - lower) / 2;
            if (N <= ComputeLines(mid)) {
                upper = mid;
            } else {
                lower = mid + 1;
            }
        }
        return lower;
    }

    void Solve() {
        scanf("%d%d", &N, &K);
        int answer = BinarySearch(0, N);
        printf("%d\n", answer);
    }
};

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

81. (614A) Link/Cut Tree

直接比较会超过 int64 的范围。注意到 AB<C 等价于 A<\lceil C/B\rceil,而 AB\le C 等价于 A \le \lfloor C / B \rfloor

#include <cstdio>
using namespace std;

typedef long long int64;

struct Solution {
    int64 L, R, K;

    void Solve() {
        scanf("%lld%lld%lld", &L, &R, &K);
        bool head = true;
        if (L == 1) {
            if (!head) putchar(' ');
            head = false;
            printf("1");
        }
        int64 p = 1;
        while (p < (L + K - 1) / K) p *= K;
        while (p <= R / K) {
            if (!head) putchar(' ');
            head = false;
            printf("%lld", p * K);
            p *= K;
        }
        if (head) {
            printf("-1\n");
        } else {
            printf("\n");
        }
    }
};

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

82. (445B) DZY Loves Chemistry

DFS 求连通分量的个数。

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

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

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

    void Solve() {
        scanf("%d%d", &N, &M);
        Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1 - 1].push_back(v2 - 1);
            Out[v2 - 1].push_back(v1 - 1);
        }
        Visited.resize(N);
        int answer = 0;
        for (int i = 0; i < N; ++i) {
            if (!Visited[i]) {
                ++answer;
                DFS(i);
            }
        }
        printf("%lld\n", 1LL << (N - answer));
    }
};

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

83. (557B) Pasha and Tea

将输入排序,小的一半用来给女生喝水就可以了。请尝试用调整法证明其正确性。

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

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

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

    void Solve() {
        scanf("%d%d", &N, &W);
        A.resize(N + N);
        for (int i = 0; i < N + N; ++i) scanf("%d", &A[i]);
        sort(A.begin(), A.end());
        double minCup1 = 1.0 / 0.0, minCup2 = 1.0 / 0.0;
        for (int i = 0; i < N; ++i) AssignMin(&minCup1, A[i]);
        for (int i = 0; i < N; ++i) AssignMin(&minCup2, A[N + i]);
        double minCup = min(minCup1, minCup2 * 0.5);
        double capacity = minCup * 3.0 * N;
        printf("%.12lf\n", min(capacity, double(W)));
    }
};

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

84. (1009B) Minimum Ternary String

题意可以这么理解,1 可以任意穿透 0 和 2,但 0 和 2 的相对位置不变。因此先删除所有的 1,然后放在最前面的一堆 0 后面就可以了。

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

char Buffer[100001];

struct Solution {
    string S, S1, S2;

    void Solve() {
        scanf("%s", Buffer);
        S = Buffer;
        S1.reserve(S.size());
        int one = 0;
        for (int i = 0; i < S.size(); ++i) {
            if (S[i] == '1') {
                ++one;
            } else {
                S1 += S[i];
            }
        }
        int p = 0;
        while (p < S1.size() && S1[p] == '0') ++p;
        S2 = S1.substr(0, p);
        S2.reserve(S.size());
        for (int i = 0; i < one; ++i) S2 += '1';
        S2 += S1.substr(p);
        printf("%s\n", S2.c_str());
    }
};

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

85. (763A) Timofey and a tree

先随便找一个点把树变成有根树。然后计算出每个节点的子树的颜色(0 为杂色),再判断每个节点能否作为题目中要求的根,即删除它后得到的森林里面每棵树都是同色。

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

struct Solution {
    int N;
    vector<vector<int> > Out;
    vector<int> C, Subtree;

    void Hang(int no, int parent) {
        for (int i = 0; i < Out[no].size(); ++i) {
            if (Out[no][i] == parent) {
                swap(Out[no][0], Out[no][i]);
            } else {
                Hang(Out[no][i], no);
            }
        }
    }

    void ComputeSubtree(int no) {
        Subtree[no] = C[no];
        for (int i = 1; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            ComputeSubtree(target);
            if (Subtree[target] != Subtree[no]) Subtree[no] = 0;
        }
    }

    int FindGoodRoot(int no, int upColor) {
        bool good = (upColor != 0);
        for (int i = 1; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            if (Subtree[target] == 0) good = false;
        }
        if (good) return no;
        if (upColor != C[no]) return -1;
        int different = 0;
        for (int i = 1; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            if (Subtree[target] != upColor) ++different;
        }
        for (int i = 1; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            if (Subtree[target] != upColor) --different;
            if (different == 0) {
                int ret = FindGoodRoot(target, upColor);
                if (ret != -1) return ret;
            }
            if (Subtree[target] != upColor) ++different;
        }
        return -1;
    }

    void Solve() {
        scanf("%d", &N);
        Out.resize(N);
        for (int i = 1; i < N; ++i) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            Out[v1 - 1].push_back(v2 - 1);
            Out[v2 - 1].push_back(v1 - 1);
        }
        C.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &C[i]);
        Out[0].push_back(-1);
        Hang(0, -1);
        Subtree.resize(N);
        ComputeSubtree(0);
        int ret = FindGoodRoot(0, C[0]);
        if (ret == -1) {
            printf("NO\n");
        } else {
            printf("YES\n%d\n", ret + 1);
        }
    }
};

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

86. (414A) Mashmokh and Numbers

除了第一对数,令剩下的最大公约数都为 1。需要凑一凑满足数字两两不同的条件。

#include <cstdio>
using namespace std;

struct Solution {
    int N, K;

    void Solve() {
        scanf("%d%d", &N, &K);
        int n1 = N / 2;
        if (N == 1) {
            printf(K == 0 ? "1\n" : "-1\n");
        } else if (K < n1) {
            printf("-1\n");
        } else {
            int a1 = K - (n1 - 1);
            printf("%d %d", a1 * 2, a1 * 3);
            const int m = 400000000;
            for (int i = 1; i < n1; ++i) {
                printf(" %d %d", m + i + i, m + i + i + 1);
            }
            if (N % 2 != 0) printf(" 1");
            printf("\n");
        }
    }
};

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

87. (587A) Duff and Weight Lifting

如果最小的杠铃有奇数个,必须先拿一个。否则可以两两配对后把杠铃的大小加一,数量除以二,答案不变。

#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(*max_element(A.begin(), A.end()) + 1);
        for (int i = 0; i < N; ++i) ++B[A[i]];
        int answer = 0, carry = 0;
        for (int i = 0; i < B.size(); ++i) {
            carry += B[i];
            answer += carry % 2;
            carry /= 2;
        }
        while (carry != 0) {
            answer += carry % 2;
            carry /= 2;
        }
        printf("%d\n", answer);
    }
};

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

88. (567C) Geometric Progression

枚举中间的数,相当于查询某数左边等于 x 的数有多少个,右边等于 x 的数有多少个,可以使用 map 实现。

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

typedef long long int64;

struct Solution {
    int N, K;
    vector<int> A, B1, B2;
    map<int64, int> M1, M2;

    void Solve() {
        scanf("%d%d", &N, &K);
        A.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
        B1.resize(N);
        for (int i = 0; i < N; ++i) {
            if (A[i] % K == 0) {
                B1[i] = M1[A[i] / K];
            } else {
                B1[i] = 0;
            }
            ++M1[A[i]];
        }
        M1.clear();
        B2.resize(N);
        for (int i = N - 1; i >= 0; --i) {
            B2[i] = M2[A[i] * int64(K)];
            ++M2[A[i]];
        }
        M2.clear();
        int64 answer = 0;
        for (int i = 0; i < N; ++i) answer += int64(B1[i]) * B2[i];
        printf("%lld\n", answer);
    }
};

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

89. (372A) Counting Kangaroos is Fun

运用调整法可以证明,只要尝试把前一半的袋鼠放进后一半的袋鼠里面就可以了。

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

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

    void Solve() {
        scanf("%d", &N);
        S.resize(N);
        for (int i = 0; i < N; ++i) scanf("%d", &S[i]);
        sort(S.begin(), S.end());
        int i2 = N / 2, answer = N - N / 2;
        for (int i = 0; i < N / 2; ++i) {
            while (i2 < N && S[i2] < S[i] + S[i]) ++i2;
            if (i2 < N) {
                ++i2;
            } else {
                ++answer;
            }
        }
        printf("%d\n", answer);
    }
};

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

90. (546D) Soldier and Number Game

求质因数个数,需要先用筛法求出每个数的最小质因数,然后动态规划。

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

typedef long long int64;

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

struct Query {
    int A, B;
};

struct Solution {
    int N, M;
    vector<Query> Queries;
    vector<int> MinFactor, NumFactor;

    void Solve() {
        scanf("%d", &N);
        Queries.resize(N);
        int M = 0;
        for (int i = 0; i < N; ++i) {
            scanf("%d%d", &Queries[i].A, &Queries[i].B);
            AssignMax(&M, Queries[i].A);
        }
        MinFactor.resize(M + 1);
        for (int i = 2; i <= M; ++i) {
            if (MinFactor[i] == 0) {
                MinFactor[i] = i;
                for (int64 j = int64(i) * i; j <= M; j += i) {
                    if (MinFactor[j] == 0) MinFactor[j] = i;
                }
            }
        }
        NumFactor.resize(M + 1);
        for (int i = 2; i <= M; ++i) {
            NumFactor[i] = NumFactor[i / MinFactor[i]] + 1;
        }
        for (int i = 1; i <= M; ++i) NumFactor[i] += NumFactor[i - 1];
        for (int i = 0; i < N; ++i) {
            printf("%d\n", NumFactor[Queries[i].A] - NumFactor[Queries[i].B]);
        }
    }
};

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

91. (322B) Ciel and Flowers

如果有 3 个以上的混合花束,可以把 3 个混合花束替换成 3 个各种花束。所以存在一个最优答案,其中混合花束的数量不超过 2。枚举这个数量即可。

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

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

struct Solution {
    int R, G, B;

    void Solve() {
        scanf("%d%d%d", &R, &G, &B);
        int minRGB = min(R, min(G, B));
        int answer = 0;
        for (int i = 0; i < 3; ++i) {
            if (minRGB < i) break;
            int r = (R - i) / 3;
            int g = (G - i) / 3;
            int b = (B - i) / 3;
            AssignMax(&answer, r + g + b + i);
        }
        printf("%d\n", answer);
    }
};

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

92. (538A) Cutting Banner

计算开始有多少字符匹配,末尾处有多少字符匹配,如果两者之和能覆盖 CODEFORCES 就可以。

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

const string C = "CODEFORCES";

char Buffer[101];

struct Solution {
    string S;

    int MaxMatch(const string& s1, const string& s2) {
        for (int i = 0; ; ++i) {
            if (i == s1.size()) return i;
            if (i == s2.size()) return i;
            if (s1[i] != s2[i]) return i;
        }
    }

    void Solve() {
        scanf("%s", Buffer);
        S = Buffer;
        int match1 = MaxMatch(S, C);
        string S2 = S, C2 = C;
        reverse(S2.begin(), S2.end());
        reverse(C2.begin(), C2.end());
        int match2 = MaxMatch(S2, C2);
        printf(match1 + match2 >= C.size() ? "YES\n" : "NO\n");
    }
};

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

93. (505B) Mr. Kitayuta's Colorful Graph

使用 DFS 计算出每种颜色的连通分量,然后直接统计即可。

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

struct Graph {
    vector<vector<int> > Out;
    vector<int> Component;
    int ComponentSize;

    void AddEdge(int no1, int no2) {
        Out[no1].push_back(no2);
        Out[no2].push_back(no1);
    }

    void DFS(int no, int current) {
        Component[no] = current;
        for (int i = 0; i < Out[no].size(); ++i) {
            int target = Out[no][i];
            if (Component[target] == 0) DFS(target, current);
        }
    }

    void ComputeConnectedComponents() {
        ComponentSize = 0;
        Component.resize(Out.size());
        for (int i = 0; i < Out.size(); ++i) {
            if (Component[i] == 0) {
                DFS(i, ++ComponentSize);
            }
        }
    }

    bool SameComponent(int no1, int no2) {
        return Component[no1] == Component[no2];
    }
};

struct Solution {
    int N, M, Q;
    vector<Graph> Graphs;

    void Solve() {
        scanf("%d%d", &N, &M);
        Graphs.resize(M);
        for (int i = 0; i < M; ++i) Graphs[i].Out.resize(N);
        for (int i = 0; i < M; ++i) {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            Graphs[c - 1].AddEdge(a - 1, b - 1);
        }
        for (int i = 0; i < M; ++i) Graphs[i].ComputeConnectedComponents();
        scanf("%d", &Q);
        for (int i = 0; i < Q; ++i) {
            int u1, u2;
            scanf("%d%d", &u1, &u2);
            int answer = 0;
            for (int j = 0; j < M; ++j) {
                if (Graphs[j].SameComponent(u1 - 1, u2 - 1)) ++answer;
            }
            printf("%d\n", answer);
        }
    }
};

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

94. (1200C) Round Corridor

里外都有墙就无法通过,否则可以通过。里外都有墙的地方共有 \mathrm{GCD}(n, m) 个,分割成这么多个区域,只要判断起点和终点在不在同一个区域即可。

#include <cstdio>
using namespace std;

typedef long long int64;

int64 GCD(int64 a, int64 b) {
    if (b == 0) return a;
    return GCD(b, a % b);
}

struct Solution {
    int64 N, M, G;
    int Q;

    void Solve() {
        scanf("%lld%lld%d", &N, &M, &Q);
        G = GCD(N, M);
        for (int i = 0; i < Q; ++i) {
            int sx, ex;
            int64 sy, ey;
            scanf("%d%lld%d%lld", &sx, &sy, &ex, &ey), --sy, --ey;
            sy /= (sx == 1 ? N : M) / G;
            ey /= (ex == 1 ? N : M) / G;
            if (sy == ey) {
                printf("YES\n");
            } else {
                printf("NO\n");
            }
        }
    }
};

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

95. (1010A) Fly

最后一定没多余的燃料。从最后出发倒推,就可以得出初始需要的燃料。

#include <cstdio>
using namespace std;

struct Solution {
    int N, M, X;

    void Solve() {
        scanf("%d%d", &N, &M);
        double answer = M;
        for (int i = 0; i < N + N; ++i) {
            scanf("%d", &X);
            answer *= X / double(X - 1);
        }
        if (answer > 1e10) {
            printf("-1\n");
        } else {
            printf("%.12lf\n", answer - M);
        }
    }
};

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

96. (448D) Multiplication Table

题目有误:输出第 k 小而不是第 k 大的数。

二分答案,然后 O(n) 计算不比 x 大的数的个数即可。

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

typedef long long int64;

struct Solution {
    int64 N, M, K;

    int64 CountNoMoreThan(int64 x) {
        int64 answer = 0;
        for (int64 i = 1; i <= N; ++i) {
            answer += min(x / i, M);
        }
        return answer;
    }

    int64 BinarySearch(int64 lower, int64 upper) {
        while (lower < upper) {
            int64 mid = lower + (upper - lower) / 2;
            if (CountNoMoreThan(mid) < K) {
                lower = mid + 1;
            } else {
                upper = mid;
            }
        }
        return lower;
    }

    void Solve() {
        scanf("%lld%lld%lld", &N, &M, &K);
        printf("%lld\n", BinarySearch(1, N * M));
    }
};

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

97. (468A) 24 Game

用前几个数凑 24,后面的数两两减成一然后乘到答案上去。

#include <cstdio>
using namespace std;

struct Solution {
    int N;

    void Solve() {
        scanf("%d", &N);
        if (N <= 3) {
            printf("NO\n");
        } else if (N % 2 == 0) {
            printf("YES\n");
            printf("1 * 2 = 2\n");
            printf("2 * 3 = 6\n");
            printf("6 * 4 = 24\n");
            for (int i = 5; i < N; i += 2) {
                printf("%d - %d = 1\n", i + 1, i);
                printf("24 * 1 = 24\n");
            }
        } else {
            printf("YES\n");
            printf("1 * 2 = 2\n");
            printf("3 + 4 = 7\n");
            printf("7 + 5 = 12\n");
            printf("2 * 12 = 24\n");
            for (int i = 6; i < N; i += 2) {
                printf("%d - %d = 1\n", i + 1, i);
                printf("24 * 1 = 24\n");
            }
        }
    }
};

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

98. (282B) Painting Eggs

每分配一个蛋,都满足两者之差小于等于 500。运用归纳法,可以证明这样做是可行的。

#include <cstdio>
using namespace std;

struct Solution {
    int N, A, G;

    void Solve() {
        scanf("%d", &N);
        int difference = 0;
        for (int i = 0; i < N; ++i) {
            scanf("%d%d", &A, &G);
            if (difference + A <= 500) {
                putchar('A');
                difference += A;
            } else {
                putchar('G');
                difference -= G;
            }
        }
        putchar('\n');
    }
};

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

99. (555A) Case of Matryoshkas

除了一个从 1 开始的连续链条可以不拆之外,别的都需要拆。

#include <cstdio>
using namespace std;

struct Solution {
    int N, K;

    void Solve() {
        scanf("%d%d", &N, &K);
        int answer = N + N - K + 1;
        for (int i = 0; i < K; ++i) {
            int length;
            scanf("%d", &length);
            for (int j = 0; j < length; ++j) {
                int a;
                scanf("%d", &a);
                if (a == j + 1) answer -= 2;
            }
        }
        printf("%d\n", answer);
    }
};

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

100. (453A) Little Pony and Expected Maximum

计算出最大的数至多为 k 的情况数,减去最大的数至多为 k - 1 的情况数,就是最大的数恰好是 k 的情况数。

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

struct Solution {
    int M, N;

    void Solve() {
        scanf("%d%d", &M, &N);
        double answer = 0.0;
        for (int i = 1; i <= M; ++i) {
            answer += i * (pow(double(i) / M, N) - pow(double(i - 1) / M, N));
        }
        printf("%.12lf\n", answer);
    }
};

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