算法提高课 拓扑排序专题(4/4)
AcWing 1191. 家谱树
题意:
- 裸拓扑排序
AcWing 1192. 奖金
题意:
- 给定了\(m\)组要求\(a,b\),每次要求\(a\)的奖金比\(b\)的高,求出满足所有要求的最小花费
- 每组要求看作\(b\rightarrow a\)的一条有向边,令所有入度为0的点奖励为最小值\(100\),然后拓扑排序的过程中不断更新后继节点的答案即可,为了满足所有要求更新时取个\(max\)
- 可行性直接判断最后拓扑排序后的数组能否有\(n\)个数即可
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
const int M = 20010;
int head[N],to[M],nxt[M],tot,ind[N],award[N],n,m;
void add(int u,int v) {
to[++tot] = v,nxt[tot] = head[u],head[u] = tot;
ind[v] ++;
}
bool toposort(vector<int>& vec) {
queue<int>q;
for(int i = 1;i <= n;i ++) {
if(!ind[i]) {
// cout << i << '\n';
award[i] = 100;
q.push(i);
}
}
// vector<int>ans;
while(!q.empty()) {
int u = q.front();
q.pop();
vec.push_back(u);
for(int i = head[u];i;i = nxt[i]) {
int v = to[i];
award[v] = max(award[v],award[u] + 1);
if(--ind[v] == 0) {
q.push(v);
}
}
}
// cout << vec.size() << '\n';
return vec.size() == n;
}
int main() {
cin >> n >> m;
while(m --) {
int a,b; cin >> a >> b;
add(b,a);
}
vector<int>ans;
if(toposort(ans)) {
int s = 0;
for(auto i : ans) s += award[i];
cout << s << '\n';
} else {
cout <<"Poor Xed\n";
}
return 0;
}
AcWing 164. 可达性统计
题意:给定\(N\)个点\(M\)条边的有向无环图,问从每个点出发,能走到的点有多少个?
- 考虑比较简单的状态转移方程,令\(f_i\)表示点\(i\)能达到的点的数量
\[f_i = i \ \cup f_{j_1}\cup f_{j_2} \cup \cdots \cup f_{j_k}
\]
- 其中\(j_1,j_2,\cdots,j_k\)是点\(i\)的所有可达点
- 我们存下来点\(i\)的所有可达点,然后取个并集可以完成这个事情
- 如果我们直接存会有\(30000\times30000\),所以我们需要优化一下,优化空间和时间
- \(bitset\)可以完成这个事情,每个字节的每一位都存放了\(8\)个\(0/1\),对于\(int\)的变量我们可以将其优化到\(\frac{30000^2}{32}\)
- 取并操作也是有定好的各种函数方便我们操作
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4 + 10;
int n,m,head[N],to[N],nxt[N],tot,sorted[N],ind[N];
//按照拓扑序做dp 保证计算当前值的时候 前面的值都已经被遍历过
void add(int u,int v) {
to[++tot] = v,nxt[tot] = head[u],head[u] = tot;
ind[v]++;
}
bitset<N>f[N];//每个字节的每一位可以存8位0/1
int main() {
cin >> n >> m;
while(m --) {
int a,b; cin >> a >> b;
add(a,b);
}
int id = 0;
queue<int>q;
for(int i = 1;i <= n;i ++) {
if(!ind[i]) {
q.push(i);
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
sorted[++id] = u;
for(int i = head[u];i;i = nxt[i]) {
int v = to[i];
if(--ind[v] == 0) {
q.push(v);
}
}
}
for(int i = n;i >= 1;i --) {
int u = sorted[i];
f[u][u] = 1;
for(int j = head[u];j;j = nxt[j]) {
int v = to[j];
f[u] |= f[v];
}
}
for(int i = 1;i <= n;i ++) {
cout << f[i].count() << '\n';
}
return 0;
}
AcWing 456. 车站分级
题意:给定\(m\)条火车的运行线路(停靠站点),每个车站有一个等级,最小为\(1\),在停站是有一个要求,如果在等级大小为\(x\)的车站停下了,那么这段线路中所有等级\(\ge x\)的站点都要停,求一个合法的方案使得\(n\)个车站分成等级数目最小
- 一条线路上的停靠点要大于等于其中任意一个点
- 可以得到未停靠的点的等级一定严格小于所有停靠点中的最小点
- 然后我们就可以从所有未停靠点的连一条到停靠点的边,拓扑排序时更新每个车站的答案即可,类似于第\(2\)道题目
- 不过问题就是暴力建边我们最多会有\(500^2\times1000\)条边,很明显处理不了,这里采用一个建立中间虚拟节点的方式,既然是所有未停靠的点都要向停靠过的点连出一条边,那么我们可以建立一个虚拟节点,使得所有未停靠的连一条到虚拟节点权值为\(0\)的有向边,然后再从虚拟节点向所有停靠过的点连一条权值为\(1\)的有向边,这样我们就可以达到和暴力连边一致的效果,但是边的条数优化到了\(1000^2\),瞬间可接受了不少
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, M = 1000010;
int head[N],to[M],nxt[M],w[M],tot,ind[N],rk[N];
//一条线路上的停靠点要大于等于其中任意一个点
//可以得到未停靠的点的等级一定严格小于所有停靠点中的最小点
int main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,m; cin >> n >> m;
auto add = [&](int u,int v,int x) {
to[++tot] = v,w[tot] = x,nxt[tot] = head[u],head[u] = tot;
ind[v]++;
};
for(int o = 1;o <= m;o ++) {
int k,st,ed; cin >> k;
vector<bool>vis(n + 1,false);
for(int i = 1;i <= k;i ++) {
int stop; cin >> stop;
vis[stop] = true;
if(i == 1) st = stop;
if(i == k) ed = stop;
}
int mid = n + o;
for(int i = st;i <= ed;i ++) {
if(vis[i]) {
add(mid,i,1);
} else {
add(i,mid,0);
}
}
}
auto toposort = [&]() {
queue<int>q;
for(int i = 1;i <= n + m;i ++) {
if(!ind[i]) {
q.push(i);
if(i <= n) rk[i] = 1;
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = head[u];i;i = nxt[i]) {
int v = to[i];
rk[v] = max(rk[v],rk[u] + w[i]);
if(--ind[v] == 0)
q.push(v);
}
}
};
toposort();
int ans = 0;
for(int i = 1;i <= n;i ++) {
ans = max(ans,rk[i]);
}
cout << ans << '\n';
return 0;
}