CF1147F Zigzag Game & 稳定婚姻问题学习笔记
这题太神仙了,不得不记录一下。
我网络流做不动了,DS做不动了,DP做不动了,特别自闭。于是博弈论之神(就是随手切3500博弈的那种) \(\color{black}{\texttt{F}}\color{red}{\texttt{orever_Pursuit}}\) 建议我去学博弈论。
当时看到这题只有两个标签:博弈,交互。就去做了。看到是和交互库玩游戏,感觉很好玩。发现不会,就问了FP。他也不会。然后看题解,发现根本不是博弈(恼。但是这个知识比较重要,还是记一下。准确来说,这是60多年前的论文题/fad,没做过很难现场搞出来。
题解
可以证明选Bob一定必胜(即选择先放棋子)。
首先,我们可以默认我们拿到的操作符是 I
,并且我们选的是左边的数。如果恰好一个有变化,直接把所有边权取相反数即可。
如果必胜,那么意味着我们选了边 \(<a,b>\) ,对方选了 \(<b,c>\) ,我们一定可以找到 \(<c,d>\) ,满足 \(w_{a,b}>w_{b,c},w_{c,d}>w_{b,c}\) 。
这就是稳定婚姻问题,找到一组可行的匹配即可,直接写就完事了简直生草,国外还有论文题。
更明显一点:如果 \(b\) 认为 \(c\) 比 \(a\) 优,那么 \(c\) 一定不能认为 \(b\) 比 \(d\) 优,否则不稳定。
注意两边点的“更优”定义是不同的。左边的点认为更大的更优,右边的点认为更小的更优。
这题没了。可是蒟蒻不会稳定婚姻问题,于是学习笔记就顺带写了(
稳定婚姻问题学习笔记
男孩女孩太不习惯了 ,我们就用这道题的题面,左边的点匹配右边的点
问题定义
给一张完全二分图(你硬说男生在左女生在右也行),每一对左边的点与右边的点之间都有一个评分 \(w_{i,j}\),要求把点两两匹配,满足:
设点 \(a,c\) 在同一边,点 \(b,d\) 在另一边,当前 \((a,b),(c,d)\) 匹配。那么对于所有这种 \((a,b,c,d)\) 不能存在 \(w_{b,a}<w_{b,c}\) (\(b\) 认为\(c\) 比 \(a\) 优)且 \(w_{c,b}>w_{c,d}\) (\(c\) 认为\(b\) 比 \(d\) 优)的情况。
如果存在,那么显然 \(b,c\) 会组成一对,因为这对于 \(b,c\) 来说都更优,那么他们会各自拒绝自己原来的匹配,就不稳定了。
算法流程
定义数组 \(mch_i\) 表示 \(i\) 的匹配。
- 每次取出一个左边的没有匹配的点。
- 如果他当前最优的匹配 \(v\) 没有匹配,那么 \(u\) 匹配 \(v\) 。
- 如果 \(v\) 有匹配并且认为 \(u\) 比他当前的匹配更优,那么 \(u,v\) 匹配,原来的 \(mch_v\) 匹配取消,即 \(mch_v\) 被 \(v\) 拒绝了。
- 如果每一个节点都有匹配,那么婚姻稳定,退出。
注意 最优匹配 指的是还没有拒绝 \(u\) 的点中,\(u\) 认为最优的匹配。
正确性证明
可行性
假设算法结束后,有一个左边的点和右边的点没有匹配上(存在没匹配那么左右至少各一个点点匹配)
- 如果一个右边的点被尝试匹配过了,必然有匹配。
- 如果左边的点一直没有匹配,那么会尝试匹配右边所有的点。
所以右边必然匹配满,那么就不存在失配的问题。
时间复杂度
可以发现,每一个左边的点至多尝试 \(n\) 次与右边的点匹配,那么上界 \(O(n^2)\)
性质
可以发现主动方可以选择到他可以匹配到的最优的匹配。
\(\mathcal{Code}\) (稳定婚姻部分有详细注释。主函数如果你认真看了上面的讲解应该能写出来)
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp(x,y) make_pair(x,y)
#define pb(x) push_back(x)
#define sz(v) (int)v.size()
typedef long long LL;
typedef double db;
template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
#define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
#define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
const int N=55;
int n,w[N][N],mch[N<<1],kth[N<<1],rk[N<<1][N<<1],ID;
vector<int>e[N<<1];
bool cmp1(const int&a,const int&b){return w[ID][a-n]>w[ID][b-n];}
bool cmp2(const int&a,const int&b){return w[a][ID-n]<w[b][ID-n];}
void init(){//稳定婚姻问题模板
fill(mch+1,mch+n*2+1,0),fill(kth+1,kth+n*2+1,0);
queue<int>q;
rep(i,1,n){
e[i].resize(n+1),e[i+n].resize(n+1);//每一个点的匹配边大小是 n
rep(j,1,n)e[i][j]=j+n,e[i+n][j]=j;//拉边
ID=i,sort(e[i].begin()+1,e[i].begin()+n+1,cmp1);
ID=i+n,sort(e[i+n].begin()+1,e[i+n].begin()+n+1,cmp2);//两边的点对于“优”的定义不同
rep(j,1,n)rk[i][e[i][j]]=j,rk[i+n][e[i+n][j]]=j;//处理对于 i 这个节点的所有可能的匹配 j 在 i 看来的优秀程度
q.push(i);//没有匹配的点丢进队列
}
while(!q.empty()){
int u=q.front();q.pop();//找到一个没有匹配的点
int v=e[u][++kth[u]];//kth 是 u 当前最优的匹配排名
if(!mch[v])mch[u]=v,mch[v]=u;//v 还没有匹配
else if(rk[v][u]<rk[v][mch[v]]){
if(mch[v]<=n)q.push(mch[v]);//mch[v] 失去匹配了,重新丢进队列找匹配
mch[mch[v]]=0,mch[v]=u,mch[u]=v;//v 认为 u 更优
}else q.push(u);//没匹配上,丢进去接着找
}
}
void Main(){
scanf("%d",&n);
rep(i,1,n)rep(j,1,n)w[i][j]=read();
printf("B\n"),fflush(stdout);
char fyyAK[5];int x;
scanf("%s%d",fyyAK,&x);
if((fyyAK[0]=='I')^(x>n))rep(i,1,n)rep(j,1,n)w[i][j]*=-1;
init();
while("fyy txdy"){//只能膜拜!
printf("%d\n",mch[x]),fflush(stdout);
scanf("%d",&x);if(!~x)break;
}
}
signed main(){for(int T=read();T;--T)Main();}