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 ... 
随机推荐
- Redis学习笔记(九)——集群
			一.概述 Redis Cluster与Redis3.0.0同时发布,以此结束了Redis无官方集群方案的时代. Redis Cluster是去中心化,去中间件,也就是说,集群中的每个节点都是平等的关 ... 
- mysql-connector-java各种版本下载地址
			mysql-connector-java下载地址: http://mvnrepository.com/artifact/mysql/mysql-connector-java 目录 1.进去后选择自己的 ... 
- 蓝桥杯2020.10.17B组c++
			1.门牌制作 暴力即可 #include <iostream> #include<math.h> #include<string.h> #include<st ... 
- TypeScript魔法堂:函数类型声明其实很复杂
			前言 江湖有传"动态类型一时爽,代码重构火葬场",由于动态类型语言在开发时不受数据类型的约束,因此非常适合在项目原型阶段和初期进行快速迭代开发使用,这意味着项目未来将通过重写而非重 ... 
- Linux 系统编程 学习:04-进程间通信2:System V IPC(1)
			Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ... 
- Pycharm快捷键与基本使用方法
			pycharm常用快捷键设置 关注公众号"轻松学编程"了解更多. 可在file->settings->keymap中查找关键字然后修改快捷键 1.多行编辑 ALT+鼠标 ... 
- linux的mysql数据库创建和删除
			mysql -h localhost -u 用戶名 -p密碼 //连接数据库use desk_show; ... 
- 【Elasticsearch 技术分享】—— 十张图带大家看懂 ES 原理 !明白为什么说:ES 是准实时的!
			前言 说到 Elasticsearch ,其中最明显的一个特点就是 near real-time 准实时 -- 当文档存储在Elasticsearch中时,将在1秒内以几乎实时的方式对其进行索引和完全 ... 
- R-C3D:用于时间活动检测的区域3D网络
			论文原称:R-C3D: Region Convolutional 3D Network for Temporal Activity Detection(2017) 主要贡献: 1.提出一个包括活动候选 ... 
- Pyston v2.0 发布,解决 Python 慢速的救星
			Pyston 自从 2017 年发布 0.6.1 版本后,已经淡出了人们的视线三年多了,导致现在新人都很少听过它的大名. 前两天(2020年10月28日)Pyston 在官方博客上(https://b ... 
