Description
题目描述
给出 (n) 个数据中心,(m) 份资料。要把 (m) 份资料放到其中的两个数据中心备份,需要保证任意时刻都可以至少在一个数据中心进行备份。定义一天有 (h) 个小时,每个数据中心在一天内有一小时维护时间 (u_i)((0 leq u_i < h)),在这一小时内该数据中心无法进行备份。
由于某种原因,需要把一些数据中心的维护时间向后推迟 1 小时(一个数据中心的维护时间的向后推迟可能导致有的资料无法在任意时刻进行备份且若 (u_i = h - 1) 那么推迟后 (u_i = 0)),请你求出最少需要向后推迟多少个数据中心,并把这些数据中心的编号输出出来。
输入格式
第一行 3 个整数 (n),(m),(h),含义如上。
第二行 (n) 个整数,为 (u_i) 到 (u_n)。
接下来 (m) 行,每行 2 个整数(c_1),(c_2)((u_{c_1} eq u_{c_2})),分别表示第 (i) 份资料可以在哪两个数据中心进行备份。
注意:输入的 (u_{c_1} e u_{c_2}),意味着刚开始时任意资料都可以在任意时间进行备份。
输出格式
第一行 1 个整数 (k),表示最少需要推迟 (k) 个数据中心。
第二行 (k) 个整数,分别为 (x_1) 到 (x_k),表示需要推迟的数据中心的编号。
Solution
通过观察样例可以发现,不管你初始时是否合法都必须有向后推迟的数据中心,即至少一个。
所以题目就是要求我们选择一个点 +1,并将因此而被迫推迟的点都推迟 1,然后计算最少推迟多少个点。
那么我们如何找到哪些点是被迫推迟的呢?
对于一组条件 (x) 和 (y):
-
若 ({a_x + 1}equiv{a_y} pmod {h}),我们就从 (x) 向 (y) 建一条边。
-
若 ({a_y + 1}equiv{a_x} pmod {h}),我们就从 (y) 向 (x) 建一条边。
然后就形成了一张 (DAG),考虑到在一个环里的点,不管选哪一个整个环都会被选,所以可以 (Tarjan) 缩点。
然后不难发现,若选择出度不为 0 的点,那么它指向的那个点也会被迫选上,这明显不如只选被指向的点优。
所以我们只用找出度为 0 的点中的 (siz) 最小的点即可。
如果还是无法理解的话,可以看着代码理解一下。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n, m, h;
int a[N];
struct node{
int u, v, nxt;
}edge[N << 1];
int head[N], tot;
int low[N], dfn[N], tim;
int stk[N], top, t[N];
int siz[N], scc[N], cnt;
int out[N];
inline void add(int x, int y){
edge[++tot] = (node){x, y, head[x]};
head[x] = tot;
}
void tarjan(int x){
low[x] = dfn[x] = ++tim;
stk[++top] = x;
t[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(!dfn[y])tarjan(y), low[x] = min(low[x], low[y]);
else if(t[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]){
cnt++;
do{
siz[cnt]++;
scc[stk[top]] = cnt;
t[stk[top--]] = 0;
}while(stk[top + 1] != x);
}
}
int main(){
scanf("%d%d%d", &n, &m, &h);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 1, x, y; i <= m; ++i){
scanf("%d%d", &x, &y);
if((a[x] + 1) % h == a[y]) add(x, y);
if((a[y] + 1) % h == a[x]) add(y, x);
}
for(int i = 1; i <= n; ++i)
if(!dfn[i]) tarjan(i);
for(int i = 1; i <= tot; ++i){
int u = scc[edge[i].u], v = scc[edge[i].v];
if(u != v) out[u]++;
}
int ans = 0;
siz[0] = 1e9;
for(int i = 1; i <= cnt; ++i)
if(!out[i]) ans = siz[ans] > siz[i] ? i : ans;
printf("%d
", siz[ans]);
for(int i = 1; i <= n; ++i)
if(scc[i] == ans) printf("%d ", i);
puts("");
return 0;
}