才知道有這么個神奇的玩意。
定義,\(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;
}