【UOJ #153】【UR #10】世界线

本文介绍了一种在未知连边方案的二分图中,通过特定的交互过程确定点间匹配关系的算法。该算法利用两次连边和询问操作,通过构造特定的点集和查询策略,最终确定所有点的匹配关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出一张二分图,左边点编号为1~n,右边为n+1到2n{n+1}到{2n}n+12n
这张图满足以下特性:每个点的度数都恰好是1(左边每个点连出去一条,右边每个点被连一条,边为双向)
也就是这张图的连边方案可以用一个长度排列为n的排列描述(每个左边连向右边哪一个),

现在你只知道这张图有2n个点却不知道连边方案,但你可以通过以下流程询问交互库:

第一轮:
阶段1. addedge(x,y):交互库会在左边两点x,y之间连一条无向边;本函数可以执行多次
阶段2. query(x,y):交互库会回答右边两点x,y之间的连通性;本函数可以执行多次
注意:在阶段1是不能执行query(x,y)函数,阶段2不能执行 addedge(x,y)函数
也就是一次性连完,一次性问完
第二轮:(图已被恢复至原二分图)
阶段1. addedge(x,y):交互库会在左边两点x,y之间连一条无向边;本函数可以执行多次
阶段2. query(x,y):交互库会回答右边两点x,y之间的连通性;本函数可以执行多次
注意:同样在阶段1是不能执行query(x,y)函数,阶段2不能执行 addedge(x,y)函数

你必须在第二轮结束后输出答案,

n≤10000n\leq10000n10000
总的连边/询问次数不得超过2∗1062*10^62106


Solution

显然的可以发现,对弈每次询问,我们可以把右边的点分成几个联通块,
为了让右边查询出的联通块和左边我们连出的联通块对应,我只能用过联通块中点的个数来区分,

那么题目就变成了:
我们构造集合集SiS_iSi,使得集合之间没有交集,所有集合的并集为全集,且集合的大小两两不同,
同样的方法构造集合集TiT_iTi
构造的两个集合集需要满足,对于任意a,满足存在Si∩Tj={a}S_i\cap T_j=\{a\}SiTj={a}(交集只有这一个)

显然的,当n满足存在m使得n=(m+1)m2n=\frac{(m+1)m}{2}n=2(m+1)m,一定有解,
以n=10为例:
S1={1}S_1=\{1\}S1={1}
S2={2,3}S_2=\{2,3\}S2={2,3}
S3={4,5,6}S_3=\{4,5,6\}S3={4,5,6}
S4={7,8,9,10}S_4=\{7,8,9,10\}S4={7,8,9,10}
T1={10}T_1=\{10\}T1={10}
T2={9,6}T_2=\{9,6\}T2={9,6}
T3={8,5,3}T_3=\{8,5,3\}T3={8,5,3}
T4={7,4,2,1}T_4=\{7,4,2,1\}T4={7,4,2,1}

这样既可以出唯一解了,

那么对于不满足条件的n怎么办呢?
也可以用同样的方法,先不考虑联通块大小相同所带来的无法一一对应问题,
具体流程是:我们先按上面的方法分好S集,使得相邻SiS_iSi集合大小差不超过1,
以n=8为例:
S1={1}S_1=\{1\}S1={1}
S2={2,3}S_2=\{2,3\}S2={2,3}
S3={4,5}S_3=\{4,5\}S3={4,5}
S4={6,7,8}S_4=\{6,7,8\}S4={6,7,8}
接下来,集合T的构造对于所有i满足∣Si∣=∣Ti∣|S_i|=|T_i|Si=Ti
我们从T的最后一个开始构造,假设TiT_iTi需要放入a个元素,那么我们就从S中,可选元素个数最大的前a个中各挑出一个来组成TiT_iTi
T1={1}T_1=\{1\}T1={1}
T2={6,2}T_2=\{6,2\}T2={6,2}
T3={7,4}T_3=\{7,4\}T3={7,4}
T4={8,5,3}T_4=\{8,5,3\}T4={8,5,3}

加上个表帮助理解

当前T的选择当前S的剩余
S1{1},S2{2,3},S3{4,5},S4{6,7,8}S_1\{1\},S_2\{2,3\},S_3\{4,5\},S_4\{6,7,8\}S1{1},S2{2,3},S3{4,5},S4{6,7,8}
T4{8,5,3}T_4\{8,5,3\}T4{8,5,3}S1{1},S2{2},S3{4},S4{6,7}S_1\{1\},S_2\{2\},S_3\{4\},S_4\{6,7\}S1{1},S2{2},S3{4},S4{6,7}
T3{7,4}T_3\{7,4\}T3{7,4}S1{1},S2{2},S3{},S4{6}S_1\{1\},S_2\{2\},S_3\{\},S_4\{6\}S1{1},S2{2},S3{},S4{6}
T2{6,2}T_2\{6,2\}T2{6,2}S1{1},S2{},S3{},S4{}S_1\{1\},S_2\{\},S_3\{\},S_4\{\}S1{1},S2{},S3{},S4{}
T1{1}T_1\{1\}T1{1}S1{},S2{},S3{},S4{}S_1\{\},S_2\{\},S_3\{\},S_4\{\}S1{},S2{},S3{},S4{}

