Loading

算法提高课 拓扑排序专题(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;
}
posted @ 2022-05-24 16:02  x7x7g7c7  阅读(476)  评论(0)    收藏  举报