果然是力扣杯,难度较于平时周赛提高了不少,个人感觉最后两题并不太容易QAQ
LCP 18.早餐组合 #二分思想
题意
你获得了每种主食的价格,及每种饮料的价格,你需要选择一份主食和一份饮料,且花费不超过x元。现要求购买方案数。
分析
先分别对主食与饮料进行排序。枚举主食的价格,得出饮料最高价格,再二分寻找这一价格对应的饮料编号,小于该编号的饮料均能够与当前主食进行搭配。同理,枚举饮料价格,再二分对应主食价格。时间复杂度为O(nlogn)。速度并不快,可以用空间换取时间,用哈希表去维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| typedef long long ll; const int MOD = 1e9 + 7; class Solution { public: int breakfastNumber(vector<int>& staple, vector<int>& drinks, int x) { sort(staple.begin(), staple.end()); sort(drinks.begin(), drinks.end()); ll lans = 0, rans = 0; for (int i = 0; i < staple.size(); i++) { int last = x - staple[i]; if (last <= 0) continue; int pos = upper_bound(drinks.begin(), drinks.end(), last) - drinks.begin() - 1 + 1; lans += (ll)pos; } for (int j = 0; j < drinks.size(); j++) { int last = x - drinks[j]; if (last <= 0) continue; int pos = upper_bound(staple.begin(), staple.end(), last) - staple.begin() - 1 + 1; rans += (ll)pos; } return max(lans % MOD, rans % MOD); } };
|
LCP 19.秋叶收藏集 #线性DP
题意
有一份秋叶收藏集leaves,是含有红叶、黄叶的排列。你需要将收藏集中部分秋叶进行替换,使得其中排列成“红、黄、红”三部分,而每部分树叶数量可不相等且均需大于等于1。每次替换,可将红叶替换为黄叶,或者将黄叶替换为红叶。现要求出上述排列的最小调整次数。
分析
其实该题并不难,但是好久没有碰到线性DP了,故此处参考了官方题解。既然题目要求“红、黄、红”三部分,显然前面红色与后面红色状态不同,故我们可对部分秋叶分成三种状态。当状态为1,表示当前秋叶处于黄色部分;当状态为0或2,表示当前秋叶处于前面红色部分或后面红色部分。定义dp[i][j],表示对第0片到第i片叶子进行调整,且第i片叶子恰好处于状态j时所需的最小操作次数。
- 当j=0,说明第i片叶子处于前面红色部分,说明了第i片叶子应该为红色,且第i−1片叶子只能是红色。状态转移方程有:dp[i][0]=dp[i−1][0]+(leaves[i]!=‘r’)(当第i片叶子不为红色时,需要调整一次)
- 当j=1,说明第i片叶子处于中间黄色部分,说明了第i片叶子应该为黄色,但要注意,该片叶子既有可能处于“红-黄”的过渡状态,又可能处于“黄-黄”的中间状态。故第i−1片叶子的状态ji−1可以取0 or 1。由于我们当前时刻,只关注于第i片叶子为黄色的条件下,因此此处局部的最小值,也是整体的最小值构成之一,因而状态转移时取j=0及j=1的最小操作次数的最小值:dp[i][1]=min{dp[i−1][0],dp[i−1][1]}+(leaves[i]!=‘y’)(当第i片叶子不为黄色时,需要调整一次)
- 当j=2,说明第i片叶子处于前面红色部分,说明了第i片叶子应该为红色,但要注意,该片叶子既有可能处于“黄-红”的过渡状态,又可能处于“红-红”的连续状态。故第i−1片叶子的状态ji−1可以取1 or 2。转移方程有:dp[i][1]=min{dp[i−1][1],dp[i−1][2]}+leaves[i]!=‘r’ 注意,要转移该状态,必须满足i≥2,即保证至少之前有一片叶子属于“前面红色”、有一片叶子属于“黄色”。
初始情况下为任何状态的值赋予无穷大。对于i=0,j只能为0,故边界条件为dp[0][0]=(leaves[0]!=‘r’)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int dp[100005][3]; class Solution { public: int minimumOperations(string leaves) { int len = leaves.length(); for (int i = 0; i < len; i++) dp[i][0] = dp[i][1] = dp[i][2] = 0x3f3f3f3f; dp[0][0] = (leaves[0] != 'r'); for (int i = 1; i < len; i++) { dp[i][0] = dp[i - 1][0] + (leaves[i] != 'r'); dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + (leaves[i] != 'y'); if (i >= 2) dp[i][2] = min(dp[i - 1][1], dp[i - 1][2]) + (leaves[i] != 'r'); } return dp[len - 1][2]; } };
|
LCP 20.快速公交 #记忆化搜索 #数学 #逆向思维
题意
假定你起始站点在0,秋日市集站点为target。你如果从沿途中x号站点步行到x+1号站点,需花费inc;如果从x号站点步行到x−1号站点,需花费dec。现有m辆公交车(编号从0开始),你可以任意次数搭乘编号为i的公交车,由此能够从x号站点移动至x×jump[i]号站点,而耗时为cost[i]。现要求出到达target最少花费时间,对1e9+7取模。移动过程中可越过target站点。
分析
与以往记忆化搜索不太一般,中间过程中需要一点点数学公式推导,从而保证搜索的状态数不会太多。
如果你从0号出发去搜索的话,你可能需要每移动一步就要搜索一下是否要用到公交车。但反过来,如果你从终点target到0号相反的方向去思考,已经知道现在到达的站点编号,欲要求上一站点编号,可以通过编号与公交车移动跨度的整除关系去判断之前是否要坐公交车。感谢@Zhenghao-Liu的思路。
从起点0到终点,无疑就是步行与公交的组合,有四种决策:
直接全部步行
直接坐公交
先向前走几步再乘公交
先向后走几步再乘公交
我们把方向逆转一下,假设当前到达站点编号为curpos,我们要找到上一站点编号pre。我们依次枚举每一辆公交i,决策1容易求,同时决策1、2也属于决策3、4的子集。那么我们分析下面两个决策:
- 先向前走几步再乘公交。方向逆转一下就是,也就是pre号站点先搭乘公交i,到达第pre×jump[i]号站点,再步行step1个站点,到达curpos,即pre×jump[i]+step1=curpos。显然curpos%jump[i]得到step1,curpos/jump[i]得到pre号站点
- 先向后走几步再乘公交。即pre×jump[i]−step2=curpos,但左边等式出现减号,为了方便求出余数,我们不妨令−step2=−jump[i]+step3,此时等式变为pre×jump[i]−jump[i]+step3=(pre−1)×jump[i]+step3=curpos。显然curpos%(pre+1)可求得step3,即jump[i]−curpos%(pre+1)可求得step2。而curpos/jump[i]+1可求得pre。
这里要注意下,对于决策四,当curpos==jump[i]==2时,如果不用第20行代码中(curpos % jump[i] != 0)条件限制,会导致prepos = curpos / jump[i] + 1;一直使得prepos一直为2的死循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| typedef long long ll; class Solution { private: const int MOD = 1e9 + 7; const ll INF = 0x3f3f3f3f3f3f3f3f; unordered_map<int, ll> dp; int inc, dec, target, n; vector<int> jump, cost; public: ll DFS(int curpos) { if (curpos == 1) return dp[1] = inc; if (curpos == 0) return dp[0] = 0; if (dp.find(curpos) != dp.end()) return dp[curpos]; ll rev = (ll)curpos * inc; for (int i = 0; i < n; i++) { ll step_1 = curpos % jump[i]; ll prepos = curpos / jump[i]; rev = min(rev, step_1 * (ll)inc + cost[i] + DFS(prepos)); if (curpos % jump[i] != 0) { ll step_2 = jump[i] - curpos % jump[i]; prepos = curpos / jump[i] + 1; rev = min(rev, step_2 * (ll)dec + cost[i] + DFS(prepos)); } } return dp[curpos] = rev; } int busRapidTransit(int target, int inc, int dec, vector<int>& jump, vector<int>& cost) { this->target = target; this->inc = inc; this->dec = dec; this->n = jump.size(); this->jump = jump; this->cost = cost; dp.clear(); return DFS(target) % MOD; } };
|
LCP 21. 追逐游戏 #深搜找环 #宽搜求最近环入口
题意
N个顶点(从1计数),有N条路使得任意两个顶点可互相到达,且不存在重边。站在点上的A希望在最快时间追上站在另一点的B,而B希望尽可能延后被A追上的时间。每一回合,A先行动,B观察A的行动后再行动,上述行动是指要么留在原地,要么移动至相邻顶点。而两人一定采取最优移动策略,若A能追上B,则游戏结束,返回最少回合数;若A无法追上B,返回-1。
分析
N个节点且N条边的连通图,一定有且仅有一个环。
感谢@lucifer1004的思路与代码,真的十分清晰。
若当A、B一开始即相邻,A第一回合(B还未动)就追上了B,即A赢。
否则,我们先从该图中找到这唯一的环,对环上的点进行标记。(利用DFS及DFS序)
- 若环长度≥4,两人均有机会赢,如果B能在A拦截之前进入该环,无论怎么绕,A都无法再追上B,此时我们就要为B找到其最近的环入口,如果B到该逃脱入口的距离+1 小于 A到达该逃脱入口(利用BFS计算),B没有被A拦截,一定不会被追上。否则,A赢。
- 若环长度为3,A和B同处该环时,A一定能够追上B,即A赢。
当上述两种A赢情况成立时,接下来就要求出最少回合数。枚举B最终被追上的位置(此时一定满足 B到该点的距离+1 大于等于 A到达该点的距离 ),由于B为了拖长回合数,一定会挑选距离更长的 结束点。当我们求出B在最优情况下选择的距离最长的点时,对应地就是我们所要求的的最少回合数了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| const int MAXN = 100005; class Solution { private: vector<int> H[MAXN]; int depth[MAXN] = { 0 }, fa[MAXN] = { 0 }, n; int looplen = 0, ans = 0; bool isLoop[MAXN] = { false }; public: vector<int> BFS(int start, bool findLoop) { vector<int> dis(n + 1, 0x3f3f3f3f); queue<int> myque; dis[start] = 0; myque.push(start); while (!myque.empty()) { int u = myque.front(); myque.pop(); if (findLoop && isLoop[u]) return { u, dis[u] }; for (int i = 0; i < H[u].size(); i++) { int v = H[u][i]; if (dis[v] > dis[u] + 1) { dis[v] = dis[u] + 1; myque.push(v); } } } return dis; } void DFS(int u, int parent) { fa[u] = parent; depth[u] = depth[parent] + 1; for (int i = 0; i < H[u].size(); i++) { int v = H[u][i]; if (v == parent) continue; if (depth[v] == 0) DFS(v, u); else if (depth[v] < depth[u]) { isLoop[v] = true; looplen++; int tmp = u; while (tmp != v) { isLoop[tmp] = true; looplen++; tmp = fa[tmp]; } } } } int chaseGame(vector<vector<int>>& edges, int startA, int startB) { n = edges.size(); for (int i = 0; i < n; i++) { int u = edges[i][0], v = edges[i][1]; H[u].push_back(v); H[v].push_back(u); if (u == startA && v == startB) return 1; if (v == startA && u == startB) return 1; } DFS(1, 0); vector<int> disFromA = BFS(startA, false); vector<int> disFromB = BFS(startB, false); if (looplen >= 4) { vector<int> tmp = BFS(startB, true); if (disFromA[tmp[0]] > tmp[1] + 1) return -1; } for (int i = 1; i <= n; i++) { if (disFromA[i] > disFromB[i] + 1) ans = max(ans, disFromA[i]); } return ans; } };
|