GithubHelp home page GithubHelp logo

wizmann / acm-icpc Goto Github PK

View Code? Open in Web Editor NEW
63.0 9.0 29.0 6.32 MB

感觉自己做了假题。

Home Page: http://wizmann.tk

License: Other

C++ 64.98% Java 0.59% Python 32.71% Scala 0.12% Haskell 0.01% JavaScript 0.06% C# 0.92% Shell 0.03% Erlang 0.01% Go 0.37% Lua 0.01% F# 0.16% Swift 0.01% Hack 0.01% Kotlin 0.04% OCaml 0.01%
c-plus-plus python algorithm algorithm-challenges fun competitive-programming competitive-coding

acm-icpc's People

Contributors

wizmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

acm-icpc's Issues

CodeForces 507E - Breaking Good

https://vjudge.net/problem/CodeForces-507E

题意

给定一个有n个点,m条边的无向图。边有两种状态,”好边“意味着这条边是连通的;”坏边“表明这条边不联通。我们可以改变边的状态,即“修复坏边,使其成为好边”,或者“破坏好边,使其成为坏边”。

现在要从点1到达点n,求一条路径,要求:

  1. 路径总长最短
  2. 路径上的坏边需要被修复
  3. 不在路径上的好边需要全部被破坏

问最少需要改变多少条边的状态。

  • 1 <= n <= 1e5
  • 1 <= m <= 1e5

解法

点1到点n的最短路径用SPFA算法即可求出,记为sp。但是最短路径有可能不是唯一的,所以需要对状态改变进行计算。

已知全图有a条好边,假设某条最短路径上面有b条坏边。那么需要改变的总边数为:(a - (sp - b)) + b = a - sp + 2*b

所以需要改变的总边数只与最短路径上的坏边条数相关。我们只需要找到坏边最少的最短路径即可。

使用2维SPFA求解,并且需要记录到达该状态的上一个状态,以方便还原最短路径。

Code

Leetcode Weekly Contest 186

赛后补题,用时25~30分钟(忘按计时器了,只好用第一题交题的时间推算了)

1422. Maximum Score After Splitting a String

Problem
Code

先统计字符串里有多少个1。然后从左向右依次遍历,维护最右两边各有多少个0和1。
小心左右子串都不能为空。

这题有one pass的做法,需要小推一个公式。

1423. Maximum Points You Can Obtain from Cards

Problem
Code

前辍后辍求和,如果前辍取x个,那么后辍就取k - x个。
枚举所有的x,取最大前辍后辍和即可。

Discussion里面有用滑动窗口做的,应该是更好的解法。

1424. Diagonal Traverse II

Problem
Code

对角线上面的数有一个性质,设数的坐标为(x, y)。那么所有x + y的值相等的数都在一条对角线上。

所以我们统计所有x + y相等的数,进行归类。并且用x的大小对其进行排序。依次输出即可。

如果我们按行遍历的话,每一个x + y归成的类都是逆序的。所以并不需要排序,直接求逆序输出即可。

1425. Constrained Subset Sum

Problem
Code

这道题是几周以来最好的一题,值得一做。
到底有多好呢?好到都可以翻译一下题意的那种。

给定一个数组nums[n],求一个和最大的(可以不连续的)子序列,这个子序列里的所有元素之间的距离不能超过k。

这题最暴力的想法就是用一个dp[n]。dp[i]表示结尾为nums[i]的和最大的子序列。这样做的时间复杂度为O(n^2),会TLE。

直觉告诉我们,这里有一个优化。如果我们可以在很优的时间复杂度内(暂记做O(x))知道dp[i - k ... i]中最小的值,就可以把时间复杂度降到O(nx)。

取最小值可以用std::set,时间复杂度为O(n * logn)。也可以使用单调队列,时间复杂度为O(n)。

我们思考一下,当前下标为i的时候。如果dp[i - 3]和dp[i - 10]的值相同,我们会更倾向保留哪个值。答案非常简单,一定是dp[i - 3]。因为有着同样的值,dp[i - 3]有更大的可能在后续的查找所使用。换句话说,在值相同的情况下dp[i - 3]更优。

所以我们在这里维护一个双端队列。当一个新值到来的时候,我们会先从队尾弹出一定不符合条件的值(即下标小于i - k的值)。然后从队首弹出小于等于新值的值(因为新值一定最优)。

此时队列里面的值,一定是单调递减的。原因上面已经说过了,当i - a > i - b时(即a离i更远),只能有a > b。此时,队列里最大的值一定在队尾。

因为每遍历一个元素,有且只有一个新值,一个新值最多插入和弹出队列一次。所以整体的时间复杂度为O(n)。

赛后评分

1422. Maximum Score After Splitting a String

没新意,1分。
如果想到了one pass的话,勉强做2分。

1423. Maximum Points You Can Obtain from Cards

滑动窗口应该是正解,不过仍然没新意,1分。

1424. Diagonal Traverse II

找规律,值得一做,3分。

1425. Constrained Subset Sum

少见的单调队列的题,为什么不做做呢?5分。

总结

速度做题有的时候是以拿AC为目标的,但是真正在面试中,有的时候是以代码的质量和算法的质量为目标的。

做对不一定是做好,做好也不一定是最优。真难。

Codeforces Round #638 (Div. 2)

Contest
Code

现场出ABCD,排名1000+。中间错题太多了,除了A基本都卡了一下。最后没有时间看E了。
我是**。

A. Phoenix and Balance

题意

给定n个数(n为偶数),2^1, 2^2 ... 2^n。现在我们将其分成两组,每组各n/2个数。
问这两组的和之差的最小值是多少。

解法

因为2^n = sum(2^1 + 2^2 ... 2^(n-1)) + 2
所以我们把2^n和剩下最小的数放到一边就好了。

B. Phoenix and Beauty

题意

感觉这题出的有问题,题意不明。比如第4组样例在不加数的情况是否正确。不过如果你不强行考虑这些细节的话,这题也是可以做的。

给定一个数组,长度为n。让你在数组的任意位置添加1~n的数字,使得每一个长度为k的连续子数组之和都相等。

1 <= k <= n <= 100,最终的数组长度不要求最优,不超过10^4即可。

解法

首先我们要确定,数组里面数字的种数不能超过k。而且数字在最终数组中的排列一定是"abcdabcd...."。

写到这里我才发现我看错题了。那么问题来了,我是怎么把题做对的。。。

所以对于数组里面的任意一个数,其实都可以包含[abcd]的一个循环节里。所以最长不会超过n * k。

所以我们直接输出[abcd] * n即可,没有其他操作。

C. Phoenix and Distribution

题意

给定一个字符串,让你把字符串重新排列,并分成k部分。使得这k部分里面最大字典序的字符串最小。并输出这个字符串。

1 <= k <= n <= 10^5

解法

策略题,在这题上面浪费了不少时间。最后还是通过对拍找到的结果。

对于第一个字符,一定要选最小的。比如我们将"aabc"分成两部分,这两部分的开头一定要是"a"。
如果最小的字符不够用,比如"axy"分成两部分。那么其结果一定是"ay"和"x",即把后续的字符安插在前字符较小的部分上面。这个非常容易理解。

