题解 CF825E【Minimal Labels】

偶然间翻到三个月前写的这个题,发现现有的题解均未给出解法的正确性证明,只是不明不白地写了一些对理解做法毫无帮助的话。我认为解法的正确性并不显然,因此这篇题解主要给出正确性证明,补上逻辑漏洞。

解法与其他题解一样,即:建反图,然后跑拓扑排序,每次优先取出可以取出的编号最大的点,从 n1 赋权值。

这似乎有点反直觉:为什么倒着做就恰好能满足字典序最小呢?相信大多数人的第一反应都是正着做,这也是我认为正确性并不显然的原因。

证明:

以下“入边”“出边”“入度”“出度”等均为原图上的。

考虑整个算法的第一步,也就是分配权值 n。显然,权值 n 的点出度必须为 0

我们记在上述算法中,被分配了权值 n 的点的编号为 u。利用反证法,假设在另一种分配方案中,u 被分配了权值 xx<n),而 v 被分配了权值 n。由于算法中取出的 u 是编号最大的出度为 0 的点,而 v 的出度也必须为 0,因此我们有 u>v

对于这种分配方案中被分配权值 x+1n 的点,我们可以将它们重新分配 xn1 的权值,并将 u 重新分配 n 的权值,仍然得到合法的分配方案。这是因为:点 u 出度为 0,将 u 的权值增大并不会影响 u 与其余节点的合法性;并且考虑去掉点 u 的其他节点,重新分配权值相当于对原来的权值进行离散化,不改变偏序关系。

因此,我们可以重新分配权值使得 u 的权值为 n。并且,由于 u>v,重新分配权值后答案的字典序减小。

由于点 u 的出度为 0,给它分配了最大的权值 n 之后,其余节点与 u 之间必然合法,只需要求出其余节点的最小字典序答案即可。因此将点 u 以及它的所有入边都删除,得到规模为 n1 的子问题。又显然当 n=1 时,算法可以给出最小字典序答案,因此算法正确性得证。

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
#define likely(exp) __builtin_expect(!!(exp), 1)
#define unlikely(exp) __builtin_expect(!!(exp), 0)
using namespace std;
typedef long long ll;
const int N = 1e5+5; 

int n, m, deg[N], id[N], tms;
vector<int> e[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
void toposort() {
	priority_queue<int> q;
	rep(i, 1, n) if(!deg[i]) q.push(i);
	while(!q.empty()) {
		int u = q.top(); q.pop();
		id[u] = ++tms;
		for(int v : e[u]) if(!--deg[v]) q.push(v);
	}
}

int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, m) {
		int u, v;
		scanf("%d%d", &u, &v);
		e[v].push_back(u);
		++deg[u];
	}
	toposort();
	rep(i, 1, n) printf("%d%c", n+1-id[i], " \n"[i==n]);
	return 0;
}
posted @   rui_er  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
历史上的今天:
2022-04-22 静态点分治学习笔记
点击右上角即可分享
微信分享提示