P8276 [USACO22OPEN] Hoof and Brain P 题解
peppaking8 · · 题解
USACO 的题都超有意思的。忘了题意的同志们,我们再来回顾一下:
题意
给定
分析
在做完 P 组的 T3 后,发现第一题是 Benq 出的,所以很不可做,所以我们有大把的时间来好好研究这道 T2。所以我们一步步地分析这个题目。下面称题目中的脑 (Brain,B) 为甲,蹄 (Hoof,H) 为乙。
首先,观察样例的询问,为什么
题目没有结束,我们注意到样例中还有
比如上图例子,如果棋子初始在
定义
v 是u 的“支配点”,当且仅当u 走无限多步必须经过v 。也即,在删去v 后,u 不在任何大小>1 的强连通分量内。u 也是u 的支配点。令F(u) 为u 的所有支配点构成的集合,称为支配集。对于询问u_1,u_2 ,若F(u_1)\cap F(u_2) 非空,则甲赢。
对于相交为空的情形,事实上乙会获胜,因为每次都可以避免走到另一个棋子的支配点上。所以至此我们已经找到了甲获胜的等价条件:支配集相交非空。
接下来自然就有了
想要获得更加优秀的做法,就不能暴力求出支配集,而是研究支配的特殊性质。这里我们给出两条:
- 传递性:
v 支配u ,w 支配v ,则w 支配u 。- 偏序性:
v_1,v_2 都支配u ,则v_1,v_2 之间也有支配关系。
根据支配点的定义,容易证明以上两条。其中第二条给了我们启发:既然
回顾 NOI2021 庆典。发现那道题对图的限制和第二条很相似,最后的结论是在缩强连通分量之后图为一棵树。强连通分量内的任两点自然有共同支配点;在树上的两点,因为它们共同指向祖先,那么它们也会有共同支配点——祖先!所以至此我们发现,若将支配的关系看作无向,那么连通分支里的任两点的支配集相交非空。
为什么这一结论十分重要?因为它让这道题变得可做:只需要找到不超过
那么如何找到这些支配关系呢?容易想到的是,若出度
当然在缩点后,虽然所有点的出度都
引理:若图中每个点的出度
\ge 2 ,则不存在任何不相同两点的支配关系。
证明:每次从u 都可找到不经过点v 的路径,故对任意u\not=v ,v 都不支配u 。证毕。
至此我们解决了这道题。或许你在担心实现起来是不是非常麻烦,或是在后悔赛时没有想到正解...不过没关系,USACO 算什么 无论何时,只要领悟了这道题的精髓,从每道题里学到新知识,为什么不值得高兴呢?:)
Talk is cheap, show you the code... 提前声明,代码太丑,仅供参考~
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,m,q;
struct Edge{
int u,v;
int nxt;
}edge[N<<1];
int head[N],tot=0;
int ind[N],outd[N];
bool ok[N];
int fa[2][N];
int rt[2][N];
struct Tree{
int siz;
int ch[2];
}t[N<<6];
int ttot=0,was[N<<5],wastot=0;
bool have[N];
queue<int> tq;
string ans;
inline void add_edge(int u,int v){
tot++;
edge[tot]=(Edge){u,v,head[u]};
head[u]=tot;
ind[v]++,outd[u]++;
}
#define lson t[id].ch[0]
#define rson t[id].ch[1]
inline void pushup(int id){
t[id].siz=t[lson].siz+t[rson].siz;
}
inline int NewNode(){ // 加了空间回收,不然会 MLE
if(wastot){
return was[wastot--];
}
return (++ttot);
}
inline void addwas(int id){
if(wastot>=(N<<5)) return ;
t[id].siz=t[id].ch[0]=t[id].ch[1]=0;
was[++wastot]=id;
}
void Add(int &id,int L,int R,int k,bool del){
if(!id) id=NewNode();
if(L==R){
t[id].siz=del;
if(!del) addwas(id),id=0;
return ;
}
int mid=(L+R)>>1;
if(k<=mid) Add(lson,L,mid,k,del);
else Add(rson,mid+1,R,k,del);
pushup(id);
if(!t[id].siz) addwas(id),id=0;
}
bool Have(int id,int L,int R,int k){
if(!id) return false;
if(L==R&&t[id].siz) return true;
int mid=(L+R)>>1;
return (k<=mid)?Have(lson,L,mid,k):Have(rson,mid+1,R,k);
}
int Findson(int id,int L,int R){
if(L==R) return L;
int mid=(L+R)>>1;
if(t[lson].siz) return Findson(lson,L,mid);
else return Findson(rson,mid+1,R);
}
inline int find(int k,int x){
if(x==fa[k][x]) return x;
return fa[k][x]=find(k,fa[k][x]);
}
int shanx,shany;
void Merge(int &x,int y,int L,int R){
if(!y) return ;
if(!x) x=y;
if(L==R){
if(t[y].siz){
Add(rt[1][shanx],1,n,L,1);
int pp=find(0,L);
Add(rt[0][pp],1,n,shany,0),Add(rt[0][pp],1,n,shanx,1);
if(t[rt[0][pp]].siz==1 && !have[pp]) tq.push(pp);
}
t[x].siz=(t[x].siz|t[y].siz);
return ;
}
int mid=(L+R)>>1;
Merge(t[x].ch[0],t[y].ch[0],L,mid);
Merge(t[x].ch[1],t[y].ch[1],mid+1,R);
pushup(x);
}
inline int makemerge(int k,int x,int y){ // y merge in x
int fx=find(k,x),fy=find(k,y);
if(fx!=fy) fa[k][fy]=fx;
}
void Topo(){
for(int i=1;i<=n;i++){
if(!ind[i]) tq.push(i),ok[i]=true;
}
while(!tq.empty()){
int tp=tq.front();tq.pop();
for(int i=head[tp];i;i=edge[i].nxt){
int v=edge[i].v;
if(!(--ind[v])) tq.push(v),ok[v]=true;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(v,u);
}
Topo();
for(int i=1;i<=m;i++){
int u=edge[i].u,v=edge[i].v;
if(ok[u] || ok[v]) continue;
Add(rt[0][v],1,n,u,1);
Add(rt[1][u],1,n,v,1);
}
for(int i=1;i<=n;i++){
fa[0][i]=fa[1][i]=i;
if(ok[i]) continue;
if(t[rt[0][i]].siz==1) tq.push(i);
}
while(!tq.empty()){
int tp=tq.front();tq.pop();
int now=find(0,tp),now1=find(1,tp);
if(t[rt[0][now]].siz!=1 || have[now]) continue;
int to=Findson(rt[0][now],1,n),nowto=find(0,to),nowto1=find(1,to);
makemerge(0,to,tp);
if(Have(rt[0][nowto],1,n,now1)) Add(rt[0][nowto],1,n,now1,0),Add(rt[1][now1],1,n,nowto,0),have[nowto]=true;
Add(rt[1][nowto1],1,n,now,0);
if(t[rt[1][now1]].siz < t[rt[1][nowto1]].siz){
makemerge(1,nowto1,now1);
shanx=nowto1,shany=now1,Merge(rt[1][nowto1],rt[1][now1],1,n);
}
else{
makemerge(1,now1,nowto1);
shanx=now1,shany=nowto1,Merge(rt[1][now1],rt[1][nowto1],1,n);
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int u,v;
scanf("%d%d",&u,&v);
if(ok[u] || ok[v]){
ans+='B';
continue;
}
int fu=find(0,u),fv=find(0,v);
if(fu==fv) ans+='B';
else ans+='H';
}
cout<<ans<<endl;
}