CF1142E Pink Floyd
作者:互联网
一、题目
二、解法
首先考虑 \(m=0\) 的情况怎么做,发现如果某个点连出去一个菊花的情况,我们都有点搞不定。不难感受到本题直接确定起始点再验证的思路是困难的,我们不妨考虑逐步筛选起始点。
套用类似归纳的方法,假设我们现在有集合 \(S\) 表示可能的起始点,其中的点可能管辖一些点,那么走到它就可以走到它管辖的点。我们考虑任取 \(S\) 中的两个点 \((u,v)\) 并询问,那么如果有边 \(u\rightarrow v\),我们把 \(v\) 也纳入 \(u\) 的管辖范围,就可以把 \(v\) 从 \(S\) 中删去,这样就归纳到了子问题,不难分析询问次数 \(O(n)\)(如果边是 \(v\rightarrow u\) 那么处理方式同理)
回到本题,考虑更普遍的情况。由于本题是只能经过同色边,那么在构造中我们尽量要把绿边和粉边独立开来。
考虑这样构造初始的 \(S\):选出一个子集,使得它们之间没有粉边连接,并且它们可以到达所有点,换句话说就是这些点通过粉边可以管辖所有点。那么这样场上是不存在粉边的,就可以套用 \(m=0\) 的方法。
但是删除一个点之后,可能 \(S\) 还需要一些改动,那么我们在一开始的时候保留一个拓扑图,发现 \(S\) 中的点都是入度为 \(0\) 的点,在删除一个点之后类似拓扑排序把新释放出来的,入度为 \(0\) 的点加入即可。这样最后剩下的点就是答案,因为它可以通过粉边到达自己初始管辖的点,可以通过绿边到达已经被删除的点。
时间复杂度 \(O(n+m)\)
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,vis[M],in[M],d[M];vector<int> g[M],to[M];
void dfs(int u)
{
vis[u]=in[u]=1;
for(int v:g[u])
{
if(!in[v]) to[u].push_back(v),d[v]++;
if(!vis[v]) dfs(v);
}
in[u]=0;
}
int ask(int u,int v)
{
printf("? %d %d\n",u,v);
fflush(stdout);
return read();
}
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
g[u].push_back(v);
}
queue<int> q;
for(int i=1;i<=n;i++)
if(!vis[i]) dfs(i);
for(int i=1;i<=n;i++)
if(!d[i]) q.push(i);
while(q.size()>1)
{
int u=q.front();q.pop();
int v=q.front();q.pop();
if(!ask(u,v)) swap(u,v);
q.push(u);
for(int x:to[v]) if(!--d[x])
q.push(x);
}
printf("! %d\n",q.front());
}
标签:Pink,粉边,int,CF1142E,read,Floyd,管辖,front,include 来源: https://www.cnblogs.com/C202044zxy/p/16287298.html