题解 CF1142E 【Pink Floyd】

Piwry

2020-10-17 07:36:12

题解

解析

我们逐级考虑这个问题

没有粉色边的情况

先维护一个列表 \text{top},里面的每个结点都是一颗仅由绿色边组成的树的根,且根可以到达树中的每个结点

再记录一个列表 \text{delete},里面的每个结点都是可以被 \text{top} 列表中的某个结点到达的

一开始,显然所有点都应该在 \text{top} 里。每次我们从 \text{top} 中取任意两个点 u, v 询问绿色边的方向。如果边是 (u, v),那么就把 v 放入 \text{delete}u 继续留在 \text{top};反之亦然。如此直到 \text{top} 中只剩下一个结点,显然这个结点就是答案;且询问次数是严格 n-1 次的

(值得一提的是,这种情况下该算法给出的是最优(询问最少)的方案)

粉色边组成 DAG 的情况

同样维护列表 \text{top} 并记录列表 \text{delete}。但我们一开始不能将所有结点都放入 \text{top} 了,否则我们询问的两个结点间可能会已经有粉色边了

考虑这样一种做法:初始仅将 DAG 中入度为 0 的结点放入 \text{top};每次将结点放入 \text{delete} 时,就在 DAG 中 “删去” 这个结点,并检测是否有新的入度为 0 的结点,有就将其放入 \text{top}

如此直到 \text{top} 中只剩下一个结点时,其余的结点要么在 \text{delete} 里,那么它们可以被 \text{top} 中的结点通过绿色路径到达;要么就在 DAG 未被删除的部分中,可以发现这些结点一定能被 \text{top} 中剩下的那个结点通过粉色路径到达(证明考虑 DAG 的拓扑排序性质;正是 \text{top} 中剩下的那个结点连出的边才使这些结点不被删除)。于是 \text{top} 中剩下的这个结点就是答案;且在最多 n-1 次询问后我们就能给出答案

(这种情况下该算法给出的不一定是最优的方案)

任意的情况

这里的做法可能会很多(主要是在一些细节上的差异),但大体都是强联通分量(SCC)缩 DAG 然后再处理。这里就只讲下我实现的做法

一开始我们将图中所有的 SCC 求出,并把每个 SCC 导出子图中所有的边(也就是这些 SCC “内部” 的边)都删掉,这样原图就转化为了 DAG。我们仍按照 DAG 的方式做,但对于每个 SCC,我们至多允许其中的一个结点出现在 \text{top} 列表中(为了做到这点,我们可以考虑对每个 SCC 维护一个列表 scc_list,其中是该 SCC 中入度已经为 0,但因为限制无法放入 \text{top} 的结点)。这样我们就保证了每次询问的两个结点间不会有粉色边

\text{top} 中只剩下一个结点时,设其为 u,其余的结点要么在 \text{delete} 里,那么它们可以被 u 通过绿色路径到达;要么就在 DAG 未被删除的部分中,或为 u 强联通分量中的结点(注意 u 强联通分量中的部分结点也可能在 \text{delete} 中,不过其实并不需要考虑这个问题),前者一定可以被 u强联通分量通过粉色路径到达,而后者就在 u 的强联通分量中,显然也可被 u 通过粉色路径到达

这样,我们在至多 n-1 次询问后就能给出答案。注意得出的方案并不一定是最优的,不过题目并没有要求方案的最优性

CODE

另外注意询问要换行,且要**先刷新屏幕**再读取回答 \fad ```cpp #include <cstdio> #include <cstring> #include <vector> using std::vector; using std::min; const int MAXN =1e5+20; /*------------------------------Map------------------------------*/ int first[MAXN], tote; struct edge{ int to, net; }e[MAXN]; inline void clear_map(){ tote =0; memset(first, 0, sizeof(first)); } inline void addedge(const int &u, const int &v){ ++tote; e[tote].to =v, e[tote].net =first[u]; first[u] =tote; } /*------------------------------Scc------------------------------*/ vector<int> scc_list[MAXN]; int scc_id[MAXN], in[MAXN]; /*------------------------------Tarjan------------------------------*/ int dfn[MAXN], low[MAXN], cnt; int stk[MAXN], stk_top; bool instk[MAXN]; void tarjan(int u){ low[u] =dfn[u] =++cnt; stk[stk_top++] =u; instk[u] =1; for(int l =first[u]; l; l =e[l].net){ if(!dfn[e[l].to]){ tarjan(e[l].to); low[u] =min(low[u], low[e[l].to]); } else if(instk[e[l].to]) low[u] =min(low[u], dfn[e[l].to]); } if(low[u] == dfn[u]) while(stk[stk_top] != u){ scc_id[stk[stk_top-1]] =u;/*姑且就以 u 作为 SCC 颜色*/ instk[stk[stk_top-1]] =0; --stk_top; } } int tmp_e_u[MAXN], tmp_e_v[MAXN], tmp_e_tot;/*临时存边*/ void rebuild_DAG(int n){ for(int i =1; i <= n; ++i) for(int l =first[i]; l; l =e[l].net) if(scc_id[e[l].to] != scc_id[i]){ tmp_e_u[tmp_e_tot] =i; tmp_e_v[tmp_e_tot] =e[l].to; ++tmp_e_tot; } clear_map(); for(int i =0; i < tmp_e_tot; ++i){ addedge(tmp_e_u[i], tmp_e_v[i]); ++in[tmp_e_v[i]]; } } /*------------------------------Main------------------------------*/ inline int read(){ int x =0; char c =getchar(); while(c < '0' || c > '9') c =getchar(); while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (48^c), c =getchar(); return x; } int top[MAXN], head, tail; bool in_top[MAXN]; int main(){ int n =read(), m =read(); for(int i =0; i < m; ++i){ int u =read(), v =read(); addedge(u, v); } for(int i =1; i <= n; ++i) if(!dfn[i]) tarjan(i); rebuild_DAG(n); for(int i =1; i <= n; ++i) if(in[i] == 0){ if(!in_top[scc_id[i]]){ top[tail++] =i; in_top[scc_id[i]] =1; } else scc_list[scc_id[i]].push_back(i); } while(tail-head > 1){ int u =top[head], v =top[head+1]; head +=2; printf("? %d %d\n", u, v); fflush(stdout); if(read() == 0) u ^=v ^=u ^=v; top[--head] =u;/*将 u 放回,从 head 加入没有特殊含义*/ in_top[scc_id[v]] =0; for(int l =first[v]; l; l =e[l].net){ --in[e[l].to]; if(in[e[l].to] == 0){ if(!in_top[scc_id[e[l].to]]){ top[tail++] =e[l].to; in_top[scc_id[e[l].to]] =1; } else scc_list[scc_id[e[l].to]].push_back(e[l].to); } } if((int)scc_list[scc_id[v]].size() > 0){ top[tail++] =scc_list[scc_id[v]][scc_list[scc_id[v]].size()-1]; scc_list[scc_id[v]].pop_back(); in_top[scc_id[v]] =1; } } printf("! %d", top[head]); } ```