可以归纳证明,对于任意n,这种构造方法均可行,

接下来我们再来解决联通块大小相同所带来的无法一一对应问题,
显然的,当大小为1的集合出现两个时很好解决,可以直接特判掉;
设两个大小相同的集合分别为Sx,SyS_x,S_ySx,Sy,按照定义必然∣Tx∣=∣Ty∣|T_x|=|T_y|Tx=Ty
我们发现,第一轮过后,我们已经知道点1的出边是什么,记为a1a_1a1
第二轮时,我们再集合TxT_xTx中加入点1,那么右边与a1a_1a1相联通的联通块就一定是对应集合TxT_xTx的,
这样我们就成功区分出了Tx,TyT_x,T_yTx,Ty所对应的联通块,

对于Sx,SyS_x,S_ySx,Sy,可以发现:这两个S集合与两T集合的两两交集大小均为1,
既然我们可以通过点1区分出Tx,TyT_x,T_yTx,Ty,那么我们在构造时就使得Tx,SyT_x,S_yTx,Sy的交集为空,即让TxT_xTx少一个元素,
那么TxT_xTx对应的右边联通块,与SxS_xSx对应的右边联通块,交集大小为1,
那么TxT_xTx对应的右边联通块,与SyS_ySy对应的右边联通块,交集大小为空,

这样就完美解决了一一对应的问题,
剩下的就是简单的集合求并,
(注意:因为T有一个位置少了一个元素,所以有一个元素无法通过集合合并求出,要通过排除法求出)

Code

#include "worldline.h"

