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