如果第一个字符都相同,对于后面的字符,有两种模式。第一种是将"aabbbbb"分成两部分,我们会尽可能的将后续的b平均分配,以保证最大值最小。第二种是"aabbbbbcxyz"分成两部分,此时我们会尽可能的将后缀"bbbbbcxyz"分配到一个子串里,这样的字典序才能最小。

如果理解不了的话,可以看一下Code里面的Test Cases

D. Phoenix and Science

题意

在最开始,有一坨细菌,这一坨细菌里面有1单位的细菌。

每天早上,我们可以把一坨细菌分为两坨,也可以保持现状。如果我们有很多坨细菌,我们可以只分割一部分,其它的保持现状。
每天晚上,每一坨细菌会自然生长,每一坨增加1单位的细菌。

问想要获得n个单位的细菌,最快需要几天,应该如何操作。

2 <= n <= 10^9

解法

因为把一坨细菌分割并不会影响总数,只会影响每天晚上自然生长的速率。

所以一个简单的思路就是前期尽量指数增长,后期猥琐发育直到总数到n为止。

问题来了,我们什么时候应该猥琐发育。

假设在某一个白天,我们有k坨细菌,细菌的总数为v。

  1. 如果k <= n - v <= 2 * k,我们就可以只分割部分细菌坨,在一天结束的时候产生n - v个细菌。
  2. 如果2 * k < n - v <= 4 * k,我们在这一天就不能分割所有的细菌坨,这会导致产生的总细菌数超过n。所以我们在一天分割部分细菌坨,保证下一天我们能处于上面的状态(状态1)。
  3. 如果n - v < k,如果出现了这种情况,意味着我们做错了。。。

[POJ-3449] Geometric Shapes

Problem
Code

题意

给定正方形,矩形,线段,多边形的坐标。求这些图形是否相交。

  • 正方形:给定对角线坐标(正方形可以不是正的)
  • 矩形:给定三点坐标 (也可以不是正的)
  • 线段:给定两点
  • 多边形:多边形所有点

Codeforces - 1341E Nastya and Unexpected Guest

Problem
Code

题意

在一条长度为n的线上有m个点d1, d2...dm,其中d1 == 0, dm == n。

一个人从d1(0点)出发向前奔跑,当他到达另一个点的时候可以变换方向(+1方向或-1方向),在两点之间方向不能变化,也不能跑出[0, n]的范围。一个时间单位里,人可以移动1个单位距离。

又给定一个红绿灯,绿灯持续时间为G,红灯持续时间为R。当绿灯亮起时,一定处于奔跑状态。当红灯亮起时,人必须位于一个点di,然后在di点等待R时间。开始奔跑时,绿灯恰好亮起

问人从d1(0点)奔跑到dm(n点),最小需要多少时间。

注意:只要人奔跑到了dm点,就算完成任务,不需要一定要在红灯亮起的时候”恰好“到达。

解法

简单题和难题的一个重要区别是,简单题的思路相对单一,如果掌握了其背后的性质或不变量就可以直接解题。难题的思路相对多元化,即使你做出了一些可行的尝试,也不一定是可以用来解决问题的那一些。

本题的一些性质:

  1. 相邻的两个点形成的路径一定被跑过奇数次,否则就不能到达终点(没啥卵用)
  2. 假设dx点可以在G时间之内到达终点,那么最后的一组可行解为k * (R + G) + dm - dx。在dx点出发之前,一定经过k轮的绿灯+红灯

性质1是完全正确的,但是它对于解题并没有什么帮助。虽然我们只枚举奇数,但是枚举每一条路径走过的次数仍然是O(G)的。整体的时间复杂度不符合要求。

再考虑性质2。其实在整个过程中,人的状态可以由一个dp[p][t] = k来表示,意味着在点p时,消耗了k * (R + G) + t的时间。这个空间复杂度是O(m * G)的。如果我们能保持时间复杂度也为O(m * G),那么就找到了一个可行的解决方案。

但是问题来了,我们实际上在解决一个最短路问题。而最短路的时间复杂度明显不符合要求。这里引入了一种01-BFS的算法。由于在状态转移中,k的值的变化只能为0或+1。当k值变化为0时,我们可以将其直接放到队列头部,而不会影响最终的结果。(01-BFS请参考这里,随便找了一个解释)

感觉这题其实就是为01-BFS量身定做的,如果之前不知道这个东西,这题大概是没有思路的吧。。。

Leetcode 1388. Pizza With 3n Slices

Problem
Code

题意

给你一个循环数组,数组长度为3*N。每一轮拿走一个数,并删除和它相邻的两个数(然后把剩下的元素再结成环)。

问N轮之后,你能取走的所有数之和最大为多少。

解法

这题其实是道结论题,我就非常好奇这些结论都是哪里来的。。。

首先我们要证明取走N个任意非连续的数都是可行的。因为3N个数里面,我们要取走N个,剩下2N个都是被删除的。又因为有至少一个要取走的数的左邻居或右邻居有至少2个要被删除的数,所以删除这个数之后,我们就把原有的问题缩减了规模。

例如:N = 2时,有排列"XOXOOO"和"XOOXOO"(X为要取走的数,O为要删除的数)。此时一定有足够多的O可以被删除。

剩下的就比较简单了。只需要注意数组是循环的,第一个和最后一个数不能同时取。

CodeForces - 825F String Compression

Problem

Description

我们可以把一个字符串S表示为:s_1 a_1 s_2 a_2 ... s_k a_ks_i代表一个子串,a_i是一个整数,代表这个子串重复的次数。

例如"aa"可以表示为"a2",也可以表示为"a1a1"。"bb...b"(10个b)可以表示为"b10"。

S的长度最大为8000。问使用这种方法,能表示字符串S的最小长度。

Solution

现在O(n^2)的算法,n=8000都能过了?真是活久见啊。

这题的思路非常简单。dp[i]代表前i个字符的最小表示长度。compress(S[i...j])代表子串的最小表示长度,这个可以使用KMP算法很容易的实现。

