CF1608C Game Master

Fan_sheng

2021-12-25 16:12:58

Solution

一道比较显然的隐式图问题。 ## 转化 我们考虑一种情况:选手 A 想要获胜,但是存在一个选手 B,A 在两个场地中都无法击败 B,那么应该怎么处理呢? 我们需要找到一个选手 C 来击败 B,这样 A 就不需要直接面对 B 了。如果 A 也无法击败 C 呢? 显然,这个问题具有传递性。我们最终的目的就是要找到一条长得像 $A\rightarrow K\rightarrow \cdots \rightarrow D \rightarrow C \rightarrow B$ 一样的链,使得 A 最终能够击败 K,那么 A 就能击败这条链上的所有选手。 ## 思路 考虑对于每个选手建一个点,如果 A 能够在任意一个场地中击败 B,我们就从 A 到 B 连一条有向边。如果从 A 出发可以走到某一个点,说明 A 能够击败这个选手。如果从 A 出发可以遍历全图,说明 A 有可能取得冠军。 这样,对图上每个强连通分量 tarjan 缩点后,**有且只有一个点没有入度,只有在这个点内的所有选手可能成为冠军**,我们找到这个点就解决问题。 简单证明一下:如果缩点后,有边 $u \rightarrow v$,v 无论怎样都不可能打败 u ,就不可能是冠军。又因为可以成为冠军的人可以遍历到其他所有节点,那么他们一定被缩在一个点里,这个点没有入度且能遍历到其他所有节点。 ## 建边的优化 在真正写代码的时候,我们会发现,如果真的把每个击败关系都建成边,可以达到 $\mathbb O(n^2)$ 的复杂度,需要优化。实际上,考虑以下的情况: 如果有两条边 $A \rightarrow B$ 和 $B \rightarrow C$,由于具有传递性,$ A \rightarrow C$ 这条边建了跟没建一样。 如果我们对 $a_i$ 和 $b_i$ 排序,只连接排序后大小相邻的两个选手之间的边,就能达到优化为 $\mathbb O(n)$ 的效果。 ## 代码 习惯压行,看着很丑陋。 ```cpp #include<bits/stdc++.h> using namespace std; int t,n,head[100005],flag,dfn[100005],low[100005],st[100005],color[100005],timer,cnt; bool in[100005]; struct star{ int to,nxt; }edge[400005],po[100005]; inline void add(int u,int v){ edge[++flag]=(star){v,head[u]};head[u]=flag; } bool cmp(star a,star b){ return a.nxt<b.nxt; } inline void clear(){ memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low)); memset(head,0,sizeof(head));flag=timer=cnt=0; } void tarjan(int id){ dfn[id]=low[id]=++timer,in[id]=1,st[++st[0]]=id; for(int i=head[id];i;i=edge[i].nxt){ int v=edge[i].to; if(!dfn[v]){ tarjan(v);low[id]=min(low[id],low[v]); } else if(in[v])low[id]=min(low[id],dfn[v]); } if(dfn[id]==low[id]){ cnt++; while(st[st[0]]!=id)color[st[st[0]]]=cnt,in[st[st[0]]]=0,st[0]--; color[id]=cnt,in[id]=0,st[0]--; } } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n);clear(); for(int times=1;times<=2;times++){//读入a和b,相当于循环两次 for(int i=1;i<=n;i++){ scanf("%d",&po[i].nxt);po[i].to=i; } sort(po+1,po+n+1,cmp); for(int i=2;i<=n;i++)add(po[i].to,po[i-1].to); } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].nxt){ int v=edge[j].to;if(color[i]!=color[v])in[color[v]]=1; //in之前是标记是否在tarjan栈内的,这里被我用来标记是否有入度了 } for(int i=1;i<=n;i++)putchar(in[color[i]]?'0':'1');putchar('\n'); } return 0; } ```