Problem
给你n个数据中心,m个客户,并告诉你一天的小时数h(2 ≤ n ≤ 100 000, 1 ≤ m ≤ 100 000, 2 ≤ h ≤ 100 000)。
对于数据中心j,它在
这个小时内会进行为期1小时的维修。
对于客户i,他的数据存储在
和
这两个数据中心里。你不能使这两个中心同时维修,也就是不能使
,不然那个客户就会gg。
现在,你要使至少一个数据中心的维修时间推迟1个小时,就是使
。求最少要使多少个数据中心推迟1小时,以及一种方案。
Solution
考虑转换模型。
若数据中心x被推迟,中心y会因此而不得不推迟,就从x向y连一条边。比如h=5,
,
,使中心1推迟1小时就会让
变成0。如果有个客户要使用中心1、3,那么就得从1向3连条边。
这样,问题就被转化成求每个点能到达哪些点,并取最小值。
n ≤ 100 000,不能暴力枚举起点再洪水灌溉,强行求解每个点能到哪些点。
那么可以考虑tarjan。
首先,我们使用tarjan将所有强联通分量缩为一个个点。如果我们选了某个分量中任意一点,那么这整个分量都会被选。
于是,题目被转化为求每个分量能到达多少个点,并取最小值。可以拓扑排序+DP强行计算。
考虑两个分量A、B,设A有一条连向B的边。
如果我们选了分量A,那么分量B也得被选。这显然不是最优的。
换句话说说,选了某个分量,该分量连向的分量也得被选。
于是,我们可以只考虑出度为0的分量。选取这些分量中size最小的一个。
时间复杂度: 。
Code
#include <cstdio>
#include <vector>
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=1e5+1,M=N<<1,inf=0x7FFFFFFF;
int i,n,m,h,u[N],c1,c2,tot,final[N],ans,st;
struct side
{
int to,ne;
}e[M];
inline void link(int x,int y)
{
e[++tot].to=y; e[tot].ne=final[x]; final[x]=tot;
}
int t,dfn[N],low[N],sta[N],top,col[N],cn,x,y,sz[N],s;
bool vis[N],out[N];
vector<int>cir[N];
vector<int>::iterator it;
void tarjan(int x)
{
dfn[x]=low[x]=++t; vis[x]=1; sta[++top]=x;
int i,y;
for(i=final[x];i;i=e[i].ne)
{
y=e[i].to;
if(!dfn[y]) tarjan(y), low[x]=min(low[x],low[y]);
else
if(vis[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
vis[x]=0; col[x]=++cn; sz[cn]=1; cir[cn].push_back(x);
while(sta[top]!=x)
{
col[sta[top]]=cn;
vis[sta[top]]=0;
sz[cn]++;
cir[cn].push_back(sta[top--]);
}
top--;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&h);
fo(i,1,n) scanf("%d",&u[i]);
fo(i,1,m)
{
scanf("%d%d",&c1,&c2);
if((u[c1]+1)%h==u[c2]) link(c1,c2);
if((u[c2]+1)%h==u[c1]) link(c2,c1);
}
fo(i,1,n) if(!dfn[i]) tarjan(i);
tot=0;
fo(x,1,n)
for(i=final[x];i;i=e[i].ne)
{
y=e[i].to;
if(col[x]!=col[y]) out[col[x]]=1;
}
s=inf;
fo(i,1,cn) if(!out[i]&&sz[i]<s) s=sz[i], x=i;
printf("%d\n",s);
for(it=cir[x].begin();it!=cir[x].end();it++) printf("%d ",*it);
}