[IOI 2017]simurgh

这篇博客介绍了如何解决IOI 2017中关于无向联通图的问题,特别是如何在限制询问次数内找出生成树。文章首先探讨了当节点数小于100时的解决方案,然后分析了完全图的情况,提出了两步策略:第一步求解生成树及其状态,第二步逐步添加边并动态更新度数。在一般图上,文章提出了找环的方法来确定边的状态。总体询问次数为3n+nlog2n,时间复杂度为O(nm)。

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

题意:有一张n个点m条边的无重边,无自环的无向联通图。
里面有一个生成树T,然后你需要找到它。
你每次可以询问一个生成树T’,交互库会返回给你T与T‘交集的大小。
满足n⩽500,m⩽n∗(n−1)2n\leqslant 500,m\leqslant \frac{n*(n-1)}{2}n500,m2n(n1)
用不超过8000次询问搞定。
我们先考虑一下n⩽100n\leqslant 100n100时怎么办。
我们可以把整个图变成几个简单环的并集,且这些简单环接近两两不相交。
对于一个环C,我们可以用∣C∣|C|C次询问找出其中每条边的状态。
故这种解决方法需要m次。
不妨再想一种特殊情况,即给出的图是一张完全图的时候。
分两步走:
第一步: 求出一颗生成树S,并求出其中每条边的状态
在完全图中,每条边都位于一个长度为3的环上。
所以这里每条边的状态可以用3次询问出来。
第二步: 逐步加边,扩展成T
求出S上的边的状态有一个好处,就是对于图上的一个森林M,你可以求出M中有多少条边是在T上的。
做法如下:类似kruskal一样,将S的边往M加,即能联通就加。
这样形成了一个生成树N,询问N并去除掉S的边的影响即可得到M中有多少条边是在T上的。
有了这样一种神奇操作后,我们考虑逐步加边。
我们可以动态更新每个点的度数。
每个点当前的度数即为未加的边中以它为端点的条数。
一开始,即加了0条边的时候,每个点的度数可以通过询问原图中以它为端点的边构成的森林得出。
加入一条边(u,v)时,degreeu−−,degreev−−degree_u--,degree_v--degreeu,degreev即可
每次我们拿出当前一个度数为大于0的点,通过二分寻找到一条边即可。
如此循环n-1次。
在一般图上,我们也走上面两步,但第一步的实现方法在一般图上不太适用。
必须改进。采用的是找环,但这个找环跟上面那个找环不同。
割边必为树边。
S中的非割边一定会位于至少一个环上。
对于不在S的边(u,v),(u,v)和u,v在S上的简单路径会形成一个环。
如果能求出所有这种环上每条边的状态,那么S上每条边的状态就可以求出。
好!
逐步加入这种环,假设当前环是C。
假设C中S的边中未求出的边有x条。
那么当x=0,不管。
当x=C中S的边,便历整个环,需要x+1次询问。
当x<C中S的边,询问一条C中S已知道的边,将该边答案与未知的边的答案做对比即可。询问次数:x+1
因为每次考虑一个环时x>0,所以这一部分最多询问2n-2次。
第二部分最多询问n+nlog2nn+nlog_2nn+nlog2n次。
故总询问次数3n+nlog2n3n+nlog_2n3n+nlog2n,时间复杂度O(nm)O(nm)O(nm)
可以在IOI官网上下载数据。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include "simurgh.h"
using namespace std;
int n;
#define V 505
#define E 130010
vector<int> edge1,edge2;
int bel[V],fa[V],depth[V];
int head[V],v[V<<1],nxt[V<<1],tot=0;
int seq[E],cnt=0;
vector<int> tree;
int rel[V][V];
bool vis[V];
bool mark[E],ans[E];
vector<int> answer;
inline void add_edge(int s,int e){
	tot++;v[tot]=e;nxt[tot]=head[s];head[s]=tot;
	tot++;v[tot]=s;nxt[tot]=head[e];head[e]=tot;
}
int getroot(int x){
	if(bel[x]!=x)bel[x]=getroot(bel[x]);
	return bel[x];
}

void dfs(int u,int f){
	fa[u]=f;
	for(int i=head[u];i;i=nxt[i])
		if(v[i]^f){
			depth[v[i]]=depth[u]+1;
			dfs(v[i],u);
		}
}

vector<int> vec;
int solve1(){
	for(register int i=0;i<n;++i)bel[i]=i;
	for(register int i=0;i<vec.size();++i){
		int fx=getroot(edge1[vec[i]]);
		int fy=getroot(edge2[vec[i]]);
		bel[fx]=fy;
	}
	for(register int i=0;i<tree.size();++i){
		int id=tree[i];
		int fx=getroot(edge1[id]);
		int fy=getroot(edge2[id]);
		if(fx==fy)continue;
		bel[fx]=fy;
		vec.push_back(id);
	}
	return count_common_roads(vec);
}
int solve2(){
	int h=0;
	for(register int i=0;i<n;++i)bel[i]=i;
	for(register int i=0;i<vec.size();++i){
		int fx=getroot(edge1[vec[i]]);
		int fy=getroot(edge2[vec[i]]);
		bel[fx]=fy;
		if(mark[vec[i]])h+=ans[vec[i]];
	}
	for(register int i=0;i<tree.size();++i){
		int id=tree[i];
		int fx=getroot(edge1[id]);
		int fy=getroot(edge2[id]);
		if(fx==fy)continue;
		bel[fx]=fy;
		vec.push_back(id);
		h+=ans[id];
	}
	return count_common_roads(vec)-h;
}

