LCCUP 2020 秋季编程大赛 补题
果然是力扣杯,难度较于平时周赛提高了不少,个人感觉最后两题并不太容易QAQ
LCP 18.早餐组合 #二分思想
题目链接
题意
你获得了每种主食的价格,及每种饮料的价格,你需要选择一份主食和一份饮料,且花费不超过\(x\)元。现要求购买方案数。
分析
先分别对主食与饮料进行排序。枚举主食的价格,得出饮料最高价格,再二分寻找这一价格对应的饮料编号,小于该编号的饮料均能够与当前主食进行搭配。同理,枚举饮料价格,再二分对应主食价格。时间复杂度为\(O(nlogn)\)。速度并不快,可以用空间换取时间,用哈希表去维护。
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\)片叶子的状态\(j_{i-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\)片叶子的状态\(j_{i-1}\)可以取\(1\ or\ 2\)。转移方程有:\(dp[i][1] = min\{dp[i-1][1], dp[i-1][2]\}+leaves[i] != ‘r’\) 注意,要转移该状态,必须满足\(i\geq2\),即保证至少之前有一片叶子属于“前面红色”、有一片叶子属于“黄色”。
初始情况下为任何状态的值赋予无穷大。对于\(i=0\),\(j\)只能为\(0\),故边界条件为\(dp[0][0]=(leaves[0] != ‘r’)\)
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\times 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\times jump[i]\)号站点,再步行\(step_1\)个站点,到达\(curpos\),即\(pre\times jump[i] + step_1=curpos\)。显然\(curpos\%jump[i]\)得到\(step_1\),\(curpos/jump[i]\)得到\(pre\)号站点
- 先向后走几步再乘公交。即\(pre\times jump[i]-step_2=curpos\),但左边等式出现减号,为了方便求出余数,我们不妨令\(-step_2=-jump[i]+step_3\),此时等式变为\(pre\times jump[i]-jump[i]+step_3=(pre-1)\times jump[i] + step_3=curpos\)。显然\(curpos\%(pre+1)\)可求得\(step_3\),即\(jump[i]-curpos\%(pre+1)\)可求得\(step_2\)。而\(curpos/jump[i]+1\)可求得\(pre\)。
这里要注意下,对于决策四,当\(curpos==jump[i]==2\)时,如果不用第\(20\)行代码中(curpos % jump[i] != 0)
条件限制,会导致prepos = curpos / jump[i] + 1;
一直使得\(prepos\)一直为\(2\)的死循环
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) { //说明curpos不被整除,不会出现死循环
//(pre - 1) * jump + (jump - step_2) = curpos
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序)
- 若环长度\(\geq4\),两人均有机会赢,如果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在最优情况下选择的距离最长的点时,对应地就是我们所要求的的最少回合数了。
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) {
//findLoop为true时求最近环入口点,为false时求start到所有点的最短距离
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]) { //v的DFS序 比 u的DFS序 更早,说明找到反向边。即有环
isLoop[v] = true; looplen++;
int tmp = u;
while (tmp != v) { //找到之前经过路径的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;//相邻节点的话A第一步直接抓到B
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) { //B有机会逃脱
vector<int> tmp = BFS(startB, true);
//通过BFS,找到B逃进环的最近入口。tmp[0]为环入口,tmp[1]为B到该入口的距离
if (disFromA[tmp[0]] > tmp[1] + 1)
return -1; //A到入口距离 应大于 (B到入口距离+1) ,B才顺利逃进环中
//否则 若A到入口距离 刚好为 (B到入口距离+1) 时,B到达入口后下一步A就能抓住B
}
//B不能成功进环 或者 环的阶数为三,B注定要被A追上了,那么B一定要选个距离长的点
for (int i = 1; i <= n; i++) {
if (disFromA[i] > disFromB[i] + 1) ans = max(ans, disFromA[i]);
}
return ans;
}
};
LCCUP 2020 秋季编程大赛 补题的更多相关文章
- QFNU-ACM 2020.04.05个人赛补题
A.CodeForces-124A (简单数学题) #include<cstdio> #include<algorithm> #include<iostream> ...
- codeM编程大赛E题 (暴力+字符串匹配(kmp))
题目大意:S(n,k)用k(2-16)进制表示1-n的数字所组成的字符串,例如S(16,16)=123456789ABCDEF10: 解题思路: n最大50000,k最大100000,以为暴力会超时. ...
- Technocup 2020 - Elimination Round 1补题
慢慢来. 题目册 题目 A B C D tag math strings greedy dp 状态 √ √ √ √ //∅,√,× 想法 A. CME res tp A 题意:有\(n\)根火柴,额外 ...
- 2020.12.3--vj个人赛补题
A Vasya studies music.He has learned lots of interesting stuff. For example, he knows that there are ...
- 2020.10.30--vj个人赛补题
D - D CodeForces - 743A Vladik is a competitive programmer. This year he is going to win the Interna ...
- 2020.10.23-vj个人赛补题
B - B Polycarp loves lowercase letters and dislikes uppercase ones. Once he got a string s consistin ...
- 2020.10.16--vj个人赛补题
D - Drinks Choosing Old timers of Summer Informatics School can remember previous camps in which eac ...
- 2020.10.9--vj个人赛补题
B - A Tide of Riverscape 题意:给出一组字符串,由'0','1',' . '组成,' . '可以换成 0或1,判断第 i 个和第 i+p 个字符是否可以不相等,如果可以则输出 ...
- 【补题记录】ZJU-ICPC Summer Training 2020 部分补题记录
补题地址:https://zjusummer.contest.codeforces.com/ Contents ZJU-ICPC Summer 2020 Contest 1 by Group A Pr ...
随机推荐
- 在Linux系统中安装Chrome浏览器
前言:作为一个Web开发人员,经常与我们相伴的必然少不了浏览器,而Google旗下的chrome浏览器更是凭借着出色的性能.简洁的界面被广大开发者所喜爱,今天分享下如何在linux系统下安装chrom ...
- D. Bash and a Tough Math Puzzle 解析(線段樹、數論)
Codeforce 914 D. Bash and a Tough Math Puzzle 解析(線段樹.數論) 今天我們來看看CF914D 題目連結 題目 給你一個長度為\(n\)的數列\(a\), ...
- JUC---10JMM
前提:什么是Volatile? Java 虚拟机提供轻量级的同步机制 1.保证可见性------->JMM 2.不保证原子性 3.禁止指令重排 一.什么是JMM 1.JMM : Java内存模型 ...
- WC2019 自闭记
不咕了 Day 1 2019/1/24 辣么快就到冬令营了,还沉迷于被柿子吊打的状态的菜鸡一时半会还反应不过来.我们学校这次分头去的冬令营,差点上不了车.这次做的动车居然直达广州,强啊. 然鹅还是到太 ...
- C# 时间格式处理
C#的常用时间格式意义: 1字符"y"---year,年,yy显示13,yyyy显示2013 2字符"M"---Month,月份,M显示5,MM显示05 3字符 ...
- 基于 .NET 的 FluentValidation 数据验证
学习地址:官方文档,更多更详细的内容可以看官方文档. FluentValidation 是一个基于 .NET 开发的验证框架,开源免费,而且优雅,支持链式操作,易于理解,功能完善,还是可与 MVC5. ...
- 关于layui图片/文件上传
一:常规使用 普通文件上传 (传入服务器一张图片) 1.前台代码: <!DOCTYPE html><html><head> <meta charset=& ...
- .NET5都来了,你还不知道怎么部署到linux?最全部署方案,总有一款适合你
随着2020进入4季度,.NET5正式版也已经与大家见面了.不过,尽管 .NET Core发布已经有四五年的时间,但到目前为止,依旧有很多.NET开发者在坚守者.NET4,原因不尽相同,但最大的问题可 ...
- centos6 安装 terminator
yum install terminator 报错: No package terminator available. 解决: yum install epel-release 报错 Cannot r ...
- python之路 《六》函数
---恢复内容开始--- 为什么要有函数? 当你的老板要你写一个程序 1 def 函数0(): 2 # 如果cpu占用率>90 3 # 发送邮件 4 # 发出警报 5 6 def 函数1(): ...