NOI2019 I 君的探险

I 君的探险

地下宫殿可以抽象成一张 \(N\) 个点、\(M\) 条边的无向简单图(简单图满足任意两点之间至多存在一条直接相连的边),洞穴从 \(0 \sim n − 1\) 编号。目前你并不知道边有哪些。

每个洞穴都拥有一个光源,光源有开启、关闭两种状态,只有当光源处于开启状态时它所在的洞穴才会被照亮。初始时所有的光源都处于关闭状态,而光源的状态只能用 I 君发现的神秘机关改变。更具体的,使用神秘机关可以进行如下四种操作:

  1. 向机关给定一个编号 \(x\),机关将会改变 \(x\) 号洞穴,以及与 \(x\) 号洞穴有通路直接相连的洞穴的光源状态。即原来开启的光源将会关闭;原来关闭的光源将会开启。

  2. 向机关给定一个编号 \(x\),机关将会显示当前 \(x\) 号洞穴光源的状态。

  3. 向机关给定两个编号 \(x, y\),表示你确定有一条连接 \(x\) 号洞穴与 \(y\) 号洞穴的通路,并让机关记录。

  4. 向机关给定一个编号 \(x\),机关将会判断与 \(x\) 号洞穴相连的通路是否都已被记录。

机关在完成上一次操作后才能进行下一次操作。机关不能随意使用,因此每种操作的使用次数都有限制,分别为 \(L_m, L_q, M, L_c\)。你的任务是,编写一个程序,帮助 I 君决定如何合理利用神秘机关,从而正确地找到这 \(M\) 条通路。

https://uoj.ac/problem/483

题解

将分部分分进行介绍。

测试点1~5

修改\(x\)之后查询\(x+1\sim N\)有没有变化即可。\(n-1\)次modify,\(\binom{N}{2}\)次query。

测试点6~9

使用分治。对当前集合内的点挨个扫描,亮了不管,没亮点亮,直到亮了\(\frac{N}{2}\)个为止。

这样集合就被分成了亮了的和没亮的两部分,分别递归处理即可。

\(N\log_2N\)次modify和query。

有一种更简单的方法,那就是以\(\frac{1}{2}\)的概率修改每个点。这样一对匹配点亮了和没亮的概率都是\(\frac{1}{2}\),复杂度相同。

测试点10~11

由于父节点编号小于子节点,所以对于单个节点\(x\)来说可以二分他的父节点。把\([1,mid]\)中的点都修改一遍然后查询一下\(x\)的状态就能确定\(x\)的父节点在不在\([l,mid]\)中。

对所有节点考虑,整体二分即可。

\(N\log_2N\)次modify和query。

测试点12~17

在无环图上,我们希望用一种“剥叶子”的过程,通过逐步删去度数为\(1\)的点,最后找到整个图的形态。

我们不妨记每个点的标号为\(1\sim N\),对每个\(k∈[1,\log_2N]\),我们把二进制下第\(k\)位为\(1\)的点拿出来MODIFY,然后QUERY全体点

  • 在这个操作下,\(x\)号点颜色改变,当且仅当\(x\)关联了奇数个第\(k\)位为\(1\)的点

不难发现,对每个\(k\)做一遍这个过程后,我们可以得知每个点关联的全体点的标号异或和。显然这个过程花费\(N\log_2 N\)次MODIFY和QUERY

现在,记\(sum[i]\)\(i\)关联的全体点(不包括自己)的异或和,考虑一个点\(x\)

  • \(x\)度数为\(1\),那么\(sum[x]\)\(x\)恰好有一条边

  • 通过两次QUERY、一次MODIFY,可以判定是否存在一条\(sum[x]\)\(x\)的边

  • 若存在这样一条边,可以令\(sum[sum[x]]=sum[sum[x]]⊕x\)以及\(sum[x]=0\),然后“断开”这条边

造一个队列\(Q\),初始时每个点都在其中,每次从\(Q\)中取出一个点\(x\),检查\(x\)\(sum[x]\)之间是否有一条边

  • 若是,把\(sum[𝑥]\)加入\(Q\),并按如上所述过程断开边

重复上述过程直到\(Q\)为空

正确性:

  • 若图中无环,当\(Q\)空的时候,显然所有的边都已被发现且处理

效率:

  • 初始时\(Q\)中有\(N\)个点,每条边的发现会向\(Q\)中带来一个新的点。因此处理的点的个数不超过\(N+M≤2N−1\)

  • 每次处理要求常数次MODIFY和QUERY,因此总消耗为\(N\)

总复杂度\(N\log_2N\)

测试点18~25

考虑随机化,我们跟6~9一样,先随机修改\(\frac{N}{2}\)个点,把修改的集合记为\(S\)

然后对所有点查询,如果为\(1\)那么一定与\(S\)中的有连边,把这些点记作集合\(T\)

然后跟上面的整体二分一样,我们对\(S\)集合修改一半,如果之前\(T\)中的点有变动,那么肯定跟这一半有连边。

期望的复杂度是\(O(M\log M)\)

namespace T1_5{
	CO int N=500;
	int val[N];
	
	void main(int n,int m){
		for(int i=0;i<n-1;++i){
			modify(i);
			for(int j=i+1;j<n;++j){
				int x=query(j);
				if(val[j]!=x) report(i,j),val[j]=x;
			}
		}
	}
}

namespace T6_9{
	CO int N=2e5;
	int a[N],val[N];
	
	void solve(int l,int r){
		if(l>=r) return;
		if(l+1==r) return report(a[l],a[r]);
		for(int i=l;i<=r;++i)if(rand()&1) modify(a[i]);
		for(int i=l;i<=r;++i) val[a[i]]=query(a[i]);
		sort(a+l,a+r+1,[&](int a,int b)->bool{
			return val[a]<val[b];
		});
		int mid=l-1;
		for(int i=l;i<=r;++i)if(val[a[i]]==0) mid=i;
		solve(l,mid);
		solve(mid+1,r);
	}
	void main(int n,int m){
		iota(a,a+n,0);
		solve(0,n-1);
	}
}

namespace T10_11{
	CO int N=2e5;
	int a[N],b[N];
	
	void solve(int l,int r,int ql,int qr){
		if(l>r) return;
		if(ql==qr){
			for(int i=l;i<=r;++i) report(a[i],ql);
			return;
		}
		int mid=(ql+qr)>>1;
		for(int i=ql;i<=mid;++i) modify(i);
		int h=l-1,t=r+1;
		for(int i=l;i<=r;++i){
			if(a[i]<=mid or query(a[i])) b[++h]=a[i];
			else b[--t]=a[i];
		}
		for(int i=ql;i<=mid;++i) modify(i);
		copy(b+l,b+r+1,a+l);
		solve(l,h,ql,mid);
		solve(t,r,mid+1,qr);
	}
	void main(int n,int m){
		iota(a,a+n,0);
		solve(1,n-1,0,n-1);
	}
}

namespace T12_17{ // base 1
	CO int N=2e5;
	int sum[N],vis[N];
	set<pair<int,int> > edge;
	
	void main(int n,int m){
		for(int k=0;1<<k<=n;++k){
			for(int i=1;i<=n;++i)if(i>>k&1) modify(i-1);
			for(int i=1;i<=n;++i) sum[i]^=(query(i-1)^(i>>k&1))<<k;
			for(int i=1;i<=n;++i)if(i>>k&1) modify(i-1);
		}
		deque<int> que;
		for(int i=1;i<=n;++i) que.push_back(i),vis[i]=1;
		while(que.size()){
			int x=que.front();
			que.pop_front(),vis[x]=0;
			if(sum[x]<1 or sum[x]>n or sum[x]==x) continue;
			int v0=query(sum[x]-1);
			modify(x-1);
			int v1=query(sum[x]-1);
			if(v0==v1 or edge.count({min(x,sum[x]),max(x,sum[x])})) continue;
			report(x-1,sum[x]-1);
			edge.insert({min(x,sum[x]),max(x,sum[x])}); // edit 1
			sum[sum[x]]^=x;
			if(!vis[sum[x]]) que.push_back(sum[x]),vis[sum[x]]=1;
			sum[x]=0;
		}
	}
}

namespace std{
	template<>
	struct hash<pair<int,int> >{
		IN size_t operator()(CO pair<int,int>&a)CO{
			return (int64)a.first<<32^a.second;
		}
	};
}

namespace T18_25{
	CO int N=2e5;
	unordered_set<pair<int,int> > edge;
	vector<pair<int,int> > buc;
	vector<int> to[N];
	int chk[N],val[N];
	
	void link(int x,int y){
		if(x>y) swap(x,y);
		if(edge.count({x,y})) return;
		report(x,y);
		edge.insert({x,y});
		buc.push_back({x,y});
		chk[x]=check(x),chk[y]=check(y);
	}
	void flip(int x){
		val[x]^=1;
		for(int y:to[x]) val[y]^=1;
		modify(x);
	}
	void solve(vector<int> a,vector<int> b){
		if(b.empty()) return;
		if(a.size()==1){
			for(int x:b) link(x,a[0]);
			return;
		}
		vector<int> la,ra,lb,rb;
		for(int x:a){
			if(rand()&1) flip(x),la.push_back(x);
			else ra.push_back(x);
		}
		for(int x:b){
			if(query(x)!=val[x]) lb.push_back(x);
			else rb.push_back(x);
		}
		for(int x:la) flip(x);
		solve(la,lb);
		solve(ra,rb);
	}
	void main(int n,int m){
		while((int)edge.size()<m){
			for(CO pair<int,int>&e:buc)
				to[e.first].push_back(e.second),to[e.second].push_back(e.first);
			buc.clear();
			vector<int> a,b;
			for(int i=0;i<n;++i)if(!chk[i])
				if(rand()&1) a.push_back(i),flip(i);
			for(int i=0;i<n;++i)if(!chk[i])
				if(query(i)!=val[i]) b.push_back(i);
			for(int x:a) flip(x);
			solve(a,b);
		}
	}
}

void explore(int n,int m){
	srand(20030506);
	if(n<=500) return T1_5::main(n,m);
	if(n%10==8) return T6_9::main(n,m);
	if(n%10==7) return T10_11::main(n,m);
	if(n%10==6 or n%10==5) return T12_17::main(n,m);
	T18_25::main(n,m);
}

posted on 2020-08-09 20:46  autoint  阅读(389)  评论(0编辑  收藏  举报

导航