#include <cstdio>
#include <algorithm>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define efo(i,q) for(int i=A[q];i;i=B[i][0])
#define min(q,w) ((q)>(w)?(w):(q))
#define max(q,w) ((q)<(w)?(w):(q))
using namespace std;
const int N=100500;
int read(int &n)
{
	char ch=' ';int q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
void WA()
{
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	printf("WAWAWAWAWAWAWAWAWAWAWAWA\n");
	while(1);
}
int n;
int g[N],g1[N];
int bz[200][N],bz1[200][N];
int b[200][N];
int b0;
int z[N],TI;
int d[N],p[N];
void Update()
{
	fod(i,p[0]-1,1)if(b[p[i]][0]>b[p[i+1]][0])
	{
		for(int j=i;j<p[0]&&b[p[j]][0]>b[p[j+1]][0];++j)swap(p[j],p[j+1]);
	}
}
int Find(int q,int w)
{
	int i,j;
	for(i=j=1;i<=bz[q][0]&&j<=bz1[w][0];)
	{
		if(bz[q][i]==bz1[w][j])return bz[q][i];
		if(bz[q][i]<bz1[w][j])++i;
		else ++j;
	}
	return -1;
}
void doit0(int *anse)
{
	int q,w,e=0;
	fod(i,b0,1)
	{
		w=b[b0][b[b0][0]--];
		g1[w]=i;
		fod(j,b0-1,b0-i+1)addedge(b[j][b[j][0]],w),g1[b[j][b[j][0]--]]=i;
	}

	next_step();///////////////

	++TI;
	fo(i,1,n)if(z[i]<TI)
	{
		z[i]=TI;
		d[d[0]=1]=i;
		fo(j,i+1,n)if(z[j]<TI)if(query(i,j))
		{
			z[j]=TI;
			d[++d[0]]=j;
		}
		
		bz1[d[0]][0]=d[0];
		fo(j,1,d[0])bz1[d[0]][j]=d[j];
	}
	fo(i,1,n)
	{
		anse[i]=Find(g[i],g1[i]);
	}
}
void doit1(int *anse)
{
	int q,w,e=0;
	fod(i,b0,1)
	{
		w=b[b0][b[b0][0]--];
		g1[w]=i;
		fod(j,b0-1,b0-i+1)addedge(b[j][b[j][0]],w),g1[b[j][b[j][0]--]]=i;
	}

	next_step();///////////////

	++TI;
	fo(i,1,n)if(z[i]<TI)
	{
		z[i]=TI;
		d[d[0]=1]=i;
		fo(j,i+1,n)if(z[j]<TI)if(query(i,j))
		{
			z[j]=TI;
			d[++d[0]]=j;
		}

		if(!bz1[d[0]][0])
		{
			bz1[d[0]][0]=d[0];
			fo(j,1,d[0])bz1[d[0]][j]=d[j];
		}else
		{
			bz1[0][0]=d[0];
			fo(j,1,d[0])bz1[0][j]=d[j];
		}
	}
	if(bz1[1][1]==bz[0][1])swap(bz1[1][1],bz1[0][1]);
	else if(bz1[0][1]==bz[1][1])swap(bz[1][1],bz[0][1]);
	else if(bz1[1][1]==bz[1][1])swap(bz[1][1],bz[0][1]),swap(bz1[1][1],bz1[0][1]);
	fo(i,1,n)
	{
		anse[i]=Find(g[i],g1[i]);
	}
}

int query_permutation(int n,int anse[])
{
	::n=n;
	int q,w;
	if (n==2) return 0;
	new_round();

	fo(i,0,n+10)g[i]=g1[i]=d[i]=p[i]=0;b0=0;
	for(w=q=1;w<=n&&w+q-1<=n;w+=q,++q)
	{
		g[w]=++b0;
		fo(i,1,q-1)g[i+w]=b0,addedge(i+w,w);
	}
	fo(i,0,b0+10)fo(j,0,n+10)b[i][j]=bz[i][j]=bz1[i][j]=0;
	int LA=n-w+1;
	if(LA)
	{
		fo(i,w+1,n)addedge(w,i);
	}
	fo(i,1,n)
	{
		b[g[i]][++b[g[i]][0]]=i;
	}

	next_step();///////////////

	++TI;
	fo(i,1,n)if(z[i]<TI)
	{
		z[i]=TI;
		d[d[0]=1]=i;
		fo(j,i+1,n)if(z[j]<TI&&query(i,j))
		{
			z[j]=TI;
			d[++d[0]]=j;
		}
		if(!bz[d[0]][0])
		{
			bz[d[0]][0]=d[0];
			fo(j,1,d[0])bz[d[0]][j]=d[j];
		}else
		{
			if(d[0]!=LA||bz[0][0])WA();
			bz[0][0]=d[0];
			fo(j,1,d[0])bz[0][j]=d[j];
		}
	}

	new_round();///////////////////////////////////////////////////////////////////
	if(LA==1)
	{
		doit1(anse);
		return 1;
	}
	if(!LA)
	{
		doit0(anse);
		return 1;
	}
	fo(i,1,b0-1)p[i]=i+1;p[0]=b0-1;
	if(LA)
	{
		++p[0];
		fod(i,b0-1,1)if(b[p[i]][0]>b[0][0])p[i+1]=p[i];
			else {p[i+1]=0;break;}
	}
	fo(i,0,p[0])d[i]=b[p[i]][0];d[p[0]+1]=-1;
	q=0;
	fo(i,1,n)g1[i]=-1;
	fod(i,d[0],1)
	{
		if(d[i]==LA&&d[i]==d[i+1])
		{
			if(p[p[0]])
			{
				w=b[p[p[0]]][b[p[p[0]]][0]--];
				addedge(1,w);
				g1[w]=0;
				fod(j,p[0]-1,p[0]-d[i]+1)if(p[j])addedge(b[p[j]][b[p[j]][0]],w),g1[b[p[j]][b[p[j]][0]--]]=0;
			}else
			{
				w=b[p[p[0]-1]][b[p[p[0]-1]][0]--];
				addedge(1,w);
				g1[w]=0;
				fod(j,p[0]-2,p[0]-d[i]+1)addedge(b[p[j]][b[p[j]][0]],w),g1[b[p[j]][b[p[j]][0]--]]=0;
			}
		}else
		{
			w=b[p[p[0]]][b[p[p[0]]][0]--];
			g1[w]=d[i];
			fod(j,p[0]-1,p[0]-d[i]+1)addedge(b[p[j]][b[p[j]][0]],w),g1[b[p[j]][b[p[j]][0]--]]=d[i];
		}
		Update();
	}

	next_step();///////////////

	++TI;
	z[bz[1][1]]=TI;
	bz1[0][0]=0;
	fo(i,1,n)if(z[i]<TI&&query(i,bz[1][1]))
	{
		z[i]=TI;
		bz1[0][++bz1[0][0]]=i;
	}
	fo(i,1,n)if(z[i]<TI)
	{
		z[i]=TI;
		d[d[0]=1]=i;
		fo(j,i+1,n)if(z[j]<TI)if(query(i,j))
		{
			z[j]=TI;
			d[++d[0]]=j;
		}
		if(!bz1[d[0]][0])
		{
			bz1[d[0]][0]=d[0];
			fo(j,1,d[0])bz1[d[0]][j]=d[j];
		}else WA();
	}
	anse[1]=bz[1][1];
	if(LA)
	{
		if(Find(0,0)!=-1)
		{
			fo(i,1,LA)swap(bz[0][i],bz[LA][i]);
		}
	}
	++TI;
	z[anse[1]]=TI;
	fo(i,2,n)if(g1[i]>-1)
	{
		q=anse[i]=Find(g[i],g1[i]);
		z[anse[i]]=TI;
	}
	fo(i,2,n)if(g1[i]==-1)
	{
		fo(j,1,n)if(z[j]<TI)anse[i]=j;
	}
	return 1;
}
	

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值