题意:有一张n个点m条边的无重边,无自环的无向联通图。
里面有一个生成树T,然后你需要找到它。
你每次可以询问一个生成树T’,交互库会返回给你T与T‘交集的大小。
满足n⩽500,m⩽n∗(n−1)2n\leqslant 500,m\leqslant \frac{n*(n-1)}{2}n⩽500,m⩽2n∗(n−1)
用不超过8000次询问搞定。
我们先考虑一下n⩽100n\leqslant 100n⩽100时怎么办。
我们可以把整个图变成几个简单环的并集,且这些简单环接近两两不相交。
对于一个环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
*/