恒星的亮度
题目链接:ybt高效进阶3-4-4
题目大意
有一些恒星,每个恒星有亮度。
给出一些恒星亮度的相对关系,询问这些恒星亮度总和至少有多大。
恒星最暗的亮度是 (1)。数值越大越亮。
关系有两个亮度相等,一个比另一个亮或暗,一个不比另一个亮或暗。
思路
首先,有一个小坑,就是不大于其实是小于等于,不小于是大于等于,不要搞反了。
那我们会想到如果 (A) 亮度小于 (B),那 (B) 的亮度可以是 (A+1),如果是小于等于,那就是 (A)。
如果有多个这样的条件,为了使都满足,我们要取亮度最大的。
那如果相同,那就相同咯。
因为你考虑可以从一个地方推出来全部。
但是还有大于和大于等于啊,那你如果要直接弄的话,就不能确定一开始的点的亮度了。
那我们考虑把它转化成小于和小于等于。
如果 (A) 大于 (B),那其实等价于 (B) 小于 (A)。
如果 (A) 大于等于 (B),那其实等价于 (B) 小于等于 (A)。
那接着的问题就是如何确定一开始的初始点。
然后你就会发现它有环。
那让有环变成无环。。。
Tarjan 缩点!
怎么缩呢,我们考虑把 (A) 小于或小于等于 (B) 连一条从 (A) 到 (B) 的有向边。然后缩在一起的就是亮度相同的。
然后你会想到如果一个环中有一条边是由小于构成的,那就出问题了。(因为你又要求亮度相同,然后你这个地方由不能让亮度相同,那就出矛盾了)
那你就把边分成两类,可以参与缩点的和不可以参与的。
然后你也正常缩点(不然变成不了 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;
}