所以DP公式可以写做: dp[j] = min(dp[j], dp[i] + compress(S[i + 1 ... j])

BUT,有了DP公式并不是本题的全部,我们要看到除了DP公式之外的部分才能提高解题的正确率。

对于compress(S[i...j])函数,我们既要考虑到子串有循环节的情况(KMP经典应用之一),又不能忽视子串没有循环节的情况(如"abc")。

本题的一个特殊之处是,构造数据比较困难。所以我们要从理论上多进行打磨,避免卡题。对于构造数据比较容易的题目,可以双管齐下。不过理论上的证明总比写对拍程序要省时间。。。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

#define print(x) cout << x << endl
#define input(x) cin >> x

const int INF = 0x3f3f3f3f;

char buffer[8888];

vector<int> kmp_next(const char* s, int n) {
    vector<int> res(n + 1, -1);

    for (int pre = -1, suf = 0; pre < n && suf < n; /* pass */) {
        if (pre == -1 || s[pre] == s[suf]) {
            suf++;
            pre++;
            res[suf] = pre;
        } else {
            pre = res[pre];
        }
    }
    return res;
}

int intlen(int x) {
    if (x == 0) {
        assert(false);
    }
    int len = 0;
    while (x) {
        len++;
        x /= 10;
    }
    return len;
}

int compress(const vector<int>& kmpnext, int l, int r) {
    int m = r - l + 1;
    int res = m + intlen(1);
    
    if (m % (m - kmpnext[r]) == 0) {
        int loop = m - kmpnext[r];
        res = min(res, intlen(m / loop) + loop);
    }
    return res;
}

int solve(const string& s) {
    int n = s.size();
    vector<int> dp(n + 1, INF);

    dp[0] = 0;
    dp[n] = n + 1;

    for (int i = 0; i < n; i++) {
        int m = n - i;
        vector<int> next = kmp_next(s.data() + i, m);
        dp[i] = min(dp[i], i + 1);
        for (int j = 1; j <= m; j++) {
            assert(next[j] >= 0);
            dp[i + j] = min(dp[i + j], dp[i] + compress(next, 1, j));
        }
    }

    /*
    for (int i = 0; i <= n; i++) {
        printf("%d ", dp[i]);
    }
    puts("");
    */

    return dp[n];
}

void test() {
    assert(solve("xxxcac") == 6);
    assert(solve("ababcaababcbac") == 14);
    assert(solve("aaaabbccdabababa") == 12);
    assert(solve("bbbacacb") == 7);
    assert(solve("abaabacccccccc") == 6);
    assert(solve("abaaba") == 4);
    assert(solve("a") == 2);
    assert(solve("abccd") == 6);
    assert(solve("abcc") == 5);
    assert(solve("abbcc") == 6);
    assert(solve("abcab") == 6);
    assert(solve("aaaaaaaaaa") == 3);
    assert(solve("cczabababab") == 7);
}

int main() {
    test();

    scanf("%s", buffer);
    print(solve(string(buffer)));
    return 0;
}

GCJ2020-Round1A

A. Pattern Matching

Problem
Code

题意

给定一些(2 <= N <= 50)字符串Pattern,Pattern中含有通配符(*),通配符可以匹配任意字符(可以为空)。求任意一个字符串,能匹配所有的Pattern。如果不能的话,输出"*"。

例如:

"*FOO"可以匹配"FOO", "ABCFOO",但是不能匹配"FXX"或者"FOOXYZ"。

Pattern的长度最大为100。每个Pattern至少有一个通配符(*)。
所求字符串的长度最大为10000。

思路

本题是一道掩盖的很深的签到题。

基本思路

由于所求的字符串长度远大于所有Pattern拼接起来的长度之和,所以我们对于*abc**bcd*这种情况,直接拼接成abcbcd。虽然长度上不是最优,但是也不算错。

进一步思路

我们把原字符串根据星号分割开,例如abc*cd*ef就可以分割成三段[abc*], [*cd*], [*ef]。然后在不同的字符串之间,按段匹配。

对于下面几种场景分类讨论:

  1. abc*
    只位于字符串的开头。意味着我们有一个确定性的前辍。所以我们需要在确定性前辍中找到一个最长的公共前辍。如果找不到,就算了(误
    对于前辍后面的字符串,因为我们有一个通配符,可以无视。
  2. *abc
    位于字符串的结尾,意味着一个确定性后辍。和场景1一样,需要找到一个最长的。找不到的话,则表示无解。
    对于后辍前面的字符串,同样可以无视。
  3. *abc*
    位于字符串中间,因为其前后都有星号。所以我们把这些Pattern去掉两端的星号都连起来,则一定有一个可行解。
    对于前辍和后辍,因为有通配符,无视之。
  4. abc(没有*
    由于题目描述中说明了Pattern必有一个通配符,所以这种情况不存在。

我们把前辍(1),后辍(2)和中间字符串(3)直接拼接起来,就完成了这一段的匹配。

举例:[*abc, *bc*, *cd*, *def]的前辍是abc,后辍是def,中间是bccd或者cdbc随你开心。拼接起来就是abc+bccd+def

Corner Case

如果不同的字符串段的个数不一样,我们可以在里面插入**用作占位符,反正也不会错。

CodeForces - 469D Two Sets

https://vjudge.net/problem/CodeForces-469D

题意

给定N个不同的整数p[1...n],和两个整数a和b。现在有两个集合A和B,规定:

  • 如果x属于集合A,那么a - x也一定属于集合A
  • 如果x属于集合B,那么b - x也一定属于集合B

问是否能把p[1...n]分别放到两个集合当中去。

解法

简单场景:

  1. (x, a - x)不存在,并且(x, b - x)不存在时。无解。
  2. (x, a - x)不存在,但是(x, b - x)存在。则(x, b - x)划归到B集合。
  3. (x, b - x)不存在,(x, a - x)存在。划归到A集合。

复杂场景:
x, a - x, b - x都存在于数组p中。此时我们应该将其划分到哪个集合当中呢?
假设x被归到A集合中,那么a - x一定也在A集合中。而b - x也只能在A集合中。因为如果x在A集合,b - x在B集合,显然不符合条件。
所以如果该场景出现,那么一定有x, a - x, b - x都在同一个集合内。

所以本题我们使用并查集,建立两个虚拟元素A和B,代表集合A和B。
对于简单场景,我们会把相应元素与A或B相连。对于复杂场景,我们把x, a - x, b - x相连。
如果最终结果A与B相连,则无解。反之,则输出元素所在的集合;如果元素不属于任何一个集合,则随意指定A或B即可。

Code

POJ 2289 - Jamie's Contact Groups

Problem
Code

Code上面是之前打比赛的时候写的,下面是更新后的版本。这两种本质上没区别,只是后面的写法更工程一点。

题意

给定N个人和M个组。一个组可以包含多个人,但是一个人只能在一个组里。(一对多)

给出这N个人可能属于哪些组。现在规定每个组不能超过K人,且每个人在且仅在一个组里,求K的最小值。

N <= 1000, M <= 500

解法

本题非常直观的是一个二分图匹配+二分答案的问题。难点在于本题是一个多重匹配,不能简单的使用基本的匈牙利算法。

基础的匈牙利算法,时间复杂度是O(VE)。如果我们使用拆点的话,复杂度大概会是O(VE * M^2)。不符合我们的要求。

使用多重匹配,我们只增加了顶点数,没有增加边数,所以复杂度为O(VE * M)。

拓展

POJ-2112 Optimal Milking, Code

Leetcode Biweekly Contest 25

Contest

1431. Kids With the Greatest Number of Candies

日常水题,略

1432. Max Difference You Can Get From Changing an Integer

Rule可以这样规定。
取最小值的话,把第一个不是01的数位,替换成0或1(取决于是不是第一位)。
取最大值的话,把第一个不是9的数位,替换成9。

这题挺好的,corner case很多,但是每一个都是必须要考虑到的。

1433. Check If a String Can Break Another String

把两个字符串都排序,看是否有一个串在所有位置上都大于等于另一个串。

简单的贪心,证明就略吧。

1434. Number of Ways to Wear Different Hats to Each Other

DP好题。

DP的根本意义在于确定正确的归纳假设。但是一个正确的归纳假设有时候并不是最高效的那个。
本题最直接的DP思路是:DP[i][j],i代表第几个人,j代表现在已经用过了几个帽子(二进制压缩)。这样的问题在于j的取值空间太大了:C(40, 10) > 8 * 10^8。强行用C++搞也没准能过,但一定不是正解。

我们考虑把i和j换过来,即i代表第i个帽子,j代表戴这i个帽子一共有哪些人(已有的帽子可以不戴,使用二进制压缩)。这样i的空间是40(帽子种数),j的空间是2^10(最多10个人)。这样虽然非常反直觉,但是其效率可以大大的提升。

此题为我们提供了一个非常好的DP思路。(CF的DP专题刷起来,笑)

题目打分

  • \1431. Kids With the Greatest Number of Candies
    一星,垃圾
  • \1432. Max Difference You Can Get From Changing an Integer
    四星,每次这种小思路+corner case题总是能搞我一把
  • \1433. Check If a String Can Break Another String
    一星,没新意
  • \1434. Number of Ways to Wear Different Hats to Each Other
    五星,DP新思路

Leetcode Weekly Contest 185

赛后补题,用时36分钟。

1417. Reformat The String

Code

简单题。三种情况:数字和字母数目相同,数字比字母多一个,字母比数字多一个,分类讨论即可。

做CF让我总觉得这种题背后有更大的阴谋,幸好本题是没有的。

1418. Display Table of Food Orders in a Restaurant

Code

纯实现题。搞几个Hash表做好映射,最后把输出的表格填好即可。

1419. Minimum Number of Frogs Croaking

Code

我又双叒叕看错题了。。。

因为本题只有有解和无解两种情况,所以可以直接用贪心。

方法1,搞5个set<int>记录"croak"五个字母的位置,然后每一轮贪心的去组合"croak"字符串,直到找不到为止。贪心的轮数就是最终的结果,如果不能用光所有的字符或者不能完整的组成字符串,返回-1。

方法2,搞5个计数器,每个计数器代表有多少只青蛙喊到了某一个字母。在一个新的字符到来时,例如"r",我们就去它前一个字符("c")的计数器里面-1,然后加到当前("r")的计数器里。
如果新的字符为"c",并且"k"计数器为空,我们会加入一只新的青蛙。在其他情况下,如果之前的字符计数器为空,那么直接返回-1。最后检查一下是否有青蛙没有喊完所有的字母。

方法1的时间复杂度是O(n * logn),方法2的时间复杂度是O(n)。而且方法2的代码还简单。

1420. Build Array Where You Can Find The Maximum Exactly K Comparisons

Code

吐槽:Leetcode觉得只有DP是难题吗?这种DP是真的没有难度。

我们维护一个状态dp[n][pre][cost]。意味着现在还有n个数,在之前遍历到的数组中最大的数为pre,现在的"search cost"值为cost

进行状态转移是非常容易的。不会状态转移也没有关系,DFS总会吧,记个状态随手就过了,只是慢一点而已。

题目打分

1417. Reformat The String

2星,需要一点小思路。

1418. Display Table of Food Orders in a Restaurant

2星,这种纯实现题其实可以用来练练手。

1419. Minimum Number of Frogs Croaking

4星,思路比较相对比较新。不能只靠蛮力做了。

1420. Build Array Where You Can Find The Maximum Exactly K Comparisons

3星,Leetcode的DP题基本就是鸡肋。不做觉得亏,做了觉得更亏。想做DP专题训练的可以攒一块做。

Leetcode Weekly Contest 184

一共4题。赛后补题。

1408. String Matching in an Array

Problem
Solution

暴力堆一下就行了。没意思

1409. Queries on a Permutation With Key

Problem
Solution

正解可能是树状数组,能做到查询更新都是O(logn)的。
乱搞的方法就是用vector暴力,效果一般都出奇的好。

1410. HTML Entity Parser

Problem
Solution

暴力字符串替换,想秀一下的可以用AC自动机(讲真?)。
小心"&"在替换后可能会干扰下一次替换,解决方案是调整顺序,或者加placeholder。

1411. Number of Ways to Paint N × 3 Grid

Problem
Solution

本场比赛唯一一道比较正常的题。先暴力枚举在一行中可行的所有状态。
然后按层DP,排除非法情况就行了。

总结

做Leetcode会给你一个错觉,就是一道题你看三分钟一定能看出来结果。而且实现起来也不复杂。

这会对你的大脑造成不可逆的损伤。珍爱生命,远离Leetcode.

Leetcode Weekly Contest 188

Contest

A. Build an Array With Stack Operations

随时保持栈内元素与最终结果的前辍一致即可。

B. Count Triplets That Can Form Two Arrays of Equal XOR

给定数组arr,求有多少个i, j, k,满足xorsum(arr[i...j-1]) == xorsum(arr[j...k])

首先,如果xorsum(arr[i....k]) == 0,则对于任意j,必有xorsum(arr[i...j-1]) == xorsum(arr[j...k])。

所以只需要判断有多少xorsum(arr[i...k]) == 0即可。

C. Minimum Time to Collect All Apples in a Tree

如果一棵子树有苹果,那么我们一定要访问这棵子树的根,并且会从子树的根返回整棵树的根。

所以我们计算有X个子树有苹果,最终的结果就是X - 1。

D. Number of Ways of Cutting a Pizza

方形批萨饼可以横着切或者竖着切,每一刀切完之后的上半部分或者左边部分会交给客户,剩下的部分可以接着切。

问有多少种切法,可以让每一片饼都有一个苹果。

每一刀切完后,我们都可以用剩下的饼的左上角坐标标记当前的状态。所以dp[y][x]=k代表当前剩余饼的左上角坐标为(y, x)时,已经分给k个用户包含至少一个苹果的饼。

题目打分

  • A题:2星,可做
  • B题:4星,有新意,需要一点深入思考
  • C题:2星,日常树上DP/DFS
  • D题:5星,DP好题

Codeforces 1341F - Nastya and Time Machine

Problem
Code

题意

给定一棵有N个节点的树。一个人从节点1开始遍历这棵树,此时时间为0。直到所有的点都被遍历过,并且人回到了节点1,遍历结束。从一个节点走到相邻节点,花费时间1。

如果人在节点k,此时的时间为t,那么我们将这个状态记作{k, t}
现在我们有一个时光机,我们可以将{k, t}状态倒回到任意{k, t1},0 <= t1 < t。使用时光机有一个限制,所有的{k, t}状态不能重复,否则会导致宇宙的坍缩。

问遍历完成这棵树最短需要多少时间。

解法

为什么不直接看题解呢?

我们可以通过范例了解一种遍历树的方式。

看不懂就照着样例自己走一遍,这里没有高科技。

我们可以看到,每一个度为d的节点,最多会被访问d次。那么我们将这d次的时间值记为[0, 1, 2 ... d],就可以解决这个问题了。

这个问题思路还是比较简单的,问题在于实现。因为我们可以在任意的状态进行时间回溯。肯定要设计一种策略。

假设我们有节点p,第一次访问这个节点的时间为t。那么我们访问其子节点时,可以要求其返回时间为[1, 2 ... t - 1, t + 1...d]。

剩下就是DFS的实现了。这题本质上是一道实现题。

AtCoder Beginner Contest 160

Code
Contest

D. Line++

题意

给一个无向图G,有N个顶点,编号从1~N。
对于点1 ~ N-1,一定有一条从顶点i ~ i+1的边。对于给定的点X和Y,也有一条边。

问对于每一个k(1 <= k <= N - 1),有多少个点对(i, j),使得i到j的最短路径为k。

  • 3 <= N <= 2000
  • 1 <= X, Y <= N
  • X + 1 < Y

解法

方法1(正规解法)

不考虑边XY,那么任意两点之间的距离为两点编号的差的绝对值 。
考虑边XY,两点之间(记为A、B)的距离为以下三者的最小值。

  1. 两点编号差的绝对值
  2. A->X + X->Y + Y->B
  3. A->Y + Y->X + X->B

时间复杂度为O(N^2)

方法2(套模板)

因为每条边的边长为1,所以可以将其视为一般的迷宫,使用BFS flood fill(洪泛)法求解。

时间复杂度也是O(N^2)。

E. Red and Green Apples

题意

你挑选X个红苹果和Y个绿苹果。
A个红苹果分别有权值p1....pA。B个绿苹果有权值q1....qB。还有C个无色苹果,权值为r1....rC。
无色的苹果可以被涂成红色的或者绿色的。

问挑选出来的苹果的权值和最大为多少。

  • 1 <= X <= A <= 1e5
  • 1 <= Y <= B <= 1e5
  • 1 <= C <= 1e5
  • 1 <= pi, qi, ri <= 1e9

解法

不考虑无色苹果,我们先出红苹果和绿苹果中权值最高的X和Y个即可。

考虑无色苹果的话,我们需要替换掉最多C个权值最小的红苹果或/及绿苹果。

简单排序即可。

F. Distributing Integers

题意

有一个N个节点的树,节点编号为1~N。

从树中选定一个编号为k的点,将其标记为1。然后在其相邻的任意点,标记为2。以此类推,直到标记树上的所有点。

问对于每一个k (1 <= k <= N),一共有多少种不同方法标记这棵树。

  • 2 <= N <= 2e5

解法

我们首先假设根是固定的。对于一棵(子)树,我们首先会把可用的标记分配给其子树,此时记录分配的种类数。然后向子树递归,求子树的标记方法。最后将结果相乘即可。

伪码如下:

def f(cur):
    res = 1
    nodes = 1
    for next in g[cur]:
        subtree_size = tree_size(next)
        nodes += subtree_size
        res *= f(next) * C(nodes - 1, subtree_size)
    return res

递归求解即可得出答案。

接下来需要考虑根是可变的。

错误方法:直接DP(TLE)

由于根是可变的,所以对于每一棵子树,我们需要记录其根节点以及根节点的父节点。整棵树的父节点不存在,记为-1。

所以一共有2 * (N - 1)棵子树(每一条边的两个方向)。由于这个数据规模并不大,我们就会产生错觉,认为可以直接做DP,记录所有子树的值进行状态转移。

问题在于,状态数是有限的,但是状态转移数是O(N ^ 2)的。会造成程序的超时。

正确方法:换根DP

首先计算以0点为根的情况。

然后递归计算每一棵子树的答案。首先将父节点从树上取下来,然后将父节点插入做为新的子节点。这两个操作只限于计算,不涉及树的结构的改变。

参考链接

AtCoder Beginner Contest 159

Code
Contest

D. Banned K

题意

有N个球,每个球上有一个数字Ai。

从这N个球中取一个球i,问剩下的N-1个球里面,有多少对数字相等的球。

求i = 1...N时的所有结果。

3 <= N <= 2*10^5

解法

如果单纯求某一个结果的时候,这道题就非常简单。我们只需要用一个map统计一下相同的球即可。

但是本题要求1...N的所有结果。这样我们就要尽可能复用之前计算的结果。

可以说,这题是AtCoder ABC160 F题的超级简化版。都是利用f(i)的结果,在较低的复杂度下推出f(i + 1)的结果。

做法就是先求i = 1,然后维护map,并且维护当前的结果。难度较低,这里不展开了。

E. Dividing Chocolate

二进制枚举,Leetcode经典Hard题,略。

F. Knapsack for All Segments

题意

给定一个数组A[N],和一个整数S。
规定F(L, R)的含义是在子数组A[L...R]中,加和为S的子序列的个数。

求在这个数组里所有F(L, R)之和(模998244353)。

1 <= N <= 3000
1 <= S <= 3000

解法

这题的最标准(naive)做法就是开一个三维DP[l][r][sum],代表在A[l...r]中,加和为sum的子序列的总数。但是在这道题是不行的,会TLE+MLE。所以我们要想办法降低DP纬度。

我们可以只考虑子序列的右边界,DP[r][sum]意为:加和为sum的,并且右边界到r的子序列的种类。这个DP是可以在O(n^2)的复杂度下做的。
这里需要注意一点,如果某一个数A[i]是某一个子序列中的第一个数,它自带i个子序列的种类(数组下标从1开始)。因为对于这一个子序列,其左边界可以有i种。所以要记得加上。

AtCoder Beginner Contest 161

Contest
Code

D. Lunlun Number

题意

如果一个数的所有相邻位的差的绝对值不大于1,那么称它是一个合法的Lunlun数。

求第k大的lunlun数。

解法

数位DP**。按位枚举即可。

E. Yutori

题意

一个人要在N天里随意选K天进行工作。现在已知有一些天他不能工作。又已知他在工作一天后,需要休息C天。

问在哪些天,这个人一定在工作。

解法

首先我们进行DP,dp[i]意味着在第i天,这个最多能工作几天。
我们把这个DP正向反向进行两次,获得dp1和dp2。如果dp1[i - 1] + dp2[i + 1] == k - 1,那么意味着在第i天,他一定在工作。否则不能满足工作K天的条件。

F. Division or Subtraction

题意

给你一个数N。让你随意选定一个数K。

然后规定以下操作:

def getlast(N, K):
    while N % K == 0:
        N /= K
    return N % K

问有多少个K可以满足getlast(N, K) == 1

解法

分三种情况讨论:

  1. n = k ^ a
  2. n = a * k + 1
  3. n = t * k and t % k^a == 1

2和3好像重了。。。

Codeforces Round #637 (Div2)

Contest
Code

比赛出ABCD,排名516。

E题和F题暂时没时间看,处于弃疗状态。

A. Nastya and Rice

题意

给定N个物品,质量在[a, b]之间(闭区间)。问这N个物品的总重是否在[c, d]之间(闭区间)。

解法

N个物品的总质量在区间[N * a, N * b]之间,看是否与[c, d]相交即可。

B. Nastya and Door

题意

给定一个整数数组A,问数组中是否有一个连续的子数组A[l, l + k - 1],使得子数组包含的山峰最多。

“山峰”指的是子数组中,满足A[i - 1] < A[i] > A[i + 1]的值。子数组的第一个和最后一个值一定不是山峰。

解法

双指针,用O(1)的复杂度进行子数组的状态转移。然后取最大值即可。

C. Nastya and Strange Generator

题意

这题我真是看了10分钟才明白啥意思。。。

给定一个空数组A[n]。规定以下n轮以下操作:

  1. 假设当前轮数为k
  2. 创建数组R[n],R[i]等于A[i...n]子数组中第一个没有值的位置的下标。如果找不到的话,则R[i]为空。
  3. 创建数组Count[n],Count[i]等于数组R中值为i的数字的个数
  4. 找到Count数组里面的最大值的位置,如果这个A[i]在位置上没有值,则A[i] = k。如果有多个最大值,则可以任意选择一个位置。

具体可以看题面中的Sample。。。

问给一个数组A,问这个数组能否由上面的操作构造。

解法

假设我们有数组A=[x, x, x, x, x]。(x代表空值)
按照上面的步骤。第一步我们得到的R[n] = [1, 2, 3, 4, 5],所以Count[n] = [1, 1, 1, 1, 1]。这意味着第一步我们可以在任意位置赋值1。假设我们在第3位赋值。
第二步我们有A = [x, x, 1, x, x],所以R[n] = [1, 2, 4, 4, 5]Count[n] = [1, 1, 0, 2, 1]。这意味着我们只能向A[4]赋值2。

我们可以手动模拟后面的步骤,然后得出结论:

  1. 一个合法的A数组的一定有一个A[i...n] = 1 ... n - i + 1的后辍。
  2. 把这个后辍排除掉之后(记作A1),A1数组一定有一个A[j...i] = n - i + 1 ....后辍。
  3. 每次检查新数组的后辍,直到数组不符合条件或数组为空。

D. Nastya and Scoreboard

题意

给定一个记分牌,上面有n个8字灯(可以想象一下老式的计算器),每个灯上有7个小灯带。现在踢它一脚(可以用代码模拟,雾),其中的一些小灯带发生了故障会保持常亮。

问再点亮额外k个小灯带,能形成的最大长度为n的合法整数是多少。

1 <= n <= 2000, 0 <= k <= 2000

解法

如果想要形成的整数最大,那么肯定第一位越大越好(9xx就比8xx要大对不对)。但是有很多情况第一位不能为9,例如保持常亮的灯带让我们不能形成数字9,或者如果我们第一位为9,后续不能形成合法的数字。

所以我们穷举当前的一位,使其尽量大,然后判断剩下的位数与剩余的额外灯带是否能形成合法的数字。

DP思路,但是有一点卡时限。

一个不是trick的小trick。
如果我们用DFS来实现DP的话,有可能因为递归的低效性被卡TLE。于是我们可以手动调用DFS,以减少其递归深度,达到加速的效果。

Leetcode Biweekly Contest 24

赛后补题,耗时26分钟。

1413. Minimum Value to Get Positive Step by Step Sum

Problem
Code

前辍和,处理一下就行了。

1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K

Problem
Code

略有作弊嫌疑,做之前看了别人的讨论。

简单证了一下结论:

  1. 选取的fib数可以不重复
    例如,2 * fib[n] = fib[n + 1] + fib[n -1],并不会让情况变的更坏
  2. 如果有多组可行解,那么选取最大fib数的一定是最优解
    非常容易理解,两个小的凑一个大的,一定更优

1415. The k-th Lexicographical String of All Happy Strings of Length n

Problem
Code

做这题的时候慌了一下,大概用了15分钟+。
一开始忽略了happy string这个条件,一通乱做。后来又想用数位DP的方法搞,但是复杂并且不太可行。
后来想到如果某一位定下来了,它后面的位数的排列总数是一样的。然后一位一位往后推导就可以了。
例如第一位是a,第二位肯定是[b, c]当中的一个。第三位肯定与第二位不同,但可以和第一位相同。经过计算,第[k...n]位的排列总数为2^(n - k)。

1416. Restore The Array

Problem
Code

简单DP。没啥说的。用DFS可搞,而且思路更明确。

我一直就说Medium题比Hard题难。

本场比赛题目打分

A题,1星。(不值得做)
B题,4星。(非常值得做)
C题,5星。(一定要做)
D题,3星。(没啥新意,可以做)

Leetcode Weekly Contest 193

Contest

Code

A. Running Sum of 1d Array

前辍加和,略。

B. Least Number of Unique Integers after K Removals

贪心,统计每个数字出现的次数。尽可能把出现次数最少的删掉就行了。

C. Minimum Number of Days to Make m Bouquets

二分,枚举第k天花开的情况。

D. Kth Ancestor of a Tree Node

Tarjan算法的简化版。

我ZZ的想去用DFS模拟DP(其实可行,就是不太好写)。结果放弃了,还是用普通DP的方法来做。

DP[i][j] = DP[DP[i][j - 1]][j - 1]

DP[i][j]代表节点i的第1 << j个祖先。所以i的第1 << j个祖先,就是i的第1 << (j -1)个祖先的第1 << (j-1)个祖先。

可以顺手学习一下LCA问题的tarjan算法。

Codeforces Round #633 (Div. 2)

比赛ABC,赛后DE。排名4025。

我是**。

A - Filling Diamonds

Code

题意:

给定一个钻石状图形,让你用小四边形(n=1)进行填充。问有多少种不重复的填充方案。

解法:

根据思考可得,只要确定了直立小四边形的位置,其它小四边形的位置就已经确定了。
所以对于任意图形,有且仅有n个直立小四边形的位置。

B - Sorted Adjacent Differences

Code

题意:

给定一个整数数组a[n],数组中的数可能有重复。让你对这个数组进行排序,使得: |a1−a2|≤|a2−a3|≤…≤|a_n−1−a_n|。保证一定有解。

解法:

易知数组里面最多有一组重复的数。否则不能保证一定有解。

如果数组里面有重复的数(记为v),且重复k次。那么a[1..k] = v。
然后我们在a[1..k]的左边找到一个数a[k+1],然后在a[1..k+1]的右边找到a[k + 2]。此时一定有| a[k+1] - a[k] | <= | a[k + 2] - a[k + 1] |。之后依次向两边扩展。

对于没有重复的数的数组,我们可以找到它的中位数,然后依次向两边扩展。

C - Powered Addition

Code

我真的是**

题意:

有一个整数数组a[n],每一秒可以执行一次如下操作:选择任意个(或0个)元素,a[i] += 2^(x - 1)。x为当前时间(秒),从1开始。

问最少多少秒之后,可以让数组a[n]变为不递减(nondecreasing)的。

解法:

如果我们在每一秒都向数组的最后一个数a[n]进行修改,也不会破坏“不递减”的性质。之后再向前一个数a[n-1]加尽可能大的数以维持性质。

所以我们用二分法,假设所需要的时间为T。然后更新数组,看是否能满足不递减的性质。

注意最大的加数为(1 << T) - 1

D - Edge Weight Assignment

Code

题意:

给定一棵有n个节点的树。现在让你给这棵树的每一条边赋值x(x > 1),使得任意两个叶子节点之间的简单路径的xor和为0。

求最少/最多使用多少个数可以满足以上条件。

解法:

[官方解法]

先考虑最小值。我们从最简单的n=3的情况开始。对于n=3,一定是以下的情况。

此时树上叶子节点的路径只有一条,并且xor值为0。

如果我们要把两个n=3的树合在一起,会有如下两种情况。

  1. 左边的子树从叶子到根的路径xor为1,右边的根到叶子的路径xor为0。因为边的权值不能为0。所以我们只能填入2和3。
  2. 左边和右边完全对称,填入1即可。

我们继续推导,可以(猜测)得出一般情况下,所需要权值种类最小值为3。而在所有叶子节点的深度相同的(特殊)情况下,最小值为1。

再考虑最大值。

在这种情况下,每个不同子树从根到叶子的路径xor值都不相同。

所以只需要保证当前子树根节点直接连接的叶子节点权值保持一致即可。

思路扩展:可以将本题转化为,所有节点有一个权值,并且邻居节点之间的值一定不同。

E - Perfect Triples

Code

题意:

有一个无限长的序列s。其构建规则如下:

  1. 找到3个正整数a, b, c。使得a ^ b ^ c == 0,且a, b, c当前都不在s当中。
  2. 如果有多组情况,选择字典序最小的(a, b, c)
  3. s += [a, b, c]。并重复以下操作

问序列第n个值是多少。

解法:

OEIS大法失效,直接打表看结果。其特性如下:

  1. 每4^k个三元组为一大组,每组的第一个数字为4^k...(4^k) * 2 - 1
  2. 因为要保证字典序最小。每个三元组的最高位排列都是(01, 10, 11)。
  3. 后面的比特位都是('00', '00', '00'), ('01', '10', '11'), ('10', '11', '01'), ('11', '01', '10')这样的排列。每4次为一个循环。

找到规律直接搞就行了。

官方题解给了数学归纳法的证明。并且说OEIS是可行的(?)。

Educational Codeforces Round 85

比赛AB,赛后CDE。排名4736(?)

**石锤了。

A. Level Statistics

Code

题意

有一个游戏,在特定的时间点会有两个统计信息:

  1. 有多少人次玩过游戏
  2. 有多少人次通关游戏

易知,通关游戏的人次一定小于等于玩过游戏的人次(换句话说,每一次游玩都是独立的)。现在给定N个时间点的统计信息,问这些统计信息是否合法。

解法

遍历一遍,然后校验以下信息:

  1. 玩过游戏的次数和通关游戏的次数一定不会随时间减少
  2. 通关游戏的次数一定小于等于玩过游戏的次数
  3. 新增通关一定小于等于新增游玩

B. Middle Class

Code

题意

一个国家有N个人,编号为i的人有a[i]单位的钱。

现在可以执行一个操作:随意选择M个人,将他们的钱平均分。

问在任意次操作之后,最多有多少人的钱多于X。

解法

贪心的思路。把最富有的人的钱均分给穷人。

先对钱数a[]进行排序,编号从小到大枚举最多有多少人一起进行平均。然后再与X的值进行比较即可。

C. Circle of Monsters

Code

这题做不出来的**之处在于。明明B题都给了类似思路的提示了,然后你还在那里强行YY结论。。。

题意

有一个打怪游戏。玩家可以用枪打怪物,打一枪掉一点生命值。

N个怪物站一圈,每个怪物的生命值为a[i]。怪物的生命值降到0或小于0后,会自爆,对第(i + 1) % N个怪物造成b[i]的伤害。

问把怪物都打死最少需要几枪。

解法

易得如下结论:

  1. 攻击怪物一定是从某一个位置开始,依次进行攻击(因为需要利于怪物自爆的伤害)
  2. 如果开始攻击的位置定下来了,那么最终的结果是固定的

那么问题来了,从哪一个位置开始攻击最好呢?如果继续往这个方向思考下去,你会像我一样卡题(

现在换一个思路。如果我从位置0开始攻击到位置N-1,最终需要打K枪。那么我们从位置1开始攻击到位置N-1,需要打几枪?这个问题的答案很简单,我们把攻击怪物0的开销减掉,再加上怪物0自爆产生的伤害,就可以得到最终的结论了。如果我们再加上攻击完怪物N-1之后,攻击怪物0(怪物站成一个圈)所需要的开销,就可以得到从位置1开始攻击所需要的开销总和了。

所以,我们可以在O(1)的时间内,由位置M 推导出 位置M+1所需要的开销。总的时间复杂度是O(n)。

我是**

D. Minimum Euler Cycle

Code

题意

给你一个有N个顶点的有向完全图(每一对顶点之间有一对双向边相连)。

你从一个顶点开始遍历,一定能不重复的遍历所有的边,并且回到最初的顶点。我们将你所经过的顶点依次记录下来,就可以描述这一条回路。

我们找到字典序最小的回路P[],问这条回路上从第L到第R个顶点P[L...R]各是什么?

解法

本题当中的回路非常好构造,例如有n个点的图,则回路一定是:[1, 2, 1, 3, 1 ...., 1, n] ++ [2, 3, 2, 4 .... 2, n] ++ [3 ....] ++ [n - 1, n] ++ [1]

那么现在的问题就在于如何快速求第L项到第R项。再进一步说,我们要求出第L项的状态,后面的状态可以由递推得出。

首先我们要判断第L项是在第几段里。这个可以暴力求解,时间复杂度不会超过O(sqrt(n))。(二分也可以,但是没必要)

然后判断第L项在本段里是第几项。确定了位置之后,就可以往后进行推导了。注意边界条件即可。

PyPy要加输出外挂。

E. Divisor Paths

Code

看了官方题解

题意

给定一个数D。把D的所有因子都记做图中的一个点(包括1和D)。
如果因子x和y满足:x能被y整除,并且x/y为质数。则x和y之间有一条无向边。这条边的权值为x和y的因子数之差。

问给定图中任意两点A和B,问这两点间有多少条不同的最短路。(最短路指的是权值和最小的路径)

解法

要找到最短路的种类数,首先要找到一条最短路。

假设我们有两个点u和v,u = a * b * v(这里a,b为质数)。那么u和v之间的最短路一定有一条经过[u / a, u / a / b]。因为只有除以质数因子,才能保证两点之间的权值和最小。

所以对于任意两个点u和v,其最短路一定经过gcd(u, v)。并且每一步都只会除以质数因子。所以我们对u到gcd(u, v)所经过的质数因子进行全排列,再对v到gcd(u, v)的质数因子进行全排列,然后相乘即可。

优化:因为对于每一组数据,D是不变的。所以我们只需要保留D的所有质数因子。否则会TLE。

GCJ2020-Round 1B

A. Expogo

对于1 + 2^1 + 2^2....2^n最终的结果肯定是奇数。所以很容易确定第一个1是在哪个方向。
接下来把所有的值除2,就仍有1 + 2^1 + 2^2.... 2^(n-1),用第一步的方法解决之即可。

B. Blindfolded Bullseye

先随机找在圆内的点,然后做平行于x轴和y轴的垂线。用二分法确定垂线的两端,从而找到圆心。

C. Join the Ranks

ref: https://github.com/kamyu104/GoogleCodeJam-2020/blob/master/Round%201B/join_the_ranks.py

AtCoder Beginner Contest 165

Contest
Code

我是**。

E. Rotation Matching

题意

N个人编号1,2,3...N,两两比赛。
有M个比赛场地,每个比赛场地指定一组编号(a, b)。即编号为a和b的人在该场地进行一轮比赛。

每一轮比赛完成后,所有人的编号x都变为(x % N + 1)。问如何给场地进行编号,使得一个人在一轮比赛最多出场1次(可以不出场)。并且在N轮比赛中,不会有重复的对战。

看不懂描述可以去看原题,题意挺绕的。

解法

首先要看懂题。然后会有一个基本的想法:因为人与人之间的编号差值不会改变,所以每一个差值最多只能出现一次。

然后手写一下N=4,M=1的情况:

1 2 3 4
2 3 4 1
3 4 1 2
4 1 2 3

我们可以看到,选择(1, 2)和(1, 4)是可行的,但是(1, 3)不可行。(1, 2), (1, 4)的差值为1和3,(1, 3)差值为2。并且(1, 2)和(1, 4)不能同时出现。
这时就要修正上面的想法。第一,两人编号的差值有两个: |b - a| 和 |a - b + n|。这两个差值都不能重复。第二,|b - a| <= (n - 1) / 2,否则就会发生(a, b)和(b, a)两轮比赛的重复。
也就是说,对于N个人,我们最多能同时进行(N - 1) / 2场比赛,每场比赛(a, b)编号差分别为[1... (N - 1) / 2]。

现在的问题就变成了怎么样来着构造适当的(a,b)编号了。
假设我们从(x, x+1)开始向两边扩展,就可以构造出差为1,3,5..的场地。因为差值最大不能超过(n - 1) / 2,所以只会用掉一半的编号。另外一半我们从(x, x + 2)进行扩展,构造差值为2,4,5..的场地。

F. LIS on Tree

题意

给定一颗有根树,树的每一个节点都有一个值。问从根到叶子的路径中,最长上升子序列有多长。

解法

这题比较有新意,但是并不难。

使用栈+二分的方法求LIS应该是非常常见的。在树上做的问题在于怎么样维护这个栈。
因为栈在状态转移过程中可能会修改中间的值或者在后面插入值。所以我们在dfs的每一层记录下当前的操作,在返回上一层的时候恢复回来即可。

可以写一个类似回调的东西*一下。

Round 2 2018

Code
Contest

先快速写一个版本,后面再补齐吧

A. Falling Balls

我们把初始状态(n个1)和最终状态写在两行。然后画箭头将球进行转移。
如果所有的箭头不相交(可以有相同的目标点),那么一定有解。
可以很容易的观察得出,我们可以从左边开始,向右画依次箭头。最后把这种策略转化成题目要求的图即可。

B. Graceful Chainsaw Jugglers

DP可做。

又通过暴力枚举,我们可以观察到R和B的基本是连续的,也就意味着最大值不会超过sqrt(n)。所以在O(n * n * n)的复杂度下可做。

我觉得一般情况下就不要乱猜结论了。。。浪费时间还做不对。。。

C. Costume Change

二分图裸题。

为啥我就看不出来呢?。。。

D. Gridception

先写我自己的做法,标程比我的优一个数量级。后面补。
这题是不难的,只要不写挫了就行。

观察得出,最终结果一定是原图2x2的格子经过放大之后形成的pattern。每一个原图的格子放大到n * n个格子即可。(假设n > m,这样可以足够包裹之前的小矩形了)

因为2x2的格子最多有2^4=16种可能。我们在原矩阵中枚举这些可能性,然后对其进行放大。然后把原矩阵放到放大后的矩阵进行匹配,然后在其中找到最大的联通图形。

最大的时间复杂度是(假设 n > m):

  1. 16种可能
  2. 每个大矩阵的size是 (2 * n)^2,所以在大矩阵中进行匹配一共有(2 * n)^2个起始位置
  3. 每次匹配的时间复杂度是n * n,找联通图形的复杂度是n * n

所以总的复杂度是O(n^4),常数是64。加点优化,勉强能用Pypy卡过去,对C++来说应该不是问题。

原题的解法也是枚举16种格子,然后在原矩阵上划十字线(四个象限)。在每个象限上找相对应的颜色,然后找联通块。这样的常数是16,复杂度是O(n^4)。

[AtCoder] M-SOLUTIONS Programming Contest 2020

Contest
Code

A | Kyu in AtCoder

因为每个kyu都是200分。所以可以用除法来计算。

B | Magic 2

枚举ABC的值,然后检查是否符合要求即可。

C | Marks

小坑题。题意是看score[i... i+k]的乘积是否大于score[i-1...i+k-1]。
用模拟的方法就是维护一个滑动窗口,但是乘积的值有可能非常大。
所以直接比较滑动窗口进入和弹出的值的大小,来判断乘积是变大还是变小。

D | Road to Millionaire

和Leetcode上面买卖股票的题目类似。难点在于买卖股票可能手里还会剩余一部分现金。

DP公式:dp[i] = max(dp[i - 1], dp[j] + price[i] - price[j +1])

dp[i]表示在第i天,在没有任何股票的情况下,可以拥有的最大的现金数。
price[i]表示第i天的股价。
DP公式所代表的策略是:

  1. 如果第i天不操作,那么第i天和第i - 1天所能拥有的最大现金数相同。
  2. 如果第i天卖出,就枚举第k天的股价(1 <= k <= i - 1),然后用第k - 1天的最大现金全部买入。

E | M's Solution

本场最难的一题。

首先确定道路一定在某个点上。我们只需要找到新修的路位于哪个点,然后方向是什么。

方法1是卡时限的方法,首先独立枚举x轴和y轴上,选取k个点修路,对于所有点的最短距离。
然后再3^n枚举点是在x轴修路,还是在y轴修路,还是没有新修路。对于每一种场景,我们都有预先算好的所有点在x轴和y轴上的最短距离。再使用O(n)的复杂度分别取最小值进行加和即可。

方法2是DP。简单说来就是先枚举x轴上面的最短距离。然后在y轴上面做DP来找全局的最短距离。

DP公式如下:(假设点按y轴排序)

DP[i][j] = min(DP[k][j - 1], calc(k, i))

此时有k < i。

DP[i][j]表示当前在y轴有j条新路,并且最后一条位于第i个点。
calc(k, i)表示在第k个点和第i个点有新路的情况下,第k+1...i-1个点所贡献的最短距离。

因为calc(k, i)也可以使用记忆化的方法进行优化。所以枚举一次的时间复杂度为O(n^2),总的复杂度为O(2^n * n^2)。

F | Air Safety

一道码农题。

飞机相撞的场景有两种:

  1. 两架飞机相向飞行(U和D,L和R),然后同时飞到同一点上
  2. 两架飞机相垂直飞行,然后同时飞到同一点上

第一种场景比较简单,x或y相同的飞机进行聚类即可。然后用二分法求出距离最近的飞机。
第二种场景稍微复杂一点,但是根据画图可以看出,能相撞的飞机一定位于斜率k=1或k=-1的斜线上。所以我们把斜线进行聚类,然后同样用二分法找到相撞时间最近的飞机。

Kickstart Round A 2020

Contest
Code

这题咋比Leetcode还简单?。。。

A. Allocation

排序,优先选最小的

B. Plates

DP。DP[i], 代表当前已经选中了i个盘子时的最大优美值(beauty values)。

C. Workout

二分结果。设最终结果为k,如果相邻的两个项目(a - b) > k时,则需要另外添加(a - b - 1) / k个项目。

D. Bundling

把所有字符串先排序,然后根据前辍聚合。
例如,["A", "AB", "ABC", "ABCD"], k = 2。我们第一步聚合为["A"]和["AB", "ABC", "ABCD"]。然后再聚合为["A"], [ ["AB"], ["ABC", "ABCD"] ]。因为k = 2,所以我们把第二部分剩余的"AB"和第一部分的"A"进行聚合即可。

前辍不同的字符串可以忽略,因为即使成为了一组,其score也是0。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.