【笔记】竞赛图
才知道有这么个神奇的玩意。
定义,\(n\) 个点,任意两点之间存在且恰好存在一条有向边的图成为 \(n\) 阶竞赛图。
性质 \(1\) :一定存在一条哈密顿路径。
证明:数学归纳法,\(n=1\) 显然成立,当 \(n-1\) 成立时的哈密顿路径,存在相邻两点\(v_i,v_{i+1}\),使得 \(v_i\to n\ \land\ n\to v_{i+1}\) 则可以当第 \(n\) 个点插入。否则第 \(n\) 个点一定可以插入路径首/尾。
性质 \(2\) :缩点后一定是一条链。
直接证明:因为存在一条哈密顿路径,所以缩点一定是路径上连续的一段,因此缩完之后还是一条链。
反证法:假设不存在一条链,而整张图又弱连通,所以缩完点后存在 \(x\to z,y\to z\)。由于是竞赛图,\(x,y\)之间一定存在有向边,所以\(x,y\)可以重新缩点或者形成一条链。
性质 \(3\) :竞赛图的每一个强连通都存在哈密顿环。
证明:数学归纳法。\(n=1\) 显然成立,当 \(n-1\) 成立时,对于第 \(n\) 个点,如果单独形成 \(\rm SCC\) 显然成立。否则如果加入另一个\(\rm SCC\),则存在 \(x\to n,n\to y\),其中\(x,y\)属于该\(\rm SCC\) 且在环中相邻,那么可以在 \(x,y\) 之间加入第 \(n\) 个点形成新的哈密顿环。
为什么 \(x,y\) 一定在原环中相邻呢?如果不相邻,则原来的环上每一点都指向 \(n\) ,或都被 \(n\) 指向,不能形成新的 \(\rm SCC\),与假设矛盾。
性质 \(4\) :如果 \(x\) 的出度大于 \(y\) 的出度,则 \(x\) 可以到达 \(y\) 。
如果 \(x\) 和 \(y\) 在同一个强连通分量,则显然成立。
否则我们缩点后拓扑排序,第 \(x\) 所在 \(\rm SCC\) 标号 \(u\),\(y\) 所在 \(\rm SCC\) 标号 \(v\)。
如果 \(u>v\) ,则 \(x\) 向 $v $ 中所有点连边,而 \(y\) 只能向标号 \(>v\) 的点连边。同时 \(x\) 也向所有标号 \(>v\) 的点连边,所以 \(x\) 的度数一定大于 \(y\)。
因此,如果\(v<u\),则 \(x\) 的度数小于 \(y\) 与条件不符,所以 \(x\) 的出度大于 \(y\) 的出度是 \(x\) 可以到达 \(y\) 的充分条件。
比如这道题的交互方式已经告诉了我们解法。
在我们得到第一个\(\texttt{Yes}\)后需要结束询问,也就意味着我们需要构造询问使得得到的\(\texttt{Yes}\)能告诉我们两点是强连通的。
题目还给定了入度\(k_i\),我们可以猜测,如果入度大的点能够到达入度小的点,则两个点强连通。
证明:
我们对竞赛图使用强连通分量缩点,然后跑拓扑排序。则在拓扑序中相邻的两个分量,第一个分量中的任意一个点必定向第二个分量中所有点连边,则出度\(\ge sz_r\),而第二个分量不能向第一个分量连边,因为如果右边则可以继续缩点,所以出度\(<sz_r\)。对于其余的联通分量,由于是完全图,则与两个联通分量中连有相同方向的边。
所以如果两个点\(k_i<k_j\),则 \(i\) 的出度大于 \(j\) 的出度。如果 \(i,j\) 在一个联通分量,则显然强连通。否则 \(i\) 一定能到达 \(j\),如果 \(j\) 又能到达 \(i\),则两个点强连通。
一个没有太大用的性质,就是对于 \(k=0\) 的点 ,只有出边,可以将这个点删掉,并将其他 \(k\) 减 \(1\)。这样可以大大减少交互次数。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 505
using namespace std;
int n,T;
typedef pair<int,int> Pr;
Pr k[N];
struct node{
int l,r,val;
bool operator<(const node o)const{
return val>o.val;
}
}a[N*N];
int main(){
cin>>n;
rep(i,1,n)cin>>k[i].first,k[i].second=i;
sort(k+1,k+n+1);
int j=1;
while(j<=n&&k[j].first==0){
rep(u,j+1,n)k[u].first--;
j++;
}
rep(x,j,n)rep(y,x+1,n)a[++T].l=k[x].second,a[T].r=k[y].second,a[T].val=k[y].first-k[x].first;
sort(a+1,a+T+1);
rep(i,1,T){
cout<<"? "<<a[i].r<<" "<<a[i].l<<endl;
string op;cin>>op;
if(op=="Yes"){cout<<"! "<<a[i].l<<" "<<a[i].r<<endl;return 0;}
}
cout<<"! 0 0"<<endl;
return 0;
}
竞赛图一定存在一条哈密顿路径。
我们先找到哈密顿路径,然后缩点的时候一定是连续的一段缩成一个点。
考虑寻找哈密顿路径,我们递归处理。先找前 \(i-1\) 个点的哈密顿路径,再将第 \(i\) 个点插入。这是个经典问题直接二分即可。
至于缩点,我们倒叙枚举当前点 \(i\) ,找到 \(i\sim n\) 的点中能到达的哈密顿路径中最前面的点的标号。
不难发现能到达的最前面的点具有单调性,直接双指针扫一遍即可。
注意每组测试数据结束后一定要再读入一个反馈(因为这个调了一个小时
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 105
using namespace std;
int n,u[N],fa[N],mat[N];
bool get(int x,int y){
cout<<"1 "<<x-1<<" "<<y-1<<endl;
int op;cin>>op;return op;
}
bool ask(int x,int l,int r){
cout<<"2 "<<u[x]-1<<" "<<r-l+1<<" ";
rep(i,l,r)cout<<u[i]-1<<" ";
cout<<endl;int op;cin>>op;return op;
}
void solve(){
memset(fa,0,sizeof(fa));
memset(u,0,sizeof(u));
memset(mat,0,sizeof(mat));
cin>>n;
u[1]=1;fa[1]=1;
int tot=0;
rep(i,2,n){
int l=0,r=i;
while(l+1<r){
int mid=(l+r)>>1;
int cur=get(u[mid],i);
tot++;
if(cur)l=mid;else r=mid;
}
pre(j,i-1,r)u[j+1]=u[j];
u[l+1]=i;
}
int cur=n;
pre(i,n,2){
cur=min(cur,i);
while(cur>1&&ask(i,1,cur-1))cur--;
fa[i]=cur;
}
rep(i,1,n)rep(j,fa[i],i)fa[i]=min(fa[i],fa[j]);
rep(i,1,n)mat[u[i]]=i;
cout<<"3 "<<endl;
rep(i,1,n){
rep(j,1,n)if(fa[mat[i]]==fa[mat[j]])putchar('1');
else if(mat[i]<mat[j])putchar('1');else putchar('0');
cout<<endl;
}int op;cin>>op;
}
int main(){
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}