int pass[V],len,res[V];
int LCA(int a,int b){
	while(depth[a]>depth[b])a=fa[a];
	while(depth[a]<depth[b])b=fa[b];
	while(a!=b){
		a=fa[a];
		b=fa[b];
	}
	return a;
}
void search(int id,int x,int y,int lca){
	int t=-1;
	len=0;
	while(x!=lca){
		if(mark[rel[x][fa[x]]])t=rel[x][fa[x]];
		pass[++len]=rel[x][fa[x]];
		x=fa[x];
	}
	while(y!=lca){
		if(mark[rel[y][fa[y]]])t=rel[y][fa[y]];
		pass[++len]=rel[y][fa[y]];
		y=fa[y];
	}
	pass[++len]=id;
	if(t!=-1){
		vec.clear();
		for(register int i=1;i<=len;++i)
			if(pass[i]!=t)vec.push_back(pass[i]);
		int cmp=solve1();
		for(register int i=1;i<len;++i)
			if(!mark[pass[i]]){
				vec.clear();
				for(register int j=1;j<=len;++j)
					if(j!=i)vec.push_back(pass[j]);
				mark[pass[i]]=true;
				if(solve1()!=cmp)ans[pass[i]]=(!ans[t]);
				else ans[pass[i]]=ans[t];
			}
	}else{
		int maxv=-1,secmaxv=-1;
		for(register int i=1;i<=len;++i){
			vec.clear();
			for(register int j=1;j<=len;++j)
				if(j!=i)vec.push_back(pass[j]);
			res[i]=solve1();
			if(res[i]>maxv){
				maxv=res[i];
				secmaxv=maxv;
			}else if(res[i]<maxv&&res[i]>secmaxv)secmaxv=res[i];
		}
		if(secmaxv==-1){
			for(register int i=1;i<len;++i){
				mark[pass[i]]=true;
				ans[pass[i]]=false;
			}
		}else{
			for(register int i=1;i<len;++i){
				mark[pass[i]]=true;
				if(res[i]==maxv)ans[pass[i]]=false;
				else ans[pass[i]]=true;
			}
		}
	}
}

inline bool Judge(int mid){
	vec.clear();
	for(int i=1;i<=mid;++i)vec.push_back(pass[i]);
	return solve2();
}

int degree[V];
inline void init(int x){n=x;}
vector<int> find_roads(int n,vector<int> e1,vector<int> e2){
	init(n);
	memset(rel,-1,sizeof(rel));
	for(register int i=0;i<e1.size();++i){
		edge1.push_back(e1[i]);
		edge2.push_back(e2[i]);
	}
	for(register int i=0;i<n;++i)bel[i]=i;
	for(register int i=0;i<edge1.size();++i){
		rel[edge1[i]][edge2[i]]=i;
		rel[edge2[i]][edge1[i]]=i;
		int fx=getroot(edge1[i]),fy=getroot(edge2[i]);
		if(fx==fy){
			seq[++cnt]=i;
			continue;
		}
		bel[fx]=fy;
		tree.push_back(i);
		add_edge(edge1[i],edge2[i]);
	}
	dfs(0,0);
	for(register int i=1;i<=cnt;++i){
		int lca=LCA(edge1[seq[i]],edge2[seq[i]]);
		int node=edge1[seq[i]];
		bool flag=false;
		while(node!=lca){
			if(!vis[node])flag=true;
			vis[node]=true;
			node=fa[node];
		}
		node=edge2[seq[i]];
		while(node!=lca){
			if(!vis[node])flag=true;
			vis[node]=true;
			node=fa[node];
		}
		if(flag)search(seq[i],edge1[seq[i]],edge2[seq[i]],lca);
	}
	for(register int i=0;i<tree.size();++i)
		if(!mark[tree[i]])mark[tree[i]]=ans[tree[i]]=true;
	for(register int i=0;i<tree.size();++i)
		if(ans[tree[i]])answer.push_back(tree[i]);
	for(register int i=0;i<n;++i){
		vec.clear();
		for(register int j=0;j<n;++j)
			if(rel[i][j]!=-1)vec.push_back(rel[i][j]);
		degree[i]=solve2();
	}
	for(register int T=answer.size()+1;T<n;++T){
		int node=-1;
		for(register int i=0;i<n;++i)
			if(degree[i]==1){
				node=i;
				break;
			}
		len=0;
		for(register int j=0;j<n;++j)
			if(rel[node][j]!=-1)pass[++len]=rel[node][j];
		int l=1,r=len;
		while(l<r){
			int mid=(l+r)>>1;
			if(Judge(mid))r=mid;
			else l=mid+1;
		}
		degree[edge1[pass[l]]]--;
		degree[edge2[pass[l]]]--;
		answer.push_back(pass[l]);
		mark[pass[l]]=true;
		ans[pass[l]]=true;
	}
	return answer;
}/*
g++ -std=c++11 -Wall -O2 -o simurgh grader.cpp simurgh.cpp
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值