浙大集训day6:D.Keys
2184 / 5000
建筑师蒂莫西设计了一款新的逃脱游戏。
在这个游戏中,有 n 个房间编号从 0 到 n-1。
最初,每个房间只包含一把钥匙。
每个键都有一个类型,它是介于 0 和 n − 1,包括在内。
房间 i(0 ≤ i ≤ n − 1) 中的钥匙类型是 r[i]。
请注意,多个房间可能 包含相同类型的键,即值 r[i] 不一定不同。
游戏中还有 m 个双向连接器,编号从 0 到 m − 1。
j(0 ≤ j ≤ m − 1) 连接一对不同的房间 u[j] 和 v[j]。
可以连接一对房间 通过多个连接器。
游戏由单人玩,他收集钥匙并通过穿越在房间之间移动 连接器。
我们说玩家在使用这个连接器移动时穿过了连接器 j 房间 u[j] 到房间 v[j],反之亦然。
如果玩家已经收集了一个 之前 c[j] 类型的键。在游戏过程中的任何时候,玩家都在某个房间 x 中并且可以执行两个 动作类型: 1.在房间x中收集钥匙,其类型为r[x](除非他们已经收集了它)。
2.遍历连接器 j,其中 u[j] = x 或 v[j] = x,如果玩家收集了类型为 c[j] 的密钥 预先。请注意,玩家永远不会丢弃他们收集的钥匙。
玩家在一些没有钥匙的房间里开始游戏。
从房间 s 可以到达房间 t, 如果在房间 s 开始游戏的玩家可以执行上述的一些动作序列,并且 到达房间 t。
对于每个房间 i(0 ≤ i ≤ n − 1),将房间 i 可达的房间数表示为 p[i]。
提摩太 想知道在 0 ≤ i ≤ n − 1 范围内达到 p[i] 最小值的索引集 i。
输入 第一行包含两个整数 n,m(2 ≤ n, m ≤ 300000)。
第二行包含 n 个整数 r[0]r[1] 。
. . r[n − 1](0 ≤ r[i] ≤ n − 1)。
在接下来的 m 行中,每行包含三个整数 u[j], v[j], c[j](0 ≤ u[j], v[j] ≤ n − 1, u[j] 6= v[ j])。
输出 在一行中输出 n 个整数 a[i]。对于每一个 0 ≤ i ≤ n − 1,如果对于每一个这样的 j,a[i] 的值应该是 1 0 ≤ j ≤ n − 1,p[i] ≤ p[j]。否则,a[i] 的值应为 0。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+100;
int r[maxn];
int u[maxn],v[maxn],w[maxn];
//每次枚举强联通分量
//如果一条边两边的强联通分量的set包含这把钥匙
//这条边就连
int n,m;
int dfn[maxn],low[maxn],pos[maxn],cnt,scc;
stack<int> st;
vector<pair<int,int> > g[maxn];
set<int> ss[maxn];//每个分量的钥匙集合
set<int> sp[maxn];//缩点后的钥匙集合
int sz[maxn];//每个分量的点数
int ssp[maxn];//缩点后的点数
vector<int> gg[maxn];//每个联通分量内的节点
vector<int> ggg[maxn];//缩点后的节点信息
int ed[maxn];
void tj (int x) {
low[x]=dfn[x]=++cnt;
st.push(x);
for (pair<int,int> it:g[x]) {
int y=it.first;
//if (!ss[x].count(w)) continue;
if (!low[y]) {
tj(y);
low[x]=min(low[x],low[y]);
}
else if (!pos[y]) {
low[x]=min(low[x],dfn[y]);
}
}
if (low[x]==dfn[x]) {
scc++;
while (1) {
int u=st.top();
st.pop();
low[u]=low[x];
pos[u]=scc;
ssp[scc]+=sz[u];
if (u==x) break;
}
}
}
int main () {
scanf("%d%d",&n,&m);
int mmm=n;
for (int i=1;i<=n;i++) scanf("%d",r+i),ss[i].insert(r[i]),sz[i]=1,ed[i]=i,gg[i].push_back(i);
for (int i=1;i<=m;i++) {
scanf("%d%d%d",u+i,v+i,w+i);
u[i]++;
v[i]++;
if (r[u[i]]==w[i])g[u[i]].push_back(make_pair(v[i],w[i]));
if (r[v[i]]==w[i])g[v[i]].push_back(make_pair(u[i],w[i]));
}
int mm=n;
int tt=0;
int xx=5;
while (xx--) {
for (int i=1;i<=n;i++) {
dfn[i]=low[i]=pos[i]=0;
}
cnt=scc=0;
while (st.size()) st.pop();
for (int i=1;i<=n;i++) {
if (!low[i]) tj(i);
}
tt++;
if (scc==mm) break;//如果和上一轮点数一样
for (int i=1;i<=n;i++) {
for (auto it:ss[i]) {
sp[pos[i]].insert(it);
}
for (int j:gg[i]) {
ggg[pos[i]].push_back(j);
ed[j]=pos[i];//更新每个点所属的vector
}
gg[i].clear();
ssp[pos[i]]+=sz[i];
sz[i]=0;
ss[i].clear();
}
for (int i=1;i<=scc;i++) {
ss[i]=sp[i];
sz[i]=ssp[i];
sp[i].clear();
ssp[i]=0;
gg[i]=ggg[i];
ggg[i].clear();
}
for (int i=1;i<=n;i++) g[i].clear();//清空上一轮的图
for (int i=1;i<=m;i++) {
if (ed[u[i]]==ed[v[i]]) continue;
//printf("%d %d %d %d %d\n",pos[u[i]],pos[v[i]],w[i],ss[pos[u[i]]].count(w[i]),ss[pos[v[i]]].count(w[i]));
if (ss[ed[u[i]]].count(w[i]))g[ed[u[i]]].push_back(make_pair(ed[v[i]],w[i]));
if (ss[ed[v[i]]].count(w[i]))g[ed[v[i]]].push_back(make_pair(ed[u[i]],w[i]));
}
n=scc;//更新n为新一轮的点数
mm=n;//更新mm为上一轮的点数
}
int Min=1e9;
// for (int i=1;i<=mmm;i++) printf("%d ",ed[i]);
// puts("");
// for (int i=1;i<=n;i++) {
// printf("%d: ",i);
// for (pair<int,int> it:g[i]) printf("%d ",it.first);
// puts("");
// }
for (int i=1;i<=n;i++) if (g[i].size()==0) Min=min(Min,sz[i]);
for (int i=1;i<=mmm;i++) if (g[ed[i]].size()==0&&sz[ed[i]]==Min) printf("1 ");else printf("0 ");
}