果然是力扣杯,难度较于平时周赛提高了不少,个人感觉最后两题并不太容易QAQ
你获得了每种主食的价格,及每种饮料的价格,你需要选择一份主食和一份饮料,且花费不超过\(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);
}
};
有一份秋叶收藏集leaves
,是含有红叶、黄叶的排列。你需要将收藏集中部分秋叶进行替换,使得其中排列成“红、黄、红”三部分,而每部分树叶数量可不相等且均需大于等于1
。每次替换,可将红叶替换为黄叶,或者将黄叶替换为红叶。现要求出上述排列的最小调整次数。
其实该题并不难,但是好久没有碰到线性\(DP\)了,故此处参考了官方题解。既然题目要求“红、黄、红”三部分,显然前面红色与后面红色状态不同,故我们可对部分秋叶分成三种状态。当状态为1
,表示当前秋叶处于黄色部分;当状态为0
或2
,表示当前秋叶处于前面红色部分或后面红色部分。定义\(dp[i][j]\),表示对第\(0\)片到第\(i\)片叶子进行调整,且第\(i\)片叶子恰好处于状态\(j\)时所需的最小操作次数。
初始情况下为任何状态的值赋予无穷大。对于\(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];
}
};
假定你起始站点在\(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\)的子集。那么我们分析下面两个决策:
这里要注意下,对于决策四,当\(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;
}
};
\(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序)
当上述两种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;
}
};
手机扫一扫
移动阅读更方便
你可能感兴趣的文章