【ybt高效进阶3-4-4】恒星的亮度

恒星的亮度

题目链接:ybt高效进阶3-4-4

题目大意

有一些恒星,每个恒星有亮度。

给出一些恒星亮度的相对关系,询问这些恒星亮度总和至少有多大。

恒星最暗的亮度是 1。数值越大越亮。

关系有两个亮度相等,一个比另一个亮或暗,一个不比另一个亮或暗。

思路

首先,有一个小坑,就是不大于其实是小于等于,不小于是大于等于,不要搞反了。

那我们会想到如果 A 亮度小于 B,那 B 的亮度可以是 A+1,如果是小于等于,那就是 A
如果有多个这样的条件,为了使都满足,我们要取亮度最大的。

那如果相同,那就相同咯。

因为你考虑可以从一个地方推出来全部。
但是还有大于和大于等于啊,那你如果要直接弄的话,就不能确定一开始的点的亮度了。
那我们考虑把它转化成小于和小于等于。

如果 A 大于 B,那其实等价于 B 小于 A
如果 A 大于等于 B,那其实等价于 B 小于等于 A

那接着的问题就是如何确定一开始的初始点。

然后你就会发现它有环。
那让有环变成无环。。。
Tarjan 缩点!

怎么缩呢,我们考虑把 A 小于或小于等于 B 连一条从 AB 的有向边。然后缩在一起的就是亮度相同的。
然后你会想到如果一个环中有一条边是由小于构成的,那就出问题了。(因为你又要求亮度相同,然后你这个地方由不能让亮度相同,那就出矛盾了)
那你就把边分成两类,可以参与缩点的和不可以参与的。
然后你也正常缩点(不然变成不了 DAG),然后建缩点后的图的时候如果一条边是不能参与缩点的,但是它的两边在缩点后属于同一个的话,就说明矛盾,就无解了。

然后你就会发现 DAG 的起始点可能不止一个,但是问题不大,就都设为亮度是 1 不久好了吗。
然后就会想到用拓扑序 DP,然后你在转移的时候判断一下你转移的边的类型,如果是小于的就是原来的 +1,否则就是原来的。
(因为你要总和最小)
(我一开始直接无脑加一,就只有 70 分)

然后就可以了,具体实现可以看看代码。

代码

#include<queue> #include<cstdio> #include<cstring> #include<iostream> #define ll long long using namespace std; struct node { int to, nxt, cc; }e[1000001], e_[1000001]; ll ans; int le_[100001], KK_; int n, m, le[100001], KK, in[100001]; int op, x, y, dfn[100001], tmp, tot, ru[100001]; int low[100001], sta[100001], num[100001], dis[100001]; queue <int> q; void add(int x, int y, int cc) {//原来的图 e[++KK] = (node){y, le[x], cc}; le[x] = KK; } void add_(int x, int y, int cc) {//缩点后的图 e_[++KK_] = (node){y, le_[x], cc}; le_[x] = KK_; } void tarjan(int now) {//Tarjan 缩点 dfn[now] = low[now] = ++tmp; sta[++sta[0]] = now; for (int i = le[now]; i; i = e[i].nxt) if (!dfn[e[i].to]) { tarjan(e[i].to); low[now] = min(low[now], low[e[i].to]); } else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]); if (dfn[now] == low[now]) { in[now] = ++tot; num[tot] = 1; while (sta[sta[0]] != now) { num[tot]++; in[sta[sta[0]]] = tot; sta[0]--; } sta[0]--; } } int main() { scanf("%d %d", &n, &m); for (int i = 1; i <= m; i++) { scanf("%d %d %d", &op, &x, &y); if (op == 1) {//亮度相等,直接连缩点的边 add(x, y, 0); add(y, x, 0); continue; } if (op == 2) {//小于,连不可以缩点的边 add(x, y, 1); continue; } if (op == 3) {//A 不小于 B,就是 A 大于等于 B,又可以是 B 小于等于 A,连可以缩点的边 add(y, x, 0); continue; } if (op == 4) {//A 大于 B,相当于 B 小于 A,那就连可以缩点的边 add(y, x, 1); continue; } if (op == 5) {//A 不大于 B,就是小于等于,连小于等于的边 add(x, y, 0); continue; } } for (int i = 1; i <= n; i++)//缩点 if (!dfn[i]) tarjan(i); for (int i = 1; i <= n; i++) for (int j = le[i]; j; j = e[j].nxt) if (in[i] != in[e[j].to]) {//连不同点之间的边 add_(in[i], in[e[j].to], e[j].cc); ru[in[e[j].to]]++;//统计入度,到时拓扑用 } else if (e[j].cc) {//不能缩点的边用来缩点了,则无解 printf("-1"); return 0; } for (int i = 1; i <= tot; i++) if (!ru[i]) q.push(i), dis[i] = 1; while (!q.empty()) {//拓扑序 DP 求每个的亮度 int now = q.front(); q.pop(); ans += 1ll * dis[now] * num[now];//记得亮度加进总亮度之间要乘这个点包含点的个数 for (int i = le_[now]; i; i = e_[i].nxt) { ru[e_[i].to]--; dis[e_[i].to] = max(dis[e_[i].to], dis[now] + e_[i].cc); //如果这个边是小于,那 cc 值就是 1,亮度也至少要加一 //如果这个边是小于等于,那 cc 值就是 0,亮度可以不用加,跟之前一样 //刚好符合,我们就不用新开变量,用这个 cc 值就好了 if (!ru[e_[i].to]) { q.push(e_[i].to); } } } printf("%lld", ans); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_GXJJ_3-4-4.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(364)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示