0%

[LGR-133] 邮箱题

额,又在神秘的地方被卡住了

怎么什么题都不会呜呜呜

题目链接

题意

给定一张 个点 条边的有向图。

每一个点上有一个门,只有拿上了对应的钥匙才能打开这扇门,进入这个点。

所有的门互不相同, 号点上有 号点的钥匙, 构成了一个排列。

初始时你在点 ,并且已经获得了点 的钥匙,求有多少个点你可以到达,有多少个点你可以到达之后再走回点

对于每一个 求出答案。

题解

考虑这么一个事情,当前拿到的钥匙种类数至多比到过的房间数多 ,所以每一次如果要去一个新的房间,那么这个房间的编号是固定的。

先考虑对于一个点 如何求出答案,首先把 所在的那个置换环拿出来。

定义可达结构为若干个点,使得对于任意两个点,以 为起点时,若当前在前面的点,则能走到后面的点。

定义强连通分量为若干个点,使得对于任意两个点,以 为起点时,那两个点互相可达。

显然若干个点可以组成一个强连通分量,若干个强连通分量组成一个可达结构。

那么初始时从 开始,每一步尝试能否将可达结构扩展一步,扩展结束后尽可能合并最后两个强连通分量。

的答案就是对应可达结构与强连通分量的点数,能做到

现在考虑做一遍跑出同一个置换环内的所有答案。还是对于每一个环分开考虑。

首先考虑断环为链,然后将这个链复制成两份,然后前面那个点的答案就是最终答案。

考虑怎么从后到前求出答案,过程大概是这样的,考虑一个个加点的过程:

  1. 首先新加的点自成一个可达结构与强连通分量;
  2. 考虑最前面的两个可达结构能否合并,不能则退出;
  3. 尽可能合并第一个可达结构里的强连通分量;特殊的事情是,若这个可达结构最终有多个强连通分量,则新加的点对最后一个强连通分量没有贡献,可以直接推出,否则回到第 步。

根据上面的内容,合并两个可达结构的时候,前一个可达结构一定只有一个强连通分量。

此时可以用 表示终点为 的,起点编号最大的点。

此时若两个可达结构第一个点分别是 ,则只需要满足 ,就可以合并两个可达结构。

然后是如何合并两个强连通分量的问题。

考虑加入一个点 时,对于每一个可达结构,维护所有终点编号 的,起点在这个可达结构内的边。

这样,新合并完一个可达结构,维护强连通分量时,只需要便利一遍第二个可达结构的边,进行合并。

注意到便利完之后这些边一定没用了,可以直接删掉。(其实同时,此时第一个可达结构一定没有记录任何边)

所以直接开 vector 记一下就好了,时间复杂度

代码

注意因为断环为链了,所以每一个点出现了两次,所以答案要和环长取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline ll lread()
{
ll s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
vc<int>rg[1500001];
vc<int>mem[3000001];
int pre[3000001];
int id[3000001];
int fa1[3000001];
int fa2[3000001];
bool vis[1500001];
int ans1[1500001];
int ans2[1500001];
int p[3000001];
int n,m;
inline int find(int *fa,int num)
{
if(fa[num]==num) return num;
return fa[num]=find(fa,fa[num]);
}
inline void clear()
{
for(int i=1;i<=2*n;i++) mem[i].clear(),pre[i]=0;
for(int i=1;i<=n;i++) vis[i]=0,rg[i].clear();
}
inline void run(int s)
{
// printf("run %d\n",s);
vc<int>nod;
for(int i=s;!vis[i];i=p[i]) nod.push_back(i),vis[i]=1;
int len=nod.size();
for(int i=0;i<len;i++) nod.push_back(nod[i]+n);
for(int i=0;i<2*len;i++) id[nod[i]]=i+1;
for(int i=1;i<2*len;i++) p[nod[i-1]]=nod[i];

for(int i=2*len;i;i--)
{
int t=nod[i-1],tt=t>n?t-n:t;
for(int j:rg[tt])
{
if(id[j]>=i) mem[find(fa1,j)].push_back(j);
if(id[j]<=i) pre[t]=max(pre[t],id[j]);
if(id[j+n]>=i) mem[find(fa1,j+n)].push_back(j+n);
if(id[j+n]<=i) pre[t]=max(pre[t],id[j+n]);
}
while(id[find(fa1,t)]!=2*len)
{
int nx=p[find(fa1,t)];// printf("t=%d nx=%d\n",t,nx);
if(pre[nx]<id[t]) break;
fa1[find(fa1,t)]=nx;
// printf("mem %d\n",find(fa1,t));
for(int u:mem[find(fa1,t)])
{
while(find(fa2,t)!=find(fa2,u)) fa2[find(fa2,t)]=p[find(fa2,t)];
}
mem[find(fa1,t)].clear();
if(find(fa1,t)!=find(fa2,t)) break;
}
if(i<=len)
{
ans1[t]=min(len,id[find(fa1,t)]-id[t]+1);
ans2[t]=min(len,id[find(fa2,t)]-id[t]+1);
}
// printf("i=%d t=%d pre=%d\n",i,t,pre[t]);
// for(int j=i;j<=2*len;j++) printf("%d%c",find(fa1,nod[j-1])," \n"[j==2*len]);
// for(int j=i;j<=2*len;j++) printf("%d%c",find(fa2,nod[j-1])," \n"[j==2*len]);
}

for(int i=0;i<2*len;i++) id[nod[i]]=0;
}
inline void solve()
{
n=read(),m=read();
for(int i=1;i<=n;i++) p[i]=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
rg[v].push_back(u);
}
for(int i=1;i<=2*n;i++) fa1[i]=fa2[i]=i;
for(int i=1;i<=n;i++) if(!vis[i]) run(i);

for(int i=1;i<=n;i++) printf("%d %d\n",ans1[i],ans2[i]);
}
int main()
{
int T=read();
while(T--) clear(),solve();
return 0;
}
/*
1
4 3
3 1 4 2
2 4
3 4
4 2
*/