【拓扑排序】P7150 [USACO20DEC] Stuck in a Rut S|普及+
本文涉及知识点
C++图论
P7150 [USACO20DEC] Stuck in a Rut S
题目描述
Farmer John 最近扩大了他的农场,从奶牛们的角度看来这个农场相当于是无限大了!奶牛们将农场上放牧的区域想作是一个由正方形方格组成的无限大二维方阵,每个方格中均有美味的草(将每个方格看作是棋盘上的一个方格)。Farmer John 的 N N N 头奶牛( 1 ≤ N ≤ 1000 1≤N≤1000 1≤N≤1000)初始时位于不同的方格中,一部分朝向北面,一部分朝向东面。
每一小时,每头奶牛会执行以下二者之一:
- 如果她当前所在的方格里的草已经被其他奶牛吃掉了,则她会停下(并从这个时刻开始一直保持停止)。
- 吃完她当前所在的方格中的所有草,并向她朝向的方向移动一个方格。
经过一段时间,每头奶牛的身后会留下一条被啃秃了的轨迹。
如果两头奶牛在一次移动中移动到了同一个有草的方格,她们会分享这个方格中的草,并在下一个小时继续沿她们朝向的方向移动。
当 Farmer John 看到停止吃草的奶牛时会不高兴,他想要知道谁该为他停止吃草的奶牛受到责备。如果奶牛 b b b
停在了之前奶牛 a a a 吃过草的一个方格,我们就称奶牛 a a a 阻碍了奶牛 b b b。进一步地,如果奶牛 a a a 阻碍了奶牛 b b b 且奶牛 b b b 阻碍了奶牛 c c c,我们认为奶牛 a a a 也阻碍了奶牛 c c c(也就是说,「阻碍」关系具有传递性)。每头奶牛受到责备的程度与这头奶牛阻碍的奶牛数量一致。请计算每头奶牛受到责备的数量——也就是说,每头奶牛阻碍的奶牛数量。
输入格式
输入的第一行包含 N N N。以下 N N N 行,每行描述一头奶牛的起始位置,包含一个字符 N(表示朝向北面) 或 E(表示朝向东面),以及两个非负整数 x x x 和 y y y( 0 ≤ x ≤ 10 9 0≤x≤10^9 0≤x≤109, 0 ≤ y ≤ 10 9 0≤y≤10^9 0≤y≤109)表示方格的坐标。所有 x x x 坐标各不相同,所有 y y y 坐标各不相同。
为了使方向和坐标尽可能明确,如果一头奶牛位于方格 ( x , y ) (x,y) (x,y) 并向北移动,她会到达方格 ( x , y + 1 ) (x,y+1) (x,y+1)。如果她向东移动,她会到达方格 ( x + 1 , y ) (x+1,y) (x+1,y)。
输出格式
输出 N N N 行。输出的第 i i i 行包含输入中的第 i i i 头奶牛受到的责备的数量。
输入输出样例 #1
输入 #1
6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 1
E 9 2
输出 #1
0
0
1
2
1
0
说明/提示
在这个样例中,奶牛 3 阻碍了奶牛 2,奶牛 4 阻碍了奶牛 5,奶牛 5 阻碍了奶牛 6。根据传递性,奶牛 4 也阻碍了奶牛 6。
- 测试点 2-5 中,所有坐标不超过 2000 2000 2000。
- 测试点 6-10 没有额外限制。
供题:Brian Dean
错误解法 [USACO20DEC] Stuck in a Rut S 有向森林
性质一:任意方格(r,c)最多2头牛吃过。非r行的牛不会右移通过(r,c),而r行的牛,最多只有一头;非c列的牛无法通过上移通过(r,c),而c列的牛顶多一头。
推论一:如果两头牛同时到达此格,则相互无影响。否则先到的牛阻碍后到的牛。
通过以下方式建立有向图G:如果a阻碍b,则a是b的父节点。
G无环:a阻碍b后,b停止了。无法阻碍其它牛。
G任意节点只有一个父节点:假定某牛被阻碍于(r,c),如果有两个父节点。则有三头牛经过(r,c),和性质一矛盾。
e[i]记录牛i阻碍的牛,则 e [ i ] = ∑ j 是 i 的孩子 ( e [ j ] + 1 ) e[i]=\sum_{j是i的孩子}(e[j]+1) e[i]=∑j是i的孩子(e[j]+1),按拓扑序处理各节点。
通过[a,b]枚举任意两条牛
{
如果方向相同忽略。
如果a向上,a和b互换。
如果a的列大行小,忽略。
如果(bc-ac) < ( ar - br) 则a阻碍b
大于b阻碍a
}
如果 一头牛被多头牛阻碍,取时间最小的。
vPar[i]记录i的父节点,初始-1。
vTime[i]记录i被父节点阻碍的时间,初始INT_MAX/2。如果多个父节点,以vTime小的为准。
可以直接就 有向森林的层次,根节点的层次是0。从层次大的开始枚举。
错误原因:某头牛被阻碍后,不会再阻碍其它牛。
class Solution {public:vector<int> Ans(int N, const vector<tuple<char, int, int>>& cr) {vector<int> pars(N, -1),vTime(N,INT_MAX/2);auto Add = [&](int cur, int par,int time) {if (vTime[cur] <= time) { return; }pars[cur] = par;vTime[cur] = time;};auto Do = [&](int i, int j) {if (get<0>(cr[i]) == get<0>(cr[j])) { return; }if ('N' == get<0>(cr[i])) {swap(i, j);}const auto& [d1, c1, r1] = cr[i];const auto& [d2, c2, r2] = cr[j];if ((c1 > c2) || (r1 < r2)) { return; }int iCmp = (c2 - c1) - (r1 - r2);if (iCmp < 0) {Add(j, i,c2-c1);}else if(iCmp >0 ){Add(i, j,r1-r2);}};for (int i =0;i <N ;i++)for (int j = i+1;j < N ;j++ ){Do(i, j);}CParentToNeiBo pn(pars);auto leves = CBFSLeve::Leve(pn.m_vNeiBo, pn.m_roots);auto sort = CBFSLeve::LeveSort(leves);vector<int> e(N);for (auto it = sort.rbegin(); it != sort.rend(); ++it) {for (const auto& son : pn.m_vNeiBo[*it]) {e[*it] += e[son] + 1;}}return e;}};
改进
性质一:上述解法的Add实际上求的是射线交点。阻碍点一定是射线交点,射线交点不一定是阻碍点:某条射线在到达交点前阻碍,下文简称取消。
性质二:交点(r,c)被取消有两种情况:水平线被(r,c1)阻碍,c1<c;竖直线被(r1,c)阻碍,r1<r。
上述方法通过Add函数增加父节点,改成增加交点。
将交点按先x,后y升序排序,可以保证无后效性。被阻碍的线之后产生的交点删除(标记)。如果水平线被(r,c)阻碍,则rToMax[r]=c,x,y各不相同,故rToMaxc[r]只会被操作0次或1次。如果竖直线被阻碍,则cToMaxr[c]=r;
按顺序枚举交点(r,c):
如果rtoMaxc存在且 c > rToMax[r] 忽略 cToMaxr类似。
增加父节点
修改rToMaxc和cToRMax
代码
核心代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>#include <bitset>
using namespace std;template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {in >> pr.first >> pr.second;return in;
}template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t);return in;
}template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);return in;
}template<class T = int>
vector<T> Read() {int n;cin >> n;vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}
template<class T = int>
vector<T> ReadNotNum() {vector<T> ret;T tmp;while (cin >> tmp) {ret.emplace_back(tmp);if ('\n' == cin.get()) { break; }}return ret;
}template<class T = int>
vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}class CParentToNeiBo
{
public:CParentToNeiBo(const vector<int>& parents){m_vNeiBo.resize(parents.size());for (int i = 0; i < parents.size(); i++){if (-1 == parents[i]){m_roots.emplace_back(i);}else{m_vNeiBo[parents[i]].emplace_back(i);}}}vector<vector<int>> m_vNeiBo;vector<int> m_roots;
};
class CBFSLeve {
public:static vector<int> Leve(const vector<vector<int>>& neiBo, vector<int> start) {vector<int> leves(neiBo.size(), -1);for (const auto& s : start) {leves[s] = 0;}for (int i = 0; i < start.size(); i++) {for (const auto& next : neiBo[start[i]]) {if (-1 != leves[next]) { continue; }leves[next] = leves[start[i]] + 1;start.emplace_back(next);}}return leves;}template<class NextFun>static vector<int> Leve(int N, NextFun nextFun, vector<int> start) {vector<int> leves(N, -1);for (const auto& s : start) {leves[s] = 0;}for (int i = 0; i < start.size(); i++) {auto nexts = nextFun(start[i]);for (const auto& next : nexts) {if (-1 != leves[next]) { continue; }leves[next] = leves[start[i]] + 1;start.emplace_back(next);}}return leves;}static vector<vector<int>> LeveNodes(const vector<int>& leves) {const int iMaxLeve = *max_element(leves.begin(), leves.end());vector<vector<int>> ret(iMaxLeve + 1);for (int i = 0; i < leves.size(); i++) {ret[leves[i]].emplace_back(i);}return ret;};static vector<int> LeveSort(const vector<int>& leves) {const int iMaxLeve = *max_element(leves.begin(), leves.end());vector<vector<int>> leveNodes(iMaxLeve + 1);for (int i = 0; i < leves.size(); i++) {leveNodes[leves[i]].emplace_back(i);}vector<int> ret;for (const auto& v : leveNodes) {ret.insert(ret.end(), v.begin(), v.end());}return ret;};
};
class Solution {
public:vector<int> Ans(int N, const vector<tuple<char, int, int>>& cr) {vector<tuple<int, int, int, int>> crCurPar;auto Do = [&](int i, int j) {if (get<0>(cr[i]) == get<0>(cr[j])) { return; }if ('N' == get<0>(cr[i])) {swap(i, j);}const auto& [d1, c1, r1] = cr[i];const auto& [d2, c2, r2] = cr[j];if ((c1 > c2) || (r1 < r2)) { return; }int iCmp = (c2 - c1) - (r1 - r2);if (iCmp < 0) {crCurPar.emplace_back(c2, r1, j, i);}else if (iCmp > 0) {crCurPar.emplace_back(c2, r1, i, j);}};for (int i = 0; i < N; i++)for (int j = i + 1; j < N; j++){Do(i, j);}sort(crCurPar.begin(), crCurPar.end());vector<int> pars(N, -1);unordered_map<int, int> rToMaxC, cToMaxR;for (const auto& [c, r, cur, par] : crCurPar) {if (rToMaxC.count(r) && (rToMaxC[r] < c)) { continue; }if (cToMaxR.count(c) && (cToMaxR[c] < r)) { continue; }pars[cur] = par;if ('N' == get<0>(cr[cur])) {cToMaxR[get<1>(cr[cur])] = r;}else {rToMaxC[get<2>(cr[cur])] = c;}}CParentToNeiBo pn(pars);auto leves = CBFSLeve::Leve(pn.m_vNeiBo, pn.m_roots);auto sort = CBFSLeve::LeveSort(leves);vector<int> e(N);for (auto it = sort.rbegin(); it != sort.rend(); ++it) {for (const auto& son : pn.m_vNeiBo[*it]) {e[*it] += e[son] + 1;}}return e;}
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUG ios::sync_with_stdio(0); cin.tie(nullptr);int n;cin >> n ;auto cr = Read<tuple<char,int, int>>(n);
#ifdef _DEBUG printf("N=%d",n);Out(cr, ",cr=");//Out(a, ",a=");//Out(que, ",que=");/*Out(que, "que=");*/
#endif // DEBUG auto res = Solution().Ans(n,cr); for (const auto& ll : res) {cout << ll << "\n";} return 0;
}
单元测试
int N;vector<tuple<char, int, int>> cr;TEST_METHOD(TestMethod1){//在这个样例中,奶牛 3 阻碍了奶牛 2,奶牛 4 阻碍了奶牛 5,奶牛 5 阻碍了奶牛 6。根据传递性,奶牛 4 也阻碍了奶牛 6。N = 6, cr = { {'E',3,5},{'N',5,3},{'E',4,6},{'E',10,4},{'N',11,1},{'E',9,2} };auto res = Solution().Ans(N, cr);AssertV({0,0,1,2,1,0}, res